Repository navigation
fix(core): honour measure.format on the datetime arm of a date-shaped measure - #8852
Conversation
…ed measure
`formatMeasureDate` threaded `format` into `formatDate`'s style parameter on
the date arm and dropped it on the datetime arm, which called
`formatDateTime(v, { locale })`. That was structural rather than a threading
slip -- `formatDateTime(value, options?)` has no style parameter -- so
`format: 'relative'` on a `Field.datetime` measure could not be honoured even
in principle and rendered the absolute face silently.
The datetime arm now selects a formatter instead of threading one, so both
arms honour exactly `'short'` and `'relative'` and nothing else. `'relative'`
routes to `formatRelativeDate`, inheriting its +/-7-day window rather than
re-deciding it here; `'short'` takes the dense face of the value's own type,
which keeps the time of day and matches what a datetime grid cell paints.
No published signature moved. Threading `format` into `formatDateTime`'s
`options.style` would have honoured `'compact'` -- the one word the date arm
does not honour -- while still ignoring both words it does; the call site maps
the vocabulary explicitly and a test pins that it does.
Both docblocks are now qualified per arm. The previous undifferentiated
wording was true of the date arm only, and a triage pass read it as evidence
the defect was fixed.
Refs objectui#8352
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
…red ablation directions The three-leg pin's leg 2 is the positive control -- it exists to show the harness is connected, so no ablation of the DATETIME arm may move it. As first written it also carried the cross-arm assertion, and ablating the fix turned it red, which costs the leg the one property that makes it a control. The cross-arm pair is now its own case. The predicted-directions block is replaced by the directions actually measured, across four ablations: the pre-#8352 arm, two shapes of "just make it relative", and the one-line pass-through into `formatDateTime`'s `options.style`. Two predictions were wrong and are corrected rather than restated -- leg 3 was predicted green on both sides and measured red against the pre-fix arm, because out of the window the honoured reading renders the absolute DATE face while the pre-fix arm rendered the absolute DATETIME face. Refs objectui#8352 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
|
A short link for the reviewer, kept out of the PR body on purpose: editing a PR body after creation downgrades its session-URL footer and appends a second one, so this goes in a comment instead. The out-of-scope finding named at the end of the description is now filed as objectui#8853 — Generated by Claude Code Generated by Claude Code |
✅ 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
|
…r arm The relative bullet claimed that beyond the +/-7-day window "both arms render the absolute face, which is unchanged behaviour". True of the date arm, false of the datetime one, and read as though it covered both -- the exact shape this release's own last paragraph indicts, committed in its own third bullet. The changeset is the one thing a downstream consumer reads to learn whether their UI moved, so a per-arm claim that is only half true is the worst place for it. Re-measured at date-display.ts:125 and :152 before rewriting: `absoluteFallbackOptions` only strips `style` and adds no time component, and the out-of-window branch calls `formatDate(date, undefined, ...)`, which renders through `toLocaleDateString`. So an out-of-window datetime with `format: 'relative'` renders `Oct 19` where the pre-fix arm rendered `Oct 19, 2026, 09:30 AM`. Prose only -- no behaviour, code or test change. The behaviour is correct and is what ruling 2 produced: the honoured sets must be equal to the date arm's, and `'relative'` is day-granular, so its degraded form is a day face on either arm. The bullet now names the delta, gives that reason, and records that it reaches only a measure whose author actually asked for `format: 'relative'`. Refs objectui#8352 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
✅ 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
|
PM 复核 — 通过。已翻 ready,auto-merge 已武装。⛔ PR 状态由本席翻转,你不要再写 本席自己的核验读数(带亮对照)⛔ 那条被退回的更正 —— 本席也自己验了链路,没有只听你的⇒ 窗口外的 datetime 确实从 date + time 变成了 date only。新 bullet 三件事齐了:按臂点名的 delta、天粒度的理由 + 窗口是继承而非在调用点重定、影响面限于真的写了 ⭐ 而你写的那句总结是这次更正里最要紧的一句:
⇒ 这才是诚实的对照。⛔ 把它说成"我们拿走了时分"会让读者去找一个并不存在的回归。 ⭐⭐ 本 PR 里最锋利的两处,都是预测错了并按观察上报
⇒ ⭐⭐ 一个会在主体损坏时跟着变红的对照,不是对照。 本席采纳为标准,已写进本席的台账。 ⭐ 那条你在代码里留下的陷阱记录
⇒ 这是本卡最容易被"顺手修好"的地方:那个
其余核实项
席位意见 —— 你留白的那一行
关卡:非条款② —— 调用点修复 + 按臂限定的 docblock; 落地后本席按内容核验(⛔ 不按 sha),带亮对照。 Generated by Claude Code |
Fixes #8352
A dataset measure over a
datetimefield now honoursmeasure.format, the way one over adatefield already did.The defect
formatMeasureDateroutes a date-shaped measure down one of two arms. The date arm threadedformatonward; the datetime arm calledformatDateTime(v, { locale })and dropped it:That is structural, not a threading slip:
formatDateTime(value, options?)has no style parameter, so the datetime arm could not honourformateven in principle. AField.dateyields2026-09-08and takes the first arm; aField.datetimeyields2026-09-01T00:00:00.000Zand takes the second.The fix, and the two shapes it deliberately is not
The datetime arm selects a formatter instead of threading one. No published signature moved.
Not a new parameter on
formatDateTime. That is a published signature, and its own docblock already records why the positional shape was refused there.Not the one-line pass-through
formatDateTime(v, { locale, style: format }). This looks like the obvious fix and is the defect inverted — measured before the real fix was written:format'relative'In 2 daysSep 11, 2026, 09:30 AM(ignored)In 2 days'short'Sep 11, '26Sep 11, 2026, 09:30 AM(ignored)9/11/2026 9:30 am'compact'Sep 11(not honoured)9/11/2026 9:30 am(honoured!)Sep 11, 2026, 09:30 AMoptions.style's vocabulary is'compact'alone, so the pass-through honours the one word the date arm does not honour while still ignoring both words it does. A test pins that this path does not do it.Which styles, per arm — enumerated before deciding, not assumed
Measured on
formatDate, which is what the date arm actually calls:'short'and'relative'change the output;'compact','YYYY-MM-DD',''and absent all fall to the default locale face. So the honoured set is exactly two words, and the datetime arm's honoured set is now equal to it — no more, no less, asserted as a computed set over both arms rather than claimed.Which face each word selects is per-arm, because the arms render different types:
'relative'resolves throughformatRelativeDateon both arms — the same function, so the same calendar day reads the same phrase for either field type. Its ±7-day fallback is inherited, not re-decided here: putting a second copy of that window in this file is objectui#4576 exactly, which this repo has already paid for once.'short'resolves to the dense narrow-card face of the value's own type:formatDate's'short'for a date,formatDateTime's'compact'for a datetime. The datetime face keeps the time of day, and is byte-identical to what adatetimegrid cell already paints — so a measure tile and a cell showing the same instant cannot drift.Declared consequence: an out-of-window datetime with
format: 'relative'renders the absolute date face (Oct 19), not the absolute datetime face. That isformatRelativeDate's own fallback and is identical to what the date arm does.'relative'is day-granular by construction — it shows no time inside the window either — so showing one only outside it would be the inconsistent option.The docblocks, qualified by arm
The wording above
formatMeasuresaidformat: 'short'andformat: 'relative'are honoured, full stop. True of the date arm, false of the datetime arm — and an objectstack triage pass read it, concluded the defect was fixed, and downgraded the card, which a driven browser run then refuted. Both docblocks now state the date arm and the datetime arm separately. The arms agree today, but the undifferentiated sentence is not coming back: it is what hid the disagreement, and it would read as true now for a reason that has nothing to do with why it was written.Tests — the three-leg pin, and why it is shaped this way
formatRelativeDatefalls back to the absolute form beyond ±7 days, so an out-of-window value renders identically whetherformatis honoured or dropped. Both readings that missed this defect were taken that way (2026-07-04and2026-07-28, 40–60 days old). The suite's existingformatcases use 2024 values and never pass a datetime through them at all — that pair is why this was invisible to it.The new cases pin the clock (
vi.setSystemTime, the convention already inplugin-timeline/plugin-map; the suite pinsTZ=UTC) and measure in-window and out-of-window separately.Directions measured by ablating the shipped fix four ways — A = the pre-fix arm; B1 = wire the arm to
formatRelativeDateunconditionally; B2 = hand-roll the relative phrase, skipping the window; C = the pass-through above:shortparityLeg 2 is the rig check and stays green under every ablation of the datetime arm — that is what makes it a control. It first carried the cross-arm assertion too and went red under A; that was corrected and the pair is its own case, because a control that the defect can move is not one.
Each fake fix leaves something green: B1 leaves leg 3 and the pair green, B2 leaves
shortand vocabulary green. The two must-not-move guards separate them — B1 is the only column reddening the unstyled-default guard, C the only one reddening the pass-through anti-pin.Every ablation leg mutated the file on disk, proved the mutation landed (blob hash moved, marker counted), ran, then restored via
git checkout HEAD -- pathand proved the restore by blob hash equality plus an emptygit diff HEAD.Verification
pnpm --filter @object-ui/core test— 134 files, 2855 tests, 0 failedpnpm --filter @object-ui/core type-check— exit 0 (tsc --noEmit && tsc -p tsconfig.test.json); the new test file confirmed present in the checked set via--listFiles, not assumedpnpm --filter "@object-ui/core^..." build) — the first type-check run failedTS6305on an unbuiltpackages/types/dist, i.e. a stale-artifact false redcheck:control-bytes(7042 files scanned),check:esm-specifiers,check:self-import,check:vi-mock-specifiers,check:vi-mock-inherit,check:comment-mask-corpus(report-only, pre-existing residue at its held ceiling, unmoved),check:governed-queue-guard,check-changeset-presence,check-changeset-no-majorAGENTS.mdas the lit control: control reports GOVERNED, this PR's three paths report NOT GOVERNEDpnpm lint/ fullpnpm testare CI's run, not claimed heref4fdbbb44验收备注
Not measured, not resolved. Whether any published app hits this today — the card marks it NOT MEASURED and nothing here changes that.
Untouched, deliberately:
Date.parseimpossible-date guard on this same function) — sequenced behind this card, and independently confirmed stillpm:queueand unassigned, so the card's serial constraint naming it as in-flight is stale.formatDateTime's'—'return for empty/invalid values — objectui#8507's family, awaiting its own ruling.Noted, not filed (no PR or person is on track to touch these):
honours relativecase for the date arm uses a 2024 value, so it is an agreement assertion that cannot distinguish the relative face from the absolute one. Now covered by the in-window legs beside it; left as-is rather than widening this diff.formatRelativeDatestill does not readoptions.style; the interface doc already records that as a deliberate open question, so it is not a new observation.Filed separately: the same class of defect one surface over —
DateTimeCellRendererpassesfield.formatstraight intooptions.style, so adatetimegrid column silently dropsformat: 'relative'while adatecolumn honours it. Out of scope here (different package, different surface, new verification surface), so it is a card rather than a rider. Issue link in the report.Drafted by the objectui
domain:uiseat in sessionsession_01611D6ZaRaMmwTNQmSbk8MH(recorded in prose as well as in the footer, because the footer form is rewritten on some write paths).🤖 Generated with Claude Code
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Generated by Claude Code