fix(fields): one home for the datetime display convention - #7621
Conversation
`formatDateTime` gains a named `'compact'` style — today's `DateTimeCellRenderer` face, byte-identical — and the cell renders through it instead of inlining its own pair of `Intl` option bags. The cell also reads `field` (it destructured `value` only), so `field.format` selects a datetime style the way it already did for `date`. `data-table`'s `formatCellValue` calls the same function for its datetime branch instead of a third, independently authored bag. `formatDateTime`'s signature is now `(value, style?, options?)`, matching `formatDate`; the `options` added in objectui#4272 moved to position three and every call site moved with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ 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
|
|
…tyle Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
… options.style Maintainer ruling B (objectui#7443, batch #34): the published signature formatDateTime(value, options?) is unchanged and the named 'compact' style is selected through options.style. DateDisplayOptions gains the optional key; the cell calls formatDateTime(date, { style, locale, t }) with the '||' default; the ten-plus in-repo call-site migrations the positional shape forced are reverted to origin/main byte for byte; every pin from ruling 1' is kept and an arity pin guards the signature. Changeset rewritten without the breaking paragraph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ 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
|
Contract review — VERDICT: PASS
Ruling B is executed, byte for byte
⭐ The ruling's own count was wrong, and the dev was right to say soRuling B says "revert the ten in-repo call-site migrations (2 runtime, 8 assertions)". Re-counted from ⭐ The silent-locale-loss mode — confirmed, and then extended past what anyone had claimedLeg A (delete the compact branch): 7 failed / 19 passed, exactly the seven predicted. Leg B (surgically re-insert the refused positional shape): 11 red in the pin file, exactly the dev's set, plus the restored residue pin's Then the reviewer did the thing that turns the dev's aside into a result — re-ran leg B under ⇒ A pin whose locale equals the machine's is blind to this failure mode, and the blind spot moves with the machine. That is the strongest available argument for ruling B, and it was not in the record before. ⭐ And the load-bearing detail: the pre-revision had migrated the residue pin to the positional shape, at which point it was green under A. The repo's pre-existing net catches shape A only because ruling B's "revert the migrations" restored that pin. The revert is load-bearing, not housekeeping. Semver — and the removal of the breaking paragraph is correct, not merely done
Pins verified as literal strings, from the head source in Node 22.22 / ICU 78.2 (no vitest in the loop)Compact The boundary flag — ruled A, with a condition that is now met
⭐ The sharpest form, which neither the dev nor I had named: on The reviewer made filing the follow-up the condition on disposing A. Condition met: objectui#7745. Declared narrowings — all three true, all three closed by CI at this head
Landing state32 check runs enumerated individually at Non-blocking, and what happened to each
3 and 4 ride with the branch update below — ⛔ they do not re-open the review and the PASS is not withdrawn. Generated by Claude Code |
⛔ Correction to my verdict comment — the branch update it referred to is not needed, and I wrote that line without re-reading the stateMy verdict (5552392941) ends: "3 and 4 ride with the branch update below." There is no branch update. Re-read this minute: ⇒ Non-blocking items 3 (changeset byte-identity wording) and 4 ( Everything else in the verdict stands; it was measured. Generated by Claude Code |
⛔ Ready to land, and this seat cannot enqueue it — stating the blocker once
The three landing checks, all met
Plus all 32 check runs enumerated individually at head What is blocking, measured
⇒ A maintainer clicking "Merge when ready" lands this. ⛔ I am not force-merging around a repository rule, and ⛔ I am not pushing an empty commit or re-drafting to nudge the state. I will keep re-checking and will enqueue the moment either the state resolves or the API surface allows it. If it merges by another hand, the only thing still owed is closing card #7443 after reading Follow-ups from the review are already filed and open, so nothing dies when the card closes: #7745 ( Generated by Claude Code |
⛔ Retracting my previous comment — this PR was already in the merge queue when I said it could not be enqueuedMy comment 5552446870 says this seat cannot enqueue the PR and that a maintainer must click "Merge when ready". That is false. Retracted in full.
6m42s after it was already queued. My The root cause is not the one I have been correcting all day, and that mattersThe three earlier corrections today (#7443, #7579, #7621) were staleness — a sentence about state written from memory instead of re-reading. This one is worse and different: I re-read four times and measured the wrong thing. I polled ⇒ I used a proxy instrument, never proved it could register a positive, and read its zero as an answer. That is exactly the discipline this seat has enforced on every dev report and every contract review today: a zero from an instrument you never proved lit is not a reading. I demanded a lit control from four agents this afternoon and did not apply one to myself. The concrete rule, added to the landing routine: ⛔ before reporting that an action failed, name the field that would show it succeeded and prove that field can show a positive. Here the authoritative signal was the Actual state#7621 is in the merge queue and will merge when it reaches the front and its queue checks pass. Nothing is owed by a maintainer. Everything else in the retracted comment was measured and stands: the three landing checks (PASS on record · ⛔ I will still not close card #7443 until I have read Generated by Claude Code |
Fixes #7443
Implements the maintainer ruling of 2026-09-04, decision batch #34, option B — comment 5539935824 — which amends batch #29 (ruling 1′) on the call shape only. One home for the
datetimedisplay convention:formatDateTimegains a named'compact'style that is today's cell face byte-for-byte, the cell renders through it, and the third independently authored spelling converges onto it. The published signatureformatDateTime(value, options?)is unchanged;'compact'rides inoptions.style.Revision history: the first delivery (head
460232c3) shipped shape A — a positionalstyleparameter inserted beforeoptions— which the ruling refuses by name. This revision (heada43aa8101, after mergingmainata00db9ef9) puts the signature back and carries the style insideoptions. Session:session_01KbJQ1y1J12nZxYzFWhP8Q3.The shape, as shipped
packages/core/src/utils/date-display.ts:231—export function formatDateTime(value: string | Date | number, options?: DateDisplayOptions): string, byte-identical tomain's signature. Inside,if (options?.style === 'compact')joins the two halves fromformatDateTimeCompactParts; otherwise the default face, unchanged.DateDisplayOptionsgainsstyle?: string(date-display.ts:75), read byformatDateTimeonly.formatDatestill takes its style positionally and does not read the key — the symmetric long-run shape (both functions readingoptions.style) is additive onformatDateand, per the ruling, not this card's.packages/fields/src/index.tsx:885computesconst style = field?.format || 'compact'and:909callsformatDateTime(date, { style, locale, t }); the compact branch keeps its two-span DOM sourced fromformatDateTimeCompactParts.packages/components/src/renderers/complex/data-table.tsx:774—formatDateTime(new Date(ts), { locale: language })for the datetime branch; the date-only branch keeps its own bag on purpose (datehas the same two-convention splitdatetimejust had:data-table's date-only cell keeps a year thatformatDatedrops #7620, below).'compact'byte-identical,DateTimeCellRendererreadsfield,formatCellValueconverges on the default face with no pixel moved.Call-site migrations reverted — 13 measured, against the ruling's ten
The A shape had moved
optionsfrom position two to three at every in-repo call. All of them are back toorigin/mainbyte for byte (git diff a00db9ef9on the five files is empty):packages/core/src/utils/dataset-format.tspackages/plugin-gantt/src/ObjectGantt.tsxpackages/core/src/utils/__tests__/dataset-format.date.test.tspackages/fields/src/__tests__/date-formatter-residue-4272.test.tspackages/plugin-dashboard/src/__tests__/DatasetWidget.dateMeasure.test.tsxThat is 3 runtime + 10 assertion call expressions across 2 + 3 files. The ruling counts "ten (2 runtime, 8 assertions)": the two runtime files match, the per-expression count does not. Recorded rather than silently adopting either number. The two runtime calls this PR itself adds (the cell and the data-table) were never migrations and are written in the B shape directly.
Pins — every leg re-run at
a43aa8101Readings computed through the built
@object-ui/coredist, for2024-07-04T07:00:00.000Z:formatDateTime(v, { style: 'compact', locale })en-US7/4/2024 7:00 am7/4/2024 7:00 amzh2024/7/4 上午7:002024/7/4 上午7:00de-DE4.7.2024 7:00 am4.7.2024 7:00 amformatDateTimedefault)en-USJul 4, 2024, 07:00 AMJul 4, 2024, 07:00 AMzh2024年7月4日 07:002024年7月4日 07:00de-DE4. Juli 2024, 07:004. Juli 2024, 07:00en-USliteral7/4/2024 7:00 am— green. Two-span DOM (time muted,ml-2) — green in all three locales.||default: an authored empty-stringformatstays on the compact face ('' || 'compact'is'compact';??would yield'') — green.datecell untouched (formatDate;'short'rendersJul 4, '24, the default stays relative) — green. The objectui#4272 shapeformatDateTime(v, { locale })(zhrenders2024年1月5日 08:30,enrendersJan 5, 2024, 08:30 AM) — green, from the restored residue pin.formatDateTime.lengthis 2 (formatDate.lengthis 3, untouched) — that turns red the moment a positional slot is inserted again; plus the Date formatter is half-localized: future relative forms and absolute timestamps stay en-US on a non-English locale #4272 call shape and the compact face selected beside a locale, both throughoptions.a43aa8101:Test Files 2 passed (2) / Tests 33 passed (33)(26 in the fields pin, 7 in the data-table pin).Verification — all at
a43aa8101, from the repo root, heavy runs through the shared verify lockpnpm exec vitest run --maxWorkers=2 packages/fields/ packages/components/src/__tests__/data-table—Test Files 142 passed (142) / Tests 2246 passed (2246), lockVERDICT command-exit 0.pnpm exec vitest run --maxWorkers=2 packages/core/ packages/plugin-gantt/ packages/plugin-dashboard/—Test Files 273 passed (273) / Tests 3821 passed (3821), lockVERDICT command-exit 0.pnpm --filter @object-ui/core|fields|components run type-check(tsc --noEmit && tsc -p tsconfig.test.json), after building the ten-package dependency closure — exit 0 with 0 TS errors, each;--listFilesconfirms the PR's test files are inside the test programs (fields 2, components 1, core 1).plugin-ganttandplugin-dashboardare byte-identical toorigin/mainand are not re-type-checked here; CI runs the full farm.check-changeset-presence(5 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)),check-changeset-no-major(No changeset declares a major bump),check-changeset-fixed(All workspace packages are in the changeset fixed group),check-changeset-overwrite(1 changeset(s) added, 0 modified, 0 deleted),check:self-import,check:phantom-deps,check:control-bytes(OK (scanned 6303 tracked text file(s))).--format json: 0 errors, 184 warnings —no-explicit-any,react-refresh/only-export-componentsand this file's pre-existingreact-hooksfindings. 15 sit on PR-added lines: 11no-explicit-any(theas anycasts the pins and the cell share withDateCellRenderer) and 4react-refreshon the barrel's re-export line, which already carried them onmain. Narrowing evidence: the population is eslint's own config (js+tseslintrecommended, not type-aware — zeroprojectServiceorparserOptionsineslint.config.js, custom rules are per-file AST rules), the file count 5 is read from the JSON output, and no configured rule reads across files, so this diff cannot move an untouched file's verdict.check:readme-exportsis NOT MEASURED locally (it needs all 40 packages built; 10 are). CI'sREADME Export Checkpassed at460232c3with the identical export set.Ablation — two legs; predictions written before running, mutation proven on disk, restore proven by blob hash
No rebuild is owed:
vitest.config.mtsaliases every@object-ui/*specifier topackages/*/src(36 aliases), and chunk 1 passed with zerodist/built, so the mutation sits on the resolution path directly. Each leg proved the edit landed bygrep -con the injected marker (1) and on the deleted text (0) before running anything; randatetime-compact-style-7443.test.tsx(26 tests); then restored withgit checkout HEAD -- ABSOLUTE_PATHundertrap … EXIT INT TERM, proven bygit hash-objectequal to theHEADblob (8d571622…) and an emptygit diff HEAD --name-only.options?.style === 'compact'branchde-DETests 7 failed / 19 passed (26)— exactly thosezhandde-DEfaces (the options bag lands in the style slot, so the locale is lost to the machine'sen-US), the arity pin, and the #4272 call shapeTests 11 failed / 15 passed (26)— exactly thoseLeg B is the guard the ruling exists for: with a positional slot,
{ style, locale }lands in the style position andoptionsis undefined — the silent locale loss objectui#4272 already paid for. Theen-UScases that stayed green in leg B did so only because the machine locale isen-US.Clause-② — every accept-set or published-surface change in this diff, one line each
@object-ui/core:DateDisplayOptionsgains an optionalstyle?: stringkey (additive;formatDateTimereads it,formatDateandformatRelativeDatedo not).@object-ui/core:formatDateTimeaccepts a new value,options.style === 'compact'; any other value, or absence, is the unchanged default face.@object-ui/core: new exportformatDateTimeCompactParts(value, options?)returning the compact face as{ date, time }, or null for the values the module renders as a dash.@object-ui/fields: re-exportsformatDateTimeCompactParts;DateTimeCellRenderernow readsfield.formatas a display style, so an authoredformaton adatetimefield is live where it was inert.@object-ui/componentschanges no rendered output.Changeset
@object-ui/core: minor,@object-ui/fields: minor,@object-ui/components: patch. Argued from this diff: core and fields add public surface (a new export, a new options key, a new accepted value) with nothing removed or reshaped, which isminor; components changes no observable output, which ispatch.majoris CI-refused byscripts/check-changeset-no-major.mjs, and nothing here is breaking.plugin-ganttis no longer listed: it is byte-identical tomain.Serial constraints, re-measured at implementation time
All 11 open PRs checked file by file (four pages for #5400). None touches
date-display.ts,dataset-format.ts,fields/src/index.tsx,data-table.tsxorObjectGantt.tsx: #7726 / #7730 / #7731 / #7732 stay insidepackages/types(pluscontent/docsandpackages/reactfor #7726), #7685 touchespackages/core/src/actions/actionKeys.tsonly, #7058 touchespackage.jsonfiles only, #5400 only.changeset/*entries.maindid move on three surface files since the merge-base (dataset-format.tsmeasure labels,fields/index.tsxlookup display field,ObjectGantt.tsxrecord-source resolution), in hunks disjoint from this PR; merged in ata00db9ef9with no conflicts.Out of scope, unchanged
formatDateTime, with different signatures and different faces —@object-ui/reactre-exports one,@object-ui/fieldsthe other #7619 (two published functions namedformatDateTime) — routed to triage; not touched here.datehas the same two-convention splitdatetimejust had:data-table's date-only cell keeps a year thatformatDatedrops #7620 (the date-only branch offormatCellValue) — remains open; the date-only bag is kept on purpose and pinned unchanged.formatDate— untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code