Skip to content

fix(components): the static table renderer reads only the declared TableColumn contract, and §TableSchema is corrected to match - #5475

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-5350-table-alias-fourth-site
Aug 21, 2026
Merged

fix(components): the static table renderer reads only the declared TableColumn contract, and §TableSchema is corrected to match#5475
os-support-ai merged 1 commit into
mainfrom
claude/issue-5350-table-alias-fourth-site

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#5350

The static table renderer was the fourth site of the undeclared column-alias family. It resolved a heading as col.header || col.label and a cell as row[col.accessorKey || col.name], while packages/types/src/data-display.ts declares TableColumn with required header and accessorKey and neither alias. Both aliases are retired here, and the published reference page that contradicted the type is corrected in the same PR.

The ruling this applies, verbatim

Recorded on #5120, 2026-08-20 — maintainer accepted the batch verbatim: 「其他接受你的建议。」

Ruled: retire the consumer-side alias; unify the producers — the #3951 direction for the whole family.

  1. This ruling is the family direction for the satellites: finding(components): the static table renderer is a fourth site of the undeclared column-alias family, and its published docs declare name/label as TableColumn while packages/types declares header/accessorKey #5350 (fourth site, static table renderer — its docs-vs-types contradiction resolves the same way: the declared header/accessorKey contract wins, schema-reference.md §TableSchema corrects to match) and finding(components): data-table's column normalization also reads an undeclared col.label alias for header — the sibling alias on the same two lines as col.name #5351 (labelheader: the producer maps the spec's ListColumnSchema.label to header at delivery — metadata vocabulary stays out of the adapter).

Triage's unlock note on #5350 applies the same words to this card: "packages/types' declared contract stands; schema-reference.md §TableSchema corrects to match."

Both halves, one PR

This site is not a mechanical follow-on of its three siblings, and that is the whole point: the alias here was not merely tolerated, it was published. §TableSchema shipped a copyable { "name": "id", "label": "#" } example plus a property row reading "Column definitions with name, label, …". Retiring the alias without correcting the page would have converted a documented, working example into a silently broken one.

  • packages/components/src/renderers/complex/table.tsxcol.header || col.label becomes col.header; row[col.accessorKey || col.name] becomes row[col.accessorKey].
  • content/docs/api/schema-reference.md §TableSchema — the example authors accessorKey/header; the property row states the declared contract.

Per-shape before/after, measured by rendering

Measured with a temporary probe that renders each shape through SchemaRenderer and reads back the actual thead/tbody text, run on both legs. Row data is { name: 'Ada', role: 'Engineer' }. The probe was removed before commit; the same shapes are pinned permanently in table-column-contract.test.tsx.

authored columnheaders BEFOREcells BEFOREheaders AFTERcells AFTERmoved
{ accessorKey }""Ada""Adano
{ name }""Ada""""cells
{ header, accessorKey }NameAdaNameAdano
{ label, accessorKey }NameAda""Adaheaders
{ name, label } — the retired docs spellingNameAda""""both
{ label, accessorKey } — the skills-guide spellingNameAda""Adaheaders

The two shapes that move nothing are exactly the two spelled the declared way. Legibility of a now-unresolvable column, pinned as measured behaviour rather than endorsed: the column keeps its slot, neighbours are unaffected, nothing throws, and because this renderer keys cells by index rather than by accessor it does not even emit React's generic missing-key warning that data-table produces. It is fully silent. Whether that should become a diagnostic is #5349's question; none is implemented here.

Census, with its counter-probe

Question: every authored type: "table" column using the name/label spelling, across docs, skills, examples, fixtures, catalogs, apps, e2e and package READMEs.

Two independent methods over the same corpus (4174 files: content/docs, skills, examples, apps, e2e, packages):

  1. Locate all 70 type: "table" occurrences repo-wide, then read each one's columns.
  2. Bracket-match every columns: array in the corpus (194 found), classify each by spelling and by the nearest enclosing type.

Both agree. Exactly fourcolumns arrays sit on a type: "table" node and carry name or label:

siteverdict
content/docs/api/schema-reference.md:402 §TableSchemathe defect — corrected in this PR
skills/objectui/guides/schema-expressions.md:500real blast radius — reported, not migrated (below)
content/docs/api/schema-reference.md:1131 §DetailViewSchema related[]excluded: not this renderer
content/docs/core/report-schema.mdx:327excluded: not this renderer

The two exclusions are textual near-misses on a different declared contract, and both were checked by reading the render path rather than the spelling:

Counter-probe. The zero-beyond-the-known-docs claim is only meaningful if the same method can see the spelling that is present, so the run reports both buckets. Method 2 found 39 column arrays spelled the declared header/accessorKey way in the same pass, six of them on type: "table" nodes: apps/site/app/playground/page.tsx:751, examples/schema-catalog/src/schemas/components-complex-table/basic-table.json:3, packages/plugin-dashboard/README.md:496, packages/types/examples/data-display-examples.json:70, packages/plugin-dashboard/src/__tests__/StaticTableWidget.test.tsx:131, and this renderer's own defaultProps. So the method demonstrably finds table-enclosed column arrays and reads their spelling correctly, and the alias bucket was non-empty in the same run. e2e/ holds no type: "table" node at all; the catalog holds exactly one, already declared-spelled.

Blast radius — reported, not migrated

skills/objectui/guides/schema-expressions.md depends on both retired aliases: its prose at L493 documents the accessor fallback as contract ("accessorKey, falling back to name"), and its worked example at L500 authors { "label": "Name", "accessorKey": "name" }, which renders an empty heading after this change. skills/** is a governed, customer-published surface outside this card's declared file surface, so it is filed as #5473 rather than silently migrated. Sequencing is triage's call.

Scope fences held

One bounded in-place fix, named rather than slipped in

The §TableSchema property row also advertised a render property. TableColumn has never declared one — the renderer hook is cell (data-display.ts:277). Correcting name/label in that sentence while leaving render standing would have republished a false claim in a line already being rewritten, so the row now states only what is both declared and honoured here: accessorKey, header, width, className, cellClassName. For the same reason "align": "right" is dropped from the example — alignis declared on TableColumn, but this renderer never reads it, so keeping it would have contradicted the corrected sentence one line below.

align is not alone in that, and the broader gap is not fixed here: 11 of the 20 keys TableSchema/TableColumn declare are inert on this renderer, including hoverable and striped, which the page documents as working and which the example still sets. That is an ADR-0049 enforce-or-remove decision rather than a re-spelling, so it is filed as #5474 and the example keeps them.

Finally, the two columns.map callbacks are typed TableColumn instead of any, so reintroducing an undeclared alias on this renderer is now a type error rather than a reviewer's catch.

Verification

All at final commit 455bbec9c, the tree this PR ships.

  • New: packages/components/src/renderers/complex/__tests__/table-column-contract.test.tsx — 5 tests, all passing.
  • Package suite, from the repo root (a package-scoped run uses a different config than CI): pnpm exec vitest run packages/components --maxWorkers=2 -> Test Files 172 passed (172) / Tests 1565 passed (1565).
  • Pinning: the pre-existing shadowed-renderer-behaviour.test.tsx authors its columns the declared way and stays green — behaviour the retirement must not move, and does not.
  • Typecheck: pnpm --filter @object-ui/components type-check -> exit 0, after pnpm build (43/43 tasks). The run echoes tsc --noEmit && tsc -p tsconfig.test.json, so this is a real pass and not a zero-match filter exiting 0.
  • Gates derived from the diff: check-control-bytes OK (4526 files), check-doc-component-types OK, check-doc-links OK, check-changeset-presence / check-changeset-no-major / check-changeset-fixed OK, and check-doc-snippet-types OK — "Semantic phase: 88 of 88 block(s) judged, 0 failed" (it hard-fails on an unbuilt workspace, so the build above is a precondition, not an optimisation).

Reverse verification

Prediction was recorded before the run. No build artifact sits between the edit and the test: vitest.config.mts:264 aliases @object-ui/components to packages/components/src, so both legs transform source and neither leg needs a rebuild — checked, not assumed.

Ablating the fix (git checkout origin/main -- table.tsx, tests kept) was predicted to flip name-only cells back to Ada, label-bearing headers back to Name, and to turn exactly 3 of the 5 new tests red — the three alias assertions — leaving the declared-spelling test and the legibility test green, since the latter asserts only slot counts and its neighbour column.

Observed: Tests 3 failed | 3 passed (6) (6 = 5 contract tests + the probe), the three failures being precisely does NOT resolve a cell through the retired name alias, does NOT resolve a header through the retired label alias, and renders nothing for a fully alias-spelled column. Every shape in the matrix flipped as predicted. Predicted and observed match exactly.

The tree was then restored with git checkout HEAD -- table.tsx and proven clean: git status --short empty, git diff --stat HEAD empty, and both retired lines confirmed back to their post-fix bytes.

Changeset

.changeset/table-renderer-declared-column-contract-5350.md, @object-ui/components: minor — declared as the behaviour change it is, including the silent failure mode of a retired-spelling column.


Generated by Claude Code

…ontract
Retire the undeclared `col.label` header alias and `col.name` accessor alias
in renderers/complex/table.tsx, the fourth site of the column-alias family, and
correct content/docs/api/schema-reference.md TableSchema to publish the declared
`header`/`accessorKey` spelling it contradicted.
Family ruling recorded on objectui#5120 (2026-08-20).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 58 chunks)3790.7 KB3867.2 KB
Main entry chunk (gzip)25.3 KB350 KB
Entry fileindex-BiNG1E4M.js
StatusPASS

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

PackageSizeGzipped
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.34KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)6.35KB2.43KB
auth (index.js)2.77KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.89KB
auth (useIsWorkspaceAdmin.js)3.04KB1.45KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.98KB113.63KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.21KB59.50KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.22KB3.08KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)30.51KB7.57KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)128.37KB32.91KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)242.04KB60.86KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.40KB30.26KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.75KB54.24KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.64KB27.13KB
plugin-map (index.js)20.08KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.52KB20.67KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)36.10KB12.26KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-support-ai
os-support-ai marked this pull request as ready for review August 21, 2026 01:59
@os-support-ai
os-support-ai added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 490f482Aug 21, 2026
23 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-5350-table-alias-fourth-site branch August 21, 2026 01:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant

@os-support-ai