Skip to content

fix(components): one home for the date display convention in data-table - #8196

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-7620-data-table-date-formatdate
Sep 7, 2026
Merged

fix(components): one home for the date display convention in data-table#8196
os-justin merged 1 commit into
mainfrom
claude/issue-7620-data-table-date-formatdate

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #7620

Maintainer ruling A (comment 5549579006, director seat, 2026-09-05T05:14Z): formatCellValue's date-only branch calls formatDate (default style) instead of building its own Intl.DateTimeFormat bag. The split was never "table versus card" — it was two faces for one value inside one table, picked by which code path the cell happened to take.

What moved, measured

Rendered through the real component (data-table with one date-only string, RTL, the tag the table itself resolves), before and after, on this branch:

locale value before after note
en current year Jul 4, 2026 Jul 4 moved — now equals the date field cell
en 2024-07-04 Jul 4, 2024 Jul 4, 2024 unmoved
de current year 4. Juli 2026 4. Juli moved
de 2024-07-04 4. Juli 2024 4. Juli 2024 unmoved
zh current year 2026年7月4日 7月4日 moved
zh 2024-07-04 2024年7月4日 2024年7月4日 unmoved

ja and ar were measured too and behave exactly like zh and de respectively: the ONLY thing that changes in any locale is that the year token disappears inside the current year. No locale reorders, re-calendars or re-abbreviates — formatDate's default bag is the former bag minus year, so Intl drops one field and keeps the rest. Past and future years are byte-identical everywhere, which is why this split went unnoticed: it diverged only on the dates users look at most.

The pin

packages/components/src/__tests__/data-table-date-convention-7620.test.tsx, in the #7621 shape:

  • FORMER_DATE_BAG is the former option bag copied verbatim; the current-year cell is asserted to EQUAL formatDate and to differ from that bag, and the past-year cell is asserted byte-identical to it.
  • a FIXTURE VALIDITY block asserts the premise both halves rest on — the two formatters disagree on the current-year fixture and agree on the past-year one — so a fixture that stopped exercising the fork (a hard-coded past year, which is what a naive copy of the datetime has two display conventions: DateTimeCellRenderer inlines its own and never calls formatDateTime #7443 pin would be) fails loudly instead of passing for free.
  • the current-year fixture is built from the clock, and July 4 is deliberate: a January or December fixture would fall into the neighbouring year under a non-UTC offset and quietly stop being a current-year date.

Ablation (committed fix, mutated back to the verbatim former bag, mutation proven on disk by anchored grep -c before and after, restore proven BY STATE — git hash-object equal to git rev-parse HEAD:PATH plus an empty git diff HEAD):

  • against the mutated (pre-fix) tree: 7 failed / 20 passed — every current-year assertion red, every past-year assertion green. That asymmetry IS the defect, and both halves are pinned.
  • against the restored tree: 27 passed.

Also restated, not left to rot (objectui#7070)

The #7443 pin's docblock said the date-only half is "deliberately NOT converged". That is now history, so it says so, and says what its own fixture still measures: DATE_ONLY there is a PAST-year value, i.e. exactly the half this change does not move. Its describe block is renamed to say "for a past-year date" rather than "untouched".

Verification

  • pnpm exec vitest run packages/components/ on 7e466a96f (the final commit) — the affected package, full suite: 238 files / 2193 tests passed, exit 0. The 238 includes one temporary measurement instrument that produced the locale table above and was deleted before this PR; CI sees 237.
  • pnpm exec turbo run type-check --filter=@object-ui/components — both projects (tsc --noEmit and tsc -p tsconfig.test.json); tsc -p tsconfig.test.json --listFiles confirms the new pin is a program input of the test project rather than silently excluded.
  • eslint on the three changed files: 0 errors. The repo-wide turbo run lint is CI's; the narrowing is safe to state because this config runs no type-aware program (no projectService, no parserOptions.project) and no custom rule in eslint-rules/ reads the filesystem, so a diff cannot move the verdict on a file it does not touch.
  • node scripts/check-changeset-presence.mjs, check-changeset-no-major.mjs, check-changeset-fixed.mjs, check-control-bytes.mjs — all green.

Changeset

.changeset/7620-data-table-date-convention.md@object-ui/components minor, as ruled, describing the visible change plainly.

Out of scope, filed separately

objectui#8194 records a THIRD face for date values: four @object-ui/fields readonly widget displays call toLocaleDateString(locale) with no options bag at all. It is not addressed here — different package, different landing point, and it wants its own decision the way this one did. Clause-② is no: no contract key moves in this PR. objectui#7619 is untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

`formatCellValue`'s date-only branch built its own `Intl.DateTimeFormat` bag
(`year: 'numeric'`) while the shared `formatDate` drops the year inside the
current year on purpose, so one table painted two faces for the same value
depending on which path the cell took: `Jul 4` in a `date` field cell beside
`Jul 4, 2026` in the fallback cell. The branch now calls `formatDate` (default
style). Current-year cells move (`Jul 4, 2026` -> `Jul 4`); past-year cells are
byte-identical, which is why the split went unnoticed.

Pinned in `data-table-date-convention-7620.test.tsx` against a verbatim copy of
the former bag, with the past-year row asserted unmoved and fixture-validity
assertions that fail if the fixture ever stops exercising the current-year
fork. The #7443 pin's date-only prose is restated: its fixture is a past-year
value, which is exactly the half this change does not move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.7 KB 3191.4 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-DHez5-Dg.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) 15.67KB 5.75KB
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) 5.13KB 2.35KB
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) 498.00KB 113.91KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 187.85KB 52.13KB
fields (index.js) 243.04KB 61.36KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 47.29KB 13.18KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.68KB 63.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.46KB 14.57KB
plugin-list (index.js) 113.34KB 27.72KB
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.33KB 3.25KB
plugin-view (index.js) 84.48KB 20.81KB
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.74KB 1.41KB
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 (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

@os-justin
os-justin marked this pull request as ready for review September 7, 2026 00:22
@os-justin
os-justin added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit c15d7ec Sep 7, 2026
33 of 34 checks passed
@os-justin
os-justin deleted the claude/issue-7620-data-table-date-formatdate branch September 7, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

date has the same two-convention split datetime just had: data-table's date-only cell keeps a year that formatDate drops

1 participant