diff --git a/.changeset/25875-audit-log-date-picker.md b/.changeset/25875-audit-log-date-picker.md
new file mode 100644
index 00000000000..27ff2012703
--- /dev/null
+++ b/.changeset/25875-audit-log-date-picker.md
@@ -0,0 +1,10 @@
+---
+'@object-ui/console': patch
+---
+
+fix(console): the audit log's From and To filters use the platform date picker the record forms use
+
+Clause-②: no
+Metadata-protocol: none
+
+Setup → Audit Log drew the browser's native date input for its From and To filters, with the operating system's `mm/dd/yyyy` face and picker whatever the console's language. Both filters now draw the same date picker as a record form's `date` field: a text box in the display locale's format, a calendar popover, a clear button and today / tomorrow shortcuts. A typed day commits on blur or Enter. The filters send the same `created_at` bounds as before (`YYYY-MM-DD`, the To day through 23:59:59), and each filter label still names its text box.
diff --git a/ui/apps/console/src/pages/system/AuditLogPage.filterLabels-13224.test.tsx b/ui/apps/console/src/pages/system/AuditLogPage.filterLabels-13224.test.tsx
index 1338b0d91c4..67d81198ae0 100644
--- a/ui/apps/console/src/pages/system/AuditLogPage.filterLabels-13224.test.tsx
+++ b/ui/apps/console/src/pages/system/AuditLogPage.filterLabels-13224.test.tsx
@@ -26,7 +26,11 @@
* it is that filter's own control: the Action select's trigger, the Object
* combobox, the Actor lookup's trigger, and the two date fields;
* - the controls found by their labels are the live filters: the action, the
- * From date and the To date each still send the filter they always sent.
+ * From date and the To date each still send the filter they always sent;
+ * - From and To are the platform date picker the record forms draw
+ * (objectstack#25875), not the browser's native date input: the label names
+ * the picker's text box, never its calendar button, and a day typed in the
+ * display locale's format commits on blur or Enter as `YYYY-MM-DD`.
*/
import '@testing-library/jest-dom/vitest';
@@ -134,6 +138,18 @@ function renderPage(): void {
render();
}
+/**
+ * Waits for the From and To date pickers to mount. They are the lazily loaded
+ * `getLazyFieldWidget('date')` chunk behind a `Suspense` skeleton, and until
+ * the chunk resolves no text box carries the label's id; the zero-delay ticks
+ * of `settle()` do not cover a chunk load on a slow runner.
+ */
+async function datePickersMounted(): Promise {
+ for (const label of ['From', 'To']) {
+ await waitFor(() => expect(screen.queryAllByLabelText(label, { exact: true })).toHaveLength(1), { timeout: 5000 });
+ }
+}
+
/** The one control a visible filter label names; fails unless it names exactly one. */
function controlLabelled(label: string): HTMLElement {
const controls = screen.queryAllByLabelText(label, { exact: true });
@@ -145,6 +161,7 @@ describe('AuditLogPage — each filter control is named by its visible label (ob
it('Action, Object, Actor, From and To each name exactly one control, their own', async () => {
renderPage();
await settle();
+ await datePickersMounted();
const action = controlLabelled('Action');
const object = controlLabelled('Object');
@@ -160,9 +177,14 @@ describe('AuditLogPage — each filter control is named by its visible label (ob
expect(object).toHaveTextContent('Any object');
// The Actor lookup's trigger button.
expect(actor).toBe(screen.getByTestId('lookup-trigger-user_id'));
- // The two date fields.
- expect(from).toHaveAttribute('type', 'date');
- expect(to).toHaveAttribute('type', 'date');
+ // The two date fields: the platform picker's text box, not the native
+ // date input and not the picker's calendar button (objectstack#25875).
+ for (const field of [from, to]) {
+ expect(field.tagName).toBe('INPUT');
+ expect(field).toHaveAttribute('type', 'text');
+ expect(field.closest('[data-slot="date-picker"]'), 'date picker not rendered').not.toBeNull();
+ }
+ expect(document.querySelector('input[type="date"]')).toBeNull();
expect(new Set([action, object, actor, from, to]).size).toBe(5);
// The accessible name assistive technology announces is the label's.
@@ -187,15 +209,22 @@ describe('AuditLogPage — each filter control is named by its visible label (ob
it('the date fields found by their labels are the live From and To filters', async () => {
renderPage();
await settle();
+ await datePickersMounted();
+ // Typed in the display locale's format (en-US here), committed on blur.
+ const from = controlLabelled('From');
await act(async () => {
- fireEvent.change(controlLabelled('From'), { target: { value: '2026-10-01' } });
+ fireEvent.change(from, { target: { value: '10/01/2026' } });
+ fireEvent.blur(from);
});
await settle();
await waitFor(() => expect(lastFilter()).toEqual({ created_at: { $gte: '2026-10-01' } }));
+ // …and on Enter.
+ const to = controlLabelled('To');
await act(async () => {
- fireEvent.change(controlLabelled('To'), { target: { value: '2026-10-20' } });
+ fireEvent.change(to, { target: { value: '10/20/2026' } });
+ fireEvent.keyDown(to, { key: 'Enter' });
});
await settle();
await waitFor(() =>
diff --git a/ui/apps/console/src/pages/system/AuditLogPage.tsx b/ui/apps/console/src/pages/system/AuditLogPage.tsx
index e4e400972a5..3a0319e38b2 100644
--- a/ui/apps/console/src/pages/system/AuditLogPage.tsx
+++ b/ui/apps/console/src/pages/system/AuditLogPage.tsx
@@ -34,7 +34,7 @@
* once FLS lands)
*/
-import { useCallback, useEffect, useId, useMemo, useState } from 'react';
+import { Suspense, useCallback, useEffect, useId, useMemo, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import { mergeFilterNodes, toFilterNodeSafely } from '@object-ui/core';
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
@@ -42,7 +42,6 @@ import {
Button,
Card,
CardContent,
- Input,
Label,
Badge,
Table,
@@ -71,7 +70,7 @@ import {
Combobox,
type ComboboxOption,
} from '@object-ui/components';
-import { LookupField, objectRefOptionLabel } from '@object-ui/fields';
+import { LookupField, getLazyFieldWidget, objectRefOptionLabel } from '@object-ui/fields';
import { ConsolePageHeader, useAdapter, useMetadata } from '@object-ui/app-shell';
import { Search, X, AlertCircle, ScrollText, ExternalLink } from 'lucide-react';
import { actionBadgeVariant, actionFilterOptionsOf } from './auditLogActions';
@@ -105,6 +104,15 @@ interface AuditRow {
const PAGE_SIZE = 50;
+/**
+ * The platform date picker a record form's `date` field draws (objectui#12642):
+ * a text box in the display locale's format with a calendar popover, through
+ * the shared lazy widget registry, so it stays in its own chunk. It writes
+ * `YYYY-MM-DD` and `''` on clear, the values the From / To filters send.
+ * Resolved once at module scope, not in render (`react-hooks/static-components`).
+ */
+const DateFilterWidget = getLazyFieldWidget('date');
+
/** The object this page browses. */
const AUDIT_OBJECT = 'sys_audit_log';
@@ -562,11 +570,27 @@ export function AuditLogPage() {
- { setFromDate(e.target.value); setPage(0); }} className="h-9" />
+ {/* The record form's date picker. Its `id` reaches the text box, the
+ control the label names; typed text commits on blur or Enter. */}
+ }>
+ { setFromDate(v == null ? '' : String(v)); setPage(0); }}
+ field={{ type: 'date', name: 'created_at_from', label: t('console.system.auditLog.from', { defaultValue: 'From' }) }}
+ />
+