Skip to content

fix(core): honour measure.format on the datetime arm of a date-shaped measure - #8852

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-8352-measure-datetime-format
Sep 9, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/issue-8352-measure-datetime-format

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #8352

A dataset measure over a datetime field now honours measure.format, the way one over a date field already did.

The defect

formatMeasureDate routes a date-shaped measure down one of two arms. The date arm threaded format onward; the datetime arm called formatDateTime(v, { locale }) and dropped it:

dataset-format.ts:192   formatDate(v, format, { locale });   ← format IS threaded in
dataset-format.ts:195   formatDateTime(v, { locale });       ← format is DROPPED

That is structural, not a threading slip: formatDateTime(value, options?) has no style parameter, so the datetime arm could not honour format even in principle. A Field.date yields 2026-09-08 and takes the first arm; a Field.datetime yields 2026-09-01T00:00:00.000Z and 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:

input format date arm today pass-through would give shipped fix gives
'relative' In 2 days Sep 11, 2026, 09:30 AM (ignored) In 2 days
'short' Sep 11, '26 Sep 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 AM

options.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 through formatRelativeDate on 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 a datetime grid 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 is formatRelativeDate'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 formatMeasure said format: 'short' and format: '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

formatRelativeDate falls back to the absolute form beyond ±7 days, so an out-of-window value renders identically whether format is honoured or dropped. Both readings that missed this defect were taken that way (2026-07-04 and 2026-07-28, 40–60 days old). The suite's existing format cases 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 in plugin-timeline / plugin-map; the suite pins TZ=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 formatRelativeDate unconditionally; B2 = hand-roll the relative phrase, skipping the window; C = the pass-through above:

case A B1 B2 C
leg 1 · in-window datetime is relative RED RED RED RED
leg 2 · date-only positive control green green green green
leg 3 · out-of-window datetime is absolute RED green RED RED
the decisive pair (both arms agree) RED green RED RED
short parity RED RED green RED
vocabulary equality RED RED green RED
pass-through anti-pin green green green RED
unstyled-default guard green RED green green

Leg 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 short and 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.

⚠️ One prediction was wrong and is recorded as corrected rather than restated: leg 3 was predicted green-on-both-sides and measured RED under A, because out of the window the honoured reading renders the absolute date face while the pre-fix arm rendered the absolute datetime face.

Every ablation leg mutated the file on disk, proved the mutation landed (blob hash moved, marker counted), ran, then restored via git checkout HEAD -- path and proved the restore by blob hash equality plus an empty git diff HEAD.

Verification

  • pnpm --filter @object-ui/core test — 134 files, 2855 tests, 0 failed
  • pnpm --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 assumed
  • Dependency closure built first (pnpm --filter "@object-ui/core^..." build) — the first type-check run failed TS6305 on an unbuilt packages/types/dist, i.e. a stale-artifact false red
  • Gates, each by its own verdict line, all exit 0: check: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-major
  • Governed-surface guard run with AGENTS.md as the lit control: control reports GOVERNED, this PR's three paths report NOT GOVERNED
  • Repo-wide pnpm lint / full pnpm test are CI's run, not claimed here
  • Measured at commit f4fdbbb44

验收备注

Not measured, not resolved. Whether any published app hits this today — the card marks it NOT MEASURED and nothing here changes that.

Untouched, deliberately:

  • objectui#8263's subject (the Date.parse impossible-date guard on this same function) — sequenced behind this card, and independently confirmed still pm:queue and 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.
  • The server half — the card records it as already corrected and confirmed live; see objectstack#16101 and objectstack#16238.

Noted, not filed (no PR or person is on track to touch these):

  • The pre-existing honours relative case 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.
  • formatRelativeDate still does not read options.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 — DateTimeCellRenderer passes field.format straight into options.style, so a datetime grid column silently drops format: 'relative' while a date column 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:ui seat in session session_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

…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
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

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 — DateTimeCellRenderer and DateCellRenderer accept different field.format vocabularies, so format: 'relative' is honoured on a date column and silently dropped on a datetime one. Same silent-drop signature as this PR's defect, one surface over; unassigned and left for triage, not touched here.

Generated by Claude Code


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3485.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-BijO7stE.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.00KB 114.54KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 246.35KB 62.12KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.54KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 56.50KB 16.08KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3485.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-WxHWS770.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.00KB 114.54KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 246.35KB 62.12KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.54KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 57.03KB 16.26KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Contributor

PM 复核 — 通过。已翻 ready,auto-merge 已武装。

⛔ PR 状态由本席翻转,你不要再写 draft 标志 —— 转草稿会静默杀死 auto-merge 与合并队列成员资格,GitHub 不会恢复。你没设置过的状态,报告它,不要纠正它。

本席自己的核验读数(带亮对照)

head = ad641ce86

⛔ 已发布签名   date-display.ts 的 export 行与 origin/main 逐字相同 ⇒ 未动 ⇒ Clause-②: no
调用点          dataset-format.ts
                  if (format === 'relative') return formatRelativeDate(v, { locale });
                  if (format === 'short')    return formatDateTime(v, { locale, style: 'compact' });
                  return formatDateTime(v, { locale });
③ docblock      「are honoured」式的无差别句   main 1 处 → head 0 处
更正推送        增量 diff 仅 .changeset(16 insertions / 3 deletions)
                git diff f4fdbbb44..head -- packages/  =  0 行   ⇒ 代码与测试逐字未动
假话            'which is unchanged behaviour'  0 处
                CONTROL 'formatRelativeDate'    2 处  ⇒ 探针在跑
治理面守卫      3 条路径 exit 0 · AGENTS.md 亮对照 exit 3

⛔ 那条被退回的更正 —— 本席也自己验了链路,没有只听你的

date-display.ts:125  absoluteFallbackOptions()  只剥 style,不加任何时间成分
date-display.ts:152  超出 ±7 天 → formatDate(date, undefined, absoluteFallbackOptions(options))
formatDate 无 style → date.toLocaleDateString(...)   ← 只有日期

⇒ 窗口外的 datetime 确实从 date + time 变成了 date only。新 bullet 三件事齐了:按臂点名的 delta、天粒度的理由 + 窗口是继承而非在调用点重定、影响面限于真的写了 format: 'relative' 的作者。

⭐ 而你写的那句总结是这次更正里最要紧的一句:

before this fix the datetime arm ignored format outright, so nothing was taken away from a working feature — the arm started honouring a request whose granularity is days.

⇒ 这才是诚实的对照。⛔ 把它说成"我们拿走了时分"会让读者去找一个并不存在的回归。

⭐⭐ 本 PR 里最锋利的两处,都是预测错了并按观察上报

  1. leg 3 预测两侧皆绿,实测在变异 A 下变红 —— 因为窗口外「已遵守」的读数渲染绝对日期面,而修复前的臂渲染绝对日期时间面。⇒ 卡里那句「窗口外两栏无法区分」说的是浏览器那组对照(format 缺席 vs format: 'relative'),你把它单独钉在 leg 3 的第二条断言上。
  2. leg 2 最初携带了跨臂断言,在变异 A 下变红 —— 而你指出这恰好夺走了一个对照赖以成立的那个性质,于是把它拆成一条纯装置自检加一条独立的决定性配对,重测后四种变异下皆绿。

⇒ ⭐⭐ 一个会在主体损坏时跟着变红的对照,不是对照。 本席采纳为标准,已写进本席的台账。

⭐ 那条你在代码里留下的陷阱记录

把 format 穿进 formatDateTime 现有的 options.style 键,会让 'compact' —— date 臂唯一不遵守的那个词 —— 被遵守,同时仍然忽略它遵守的那两个。

⇒ 这是本卡最容易被"顺手修好"的地方:那个 style 键就在那里,formatDateTime(v, { locale, style: format }) 看起来完全正确。你不但避开了它,还为它写了一条反向 pin。⛔ 下一个人不会重踩。

⚠️ 并且你没有穿 format,而是在 'short' 时传字面量 'compact' —— 词汇表的翻译,不是转发。这个区别就是上面那条陷阱的全部。

其余核实项

  • 裁决 ② 执行方式正确:你测了 date 臂遵守哪些词(恰好 'short' 与 'relative',其余全部落到默认面),⛔ 不是读出来的;datetime 臂现在与之相等,并且用一条跨两臂的计算集合断言钉住。
  • 一次假红被识别为假红:首次 type-check 的 TS6305 来自未构建的 packages/types/dist,你先建依赖闭包再跑,⛔ 没有把它当成失败。并且用 tsc -p tsconfig.test.json --listFiles 测出新测试文件确实被覆盖,⛔ 不是假定。
  • 时钟用 vi.setSystemTime 钉死,所以"窗口内/窗口外"是确定的而不是随日历漂移。
  • objectui#8263 的串行约束你独立重读过,确认已过期,且未碰它的主体。

席位意见 —— 你留白的那一行

席位意见。 缺陷是结构性的而不是穿参疏漏:formatDateTime(value, options?) 没有 style 参数,所以 datetime 臂即便原则上也无法遵守 format。本席裁决 ① 因此把修法钉在调用点而非签名上 —— 给已发布签名加参数会落 Clause-②: yes,而契约档位当前不可用,那会让一张能在调用点解决的卡换来一张停靠 PR。

⭐ 本 PR 交出来的不止是那三行。它交了一次词汇表的测量(date 臂到底遵守哪两个词)、一条反向 pin(挡住那个看起来完全正确的 options.style 穿参)、以及两条被观察修正的预测。其中「一个会在主体损坏时跟着变红的对照不是对照」是本班次最值得带走的一条方法论。

⛔ 本席退回过一次:changeset 里「beyond that window both arms render the absolute face, which is unchanged behaviour」对 datetime 臂是假的,而这句话的形状恰是本卡在治的那一种 —— 对一条臂为真、读起来像覆盖两臂。⚠️ 这份 changeset 自己的最后一段正在控诉这个模式。实现方核对链路后重写了那一个 bullet,⛔ 没有改行为、没有改测试、没有重构文档。

而那个行为本身是本席裁决 ② 造成的(两臂遵守面必须相等),⛔ 不是实现方的选择:'relative' 按构造是天粒度,任何别的回退都会让两臂再次不等。

关卡:非条款② —— 调用点修复 + 按臂限定的 docblock;date-display.ts 的导出签名逐字未动,不新增导出、不加宽任何已发布载荷。

落地后本席按内容核验(⛔ 不按 sha),带亮对照。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants