fix(fields): honour relative and short on a datetime cell, as the date cell already did (objectui#8853) - #8959
Merged
Conversation
… date cell already did `DateCellRenderer` and `DateTimeCellRenderer` are neighbours reading ONE authored `field.format` and handing it to two formatters with different vocabularies: `formatDate` honours `'short'` and `'relative'`, while `formatDateTime`'s `options.style` honours `'compact'` alone. So `format: 'relative'` painted the relative face on a `date` column and the verbose absolute face on a `datetime` one — no error, no warning, no fallback. Driven end to end through a real ObjectGrid column before any change, one row, one instant, the key authored on both fields: the `date` cell painted `In 2 days` and the `datetime` cell beside it painted `Sep 11, 2026, 09:30 AM`. The cell now SELECTS a formatter instead of threading one, which is the ruling objectui#8352 already made for `formatMeasureDate`'s datetime arm — the same defect class one surface over. No published signature moved, and no spelling that rendered before is refused now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
Contributor
✅ 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
|
This was referenced Sep 10, 2026
os-steve
marked this pull request as ready for review
September 10, 2026 10:42
This was referenced Sep 10, 2026
os-steve
deleted the
claude/issue-8853-datetime-cell-format-vocabulary
branch
September 10, 2026 11:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8853
DateCellRendererandDateTimeCellRendererare neighbours reading ONE authoredfield.formatand handing it to two formatters with different vocabularies:formatDatehonours'short'and'relative', whileformatDateTime'soptions.stylehonours'compact'alone. Soformat: 'relative'painted the relative face on adatecolumn and the verbose absolute face on adatetimeone — no error, no warning, no fallback.The premise was driven out end to end BEFORE anything changed
The card declared its own gap: "A browser run through a real
datetimegrid column has not been done… the end-to-end claim is inference from a measured formatter plus a read pass-through." So it was driven, not inferred — a realObjectGridover an object carrying adatefield and adatetimefield, both authoredformat: 'relative', one row, one instant (2026-09-11T09:30:00.000Z), clock pinned to2026-09-09T12:00:00.000Z,en-US,TZ=UTC. The grid's own body cells:["1Open", "Row One", "In 2 days", "Sep 11, 2026, 09:30 AM"]["1Open", "Row One", "In 2 days", "In 2 days"]Third cell is the
datecolumn, fourth is thedatetimecolumn beside it. Same key, same instant, same row — honoured on one, dropped on the other. Premise confirmed.The fix, and the three routes
The card left the fix shape to a ruling and listed three routes. This is route 2, and it is inherited rather than newly decided: this card is, in its own first line, "the same defect class" as objectui#8352, which shipped via PR #8852. That ruling was re-measured on
origin/mainrather than taken from the card's transcription —formatDateTime's published signature is still(value, options?)with no style parameter, andformatMeasureDate's datetime arm carries its own comment saying it honours the same two words "by choosing a formatter here".The
datetimecell now SELECTS a formatter instead of threading one:'relative'resolves throughformatRelativeDate, the same function thedatecell reaches, so one calendar day reads the same phrase in either column.'short'resolves to the dense face of this type — the compact face this cell already paints, which keeps the time of day.'compact'and date patterns such as'YYYY-MM-DD'included, falls to the default face exactly as before.⛔
formatDateTime's published signature was not widened (route 1) — expanding a published surface is a manual floor. ⛔ No refusal was added for a currently-accepted spelling (route 3) — that would be a breaking narrowing of a published metadata surface. Both refusals are pinned by tests.The break, spelled out
Two authored spellings render differently than they did, and one loses a component. All three are in the changeset body and each is pinned:
format: 'relative'on adatetimefield: wasSep 11, 2026, 09:30 AM, is nowIn 2 days.format: 'short'on adatetimefield: was that same verbose face, is now the compact face —9/11/2026 9:30 amon the measured instant.'relative'datetime now shows no time of day —Sep 20where it wasSep 20, 2026, 09:30 AM.formatRelativeDatefalls back to an absolute DATE face out there; that window belongs to that function and is inherited rather than re-decided at the call site (re-deciding it would put a second copy of the convention in the renderer — objectui#4576).'relative'is day-granular by construction, and any other fallback would make the two columns unequal again, which is the defect this change closes.Nothing was taken from a working feature: this renderer ignored both words outright before, so it starts honouring a request whose granularity is days, and only for a field whose author actually wrote one of them.
Discriminating power, proven both ways
The defect's signature is a silent drop — the runtime accepts the key, parses it, drops it, and renders something that still looks like a legitimate date — so a green suite proves nothing by itself.
Reverting the call-site change to the pinned base commit and re-running, then restoring:
Tests 6 failed | 14 passed (20)Tests 20 passed (20)The six that went RED are the repro, the three locale-agreement cases, the
'short'face and the out-of-window datetime cost. The fourteen that stayed GREEN are the controls and regression pins — they measure surfaces this change does not touch, which is exactly why they can vouch for the run.The mutation was proven to reach disk before any result was read (blob hash moved
9e593ba1…to8e9e7621…, both source anchors dropped to 0), and the restore was proven by state —git diff HEADempty and the on-disk blob back to9e593ba1…— never by an exit code. The test imports the renderer by relative source path, so no rebuild sits between the mutation and the assertion.Controls in the committed test
datecell, same run, same instant. It already honoured'relative', so it is what "honoured" looks like; without it a datetime assertion could be measuring a broken clock rather than the vocabulary.format: 'relative'and no format at all render identically on the fully workingdatecell. That is the trap objectui#8352's card recorded: a probe built out there reports "identical" on a working path and a broken one alike. It is pinned explicitly, so the in-window choice everywhere else is a measured decision rather than luck.Gates
scripts/pm/dispatch-gates.mjsdoes not exist in this repo, so the families were derived by hand from the changed-file set (packages/fields/src/index.tsx, one new test, one changeset) and are declared as such.pnpm --workspace-concurrency=2 --filter '@object-ui/fields...' buildpnpm --filter @object-ui/fields type-check(tsc --noEmit+tsconfig.test.json)pnpm --filter @object-ui/fields lintpnpm exec vitest run packages/fields/(repo ROOT)pnpm exec vitest run packages/plugin-grid/ packages/plugin-detail/ packages/core/src/utils/node scripts/check-control-bytes.mjsnode scripts/check-changeset-presence.mjsnode scripts/check-changeset-no-major.mjsnode scripts/check-governed-queue-guard.mjs --test …The consumer suites are run because the rendered output changed even though the exported byte surface did not, and both packages render datetime cells.
Acceptance notes
DateDisplayOptions.style's docblock states the split for the functions ("that function's default face"), and the functions are untouched — the mapping is at the cell.DateTimeCellRenderer's own||-not-??note still holds: an authored empty string still lands on the compact face, and that is pinned.dueLikeis deliberately not threaded, and it is filed rather than guessed as objectui#8958. Measured while here:dueLike: trueon adatetimecell is byte-identical to nodueLike(3 days ago, no red class) while thedatecell paintsOverdue 3dintext-red-600— and the Zoddescribetext for that key names "date/datetime". It is a different authored key whose affordance travels with red styling and a field-name heuristic; acquiring a second key's behaviour while honouring the first would be an unruled change.packages/fields/src/index.tsx. Two touchpackages/fields/package.jsononly (the lucide bumps, chore(deps): lucide-react 1.31.0 -> 1.43.0, with the one retired spelling repaired #8941 and chore(deps): bump lucide-react from 1.31.0 to 1.41.0 #7058); the release bot'schangeset-release/mainis stale relative tomainrather than a concurrent editor. Lit control on the same probe: every branch measured returns a non-zero total changed-file count, so those zeroes are readings. The claim's snapshot named docs(guide,readme): spell the grid child listchildren, the keygridreads (objectui#8912) #8951, which is no longer open, and predates nine of the twelve.objectui#8263holdsformatMeasureDateinpackages/core— a different file and package, untouched here.'short'face as9:00 am; the measured face is9:30 am, as the changeset body and the pins both say. Corrected in place rather than left standing.🤖 Generated with Claude Code
https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB