Repository navigation
feat(fields): record forms use the platform date picker for date fields (objectui#12622) - #12641
Conversation
…cker A record form's date and datetime fields render a locale-format text box with a calendar popover, keyboard entry, clear, and today / tomorrow shortcuts instead of the browser's native input. The value written is unchanged: YYYY-MM-DD for date, an ISO instant for datetime. Only the form registration takes the picker; inline editors and getLazyFieldWidget keep the native input. Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…uote census counts the new span The picker's Intl formatter no longer falls back to a hard-coded tag (objectui#9909 census); a malformed tag is answered as formatDate answers it. The de quote-pairing census moves 86 to 87 for fields.datePicker.invalid's one matched span (objectui#12622). Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
…rm-date-picker Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…n every page The `ui-components` group claimed all of `packages/fields`, so the picker's `import()` in `FORM_FACE_LOADERS` did not affect chunking and the picker was eager. It now lives in `widgets/date-picker/`, a `fields-date-picker` group (priority 82, `includeDependenciesRecursively: false`) takes that directory only, and `PER_CHUNK_MEMBERSHIP_CARVE_OUTS` declares the split. No ceiling, baseline or headroom constant moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Review: ACCEPT — objectui PR #12641 ·
|
Fixes #12622
Clause-②: no
What changes
A record form's
dateanddatetimefields now draw the platform date picker instead of the browser's nativeinput type=date/datetime-local. The picker is a text box in the display locale's format (10/16/2026inen,2026/10/16inzh). It has a calendar popover (the sameCalendarprimitive the calendar view's date popover opens, localized by the same date-fns resolver), keyboard entry (blur or Enter commits), a clear button, and Today / Tomorrow shortcuts.packages/fields/src/widgets/DatePickerField.tsx:DatePickerField/DateTimePickerField, one shared control. The read-only face isDateField/DateTimeField's own, rendered unchanged. A stored nonexistent day keeps the existingfields.date.impossibleDay/fields.dateTime.impossibleDaynotice.packages/fields/src/widgets/localeDateText.ts: formats and reads the text withIntlin the display locale (Latin digits). It accepts the locale's digit order, an ISO day in any locale, a two-digit year, and fordatetimea 24-hour or AM/PM clock (or the locale's own afternoon marker).packages/fields/src/index.tsx:FORM_FACE_LOADERS. OnlyregisterField(what a form resolvesfield:date/field:datetimeto) takes the picker.FIELD_DEFAULTS:fields.datePicker.today,tomorrow,clear,openCalendar,invalid.The value written is unchanged
Both faces go through the existing native adapters (
toDateInputValue,toDateTimeInputValue,fromDateTimeInputValue). Adatefield writesYYYY-MM-DD, adatetimefield writes an ISO instant, and clear writes'', all as the native inputs did. Typed text that names no real date is not written. The box is markedaria-invalidand a notice names the expected pattern.Where the native input stays (measured)
field:datewidget is not registered (no@object-ui/fieldsloaded) falls back to the form renderer's own input, with theinputTypethatObjectFormmaps.DateField/DateTimeFielddirectly and are unchanged:FieldEditWidget(grid / detail inline editors, the kanban required-fields dialog) and plugin-detail'sInlineFieldInput.getLazyFieldWidget(the action-param and bulk-action dialogs) sharesfieldWidgetMapand is unchanged. Moving these surfaces too would break pins inapp-shell/plugin-grid/plugin-detail/plugin-kanban, which are outside this card's file surface. That follow-up is recorded in the report as a decision for the seat.Measured against the dispatch's mechanism assumptions
FilterBuilder,UserFilters,FilterUI) use no picker at all: they render a nativeinput type=date. The calendar view opensPopover+Calendarinline. The exportedDatePickerin@object-ui/componentshas no consumer in the repo. It is a button trigger with the date in the locale (PPP) and a calendar, with no text entry, no clear and no shortcuts. So the reuse is theCalendarprimitive and its one locale resolver. No second calendar was written, and the text box, clear and shortcut layer is the new part. Its labels are the five keys above.toDomPropsforwards:id,name,autoFocus,tabIndex,onBlur/onFocus/onClick,className,disabled, plusaria-*/data-*.ObjectFormmaps nomin/maxfor date fields, so the native input honoured none. The text box receives the same set, soaria-required,disabledand the hostidcarry over (pinned).readonlyrenders the unchanged read-only face.Tests (at
10527ec3e)packages/fields/src/widgets/localeDateText.test.ts(9): en and zh format and placeholder, typed-text reading, a round-trip in all ten locales.packages/fields/src/widgets/DatePickerField-12622.test.tsx(13): noinput[type=date], en and zh rendering, typing writes the ISO day, Enter commits, a bad day is not written, a calendar pick writes ISO, Today / Tomorrow, clear, host id /aria-required/ disabled, the read-only face, anddatetimewrites an ISO instant and keeps its time on a calendar pick.packages/plugin-form/src/__tests__/recordFormDatePicker-12622.test.tsx(2): through the realObjectFormandregisterAllFields, the date rows carry the picker and the label still names the text box. Submitting writes2026-10-20and the ISO instant. CONTROL: thetextandnumberrows still renderinput type=text/type=number.node ../objectstack/scripts/ablation-replace.mjsreplaced theFORM_FACE_LOADERS[...] ?? loaderread inregisterFieldwith plainloader. The anchor went 1 to 0 and the blob changed. The form-path pin turned red (Tests 2 failed (2), "date picker not rendered"). Restore: blob equals HEAD andgit diff HEADis empty. A first attempt used a replacement the tool refused (its text was a substring of the anchor), so no test ran in that attempt.@object-ui/fields,@object-ui/plugin-form,@object-ui/i18ntype-checkexit 0. The new test files are confirmed intsconfig.test.jsonvia--listFiles.registerField/registerAllFields/getLazyFieldWidget/ the labelling table, plusdatetime-widgets,date-locale-channel,inline-widget-aria-invalid-7126, and the i18nall-locales-key-parity,en-zh-key-parity,fallback-placeholder-spelling-3512.packages/fields/,packages/plugin-form/,packages/i18n/. Reason: the shared verify lock was held by another seat's run for more than 25 minutes, so it was never acquired. The narrowed targeted run above is declared in its place, and CI runs the full set.check:phantom-deps,check:self-import,check:unreferenced-sources,check:doc-example-readers,check:handler-key-reads,check:side-effects-array,check:element-data-source-declaration,check:spec-symbols,check:action-forward-parity,check:designer-field-key-parity,check:icon-record-names,check:i18n-keys,check:i18n-drift,check:i18n-designer-parity,check:control-bytes,check:i18n-dead-keys(no new key reported),check-hand-rolled-comment-mask.mjs,check-changeset-fixed.mjs,check-changeset-presence.mjs.eslint --no-inline-configwith the rooteslint.config.js(the config every package'seslint .resolves) over the 17 changed.ts/.tsxfiles gives 0 errors. Only pre-existing warnings remain inindex.tsx, plus threeas anytest-fixture warnings. Type-aware linting is not enabled (noparserOptions.project/projectService), so this diff cannot move a verdict in an untouched file.Acceptance notes
datetime: the time is set by typing (or kept from the value on a calendar pick). The popover has no time control.Generated by Claude Code