Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

Description

@os-support-ai

Found while running #5120's census (not claiming; observation class). Baseline objectui main @ 9c60144.

The alias

packages/components/src/renderers/complex/table.tsx:43 resolves a cell as:

{row[col.accessorKey || col.name]}

That is the same undeclared name alias #5120 rules on for data-table.tsx:777/:786 — a fourth site of the family (data-table, ObjectDataTable, ObjectGrid pre-#5068, and this). #5120 does not name it, so it is deliberately untouched by that card's PR.

Why it is not a mechanical follow-on

The census that gated #5120 turned up something specific to this renderer: the published docs declare the name spelling as the contract for type: 'table'.

content/docs/api/schema-reference.md (§TableSchema, ~L392-418):

{ "type": "table", "columns": [ { "name": "id", "label": "#", "width": 60 } ] }

and the property table one screen below says, verbatim:

columns | TableColumn[] | Column definitions with name, label, width, align, sortable, render.

content/docs/core/report-schema.mdx:327 authors the same spelling on a table node ({ name: 'region', label: 'Region' }, plus aggregation).

But TableColumn in packages/types/src/data-display.ts:218-226 declares header: string and accessorKey: string, both required, and neither name nor label. So the docs and the type disagree about a type they both call TableColumn — the docs describe the runtime's alias set, the type describes the declared set, and each is internally consistent.

Retiring the alias here would therefore break documented, authorized usage; declaring name on TableColumn is the opposite direction and is explicitly not ruled (#5120). Which of the two spellings TableSchema actually owns is a maintainer call, not a mechanical fix — which is why this is filed rather than folded into #5120's PR.

Recheck

sed -n '25,50p' packages/components/src/renderers/complex/table.tsx
sed -n '392,420p' content/docs/api/schema-reference.md
sed -n '218,227p' packages/types/src/data-display.ts

Grading

finding, not pm:queue. Related: #5120 (the data-table half), #5068, #3951.

Activity

  1. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Triage first-touch grading (finding centralized round, 2026-08-20, triage seat session session_016A4EBi3ky1mjTi6vD2kCvA): finding → pm:blocked — the decision box for this exact alias family already exists at #5120; a second box would split one ruling. Re-verified on origin/main: table.tsx:33/:43 still reads the undeclared aliases, and the published schema-reference.md §TableSchema still contradicts packages/types/src/data-display.ts:222-226 about which spelling TableColumn owns.

    Blocked-by: #5120

    When #5120's ruling lands, apply it to this fourth site and reconcile the docs-vs-types contradiction in the same stroke.


    Generated by Claude Code

  2. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Triage unlock: the family ruling landed on #5120 (2026-08-20 — consumer-side aliases retired, producers resolve identity before delivery, declared header/accessorKey contract wins). For this card that also settles the docs-vs-types contradiction: packages/types' declared contract stands; schema-reference.md §TableSchema corrects to match. Returned to the queue; coordinate/fold with the #5120 implementation (same adapter file — the dispatching seat answers fold-or-serial).

    Blocked-by: #5120 — discharged (ruling landed).


    Generated by Claude Code

  3. claude commented on Aug 21, 2026

    @claude
    Contributor

    Claim: PM loop round 21
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5350-table-alias-fourth-site
    Worktree: objectui-issue-5350
    Domain: repo:objectui
    File surface: packages/components/src/renderers/complex/table.tsx (:33, :43) + content/docs/api/schema-reference.md §TableSchema (stop on breach; explain in the report)
    Container & model: M, mode:subagent
    Clause-②: yes — retires the undeclared col.name tolerance on a fourth site; covered by the family ruling recorded on #5120.

    Serial constraints cleared — this card was blocked and is now unblocked:


    Generated by Claude Code

  4. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    CollaboratorAuthor
    {
      "issue": 5350,
      "status": "done",
      "branch": "claude/issue-5350-table-alias-fourth-site",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5475",
      "premise_still_valid": true,
      "summary": "Both halves landed in one PR, per the family ruling recorded on #5120 (2026-08-20, maintainer verbatim 「其他接受你的建议。」). CODE: renderers/complex/table.tsx retires both undeclared aliases — the header now resolves as col.header (was col.header || col.label, :33) and the cell as row[col.accessorKey] (was row[col.accessorKey || col.name], :43). DOCS: schema-reference.md TableSchema now authors accessorKey/header in its copyable example and its property row states the declared contract, resolving the docs-vs-types contradiction the ruling settled in favour of packages/types. Both columns.map callbacks are typed TableColumn instead of any, so reintroducing an alias here is a type error rather than a reviewer's catch. PREMISE REFINEMENT, not a refutation: the card's two load-bearing claims both held and were re-verified on origin/main, but one supporting claim in the issue body did not — content/docs/core/report-schema.mdx:327 is cited there as authoring 'the same spelling on a table node', and it is not the same defect. That node is a report SECTION rendered by ReportViewer.tsx:345's own markup against ReportField, and packages/types/src/reports.ts:57-66 DECLARES name and label on ReportField. It is correctly spelled for its own contract, so it was excluded rather than migrated. Same for schema-reference.md:1131 (DetailViewSchema related[], forwarded to a RelatedList by DetailView.tsx:1526-1533, never the component-registry table key). SCOPE FENCES HELD: data-table.tsx / ObjectDataTable.tsx / ObjectGrid.tsx and the object-grid doc examples untouched (#5120+#5351 in flight). CRUD DECISION, as instructed: checked #5373 before deciding and left the type:'crud' examples alone — its maintainer ruling (2026-08-20) retires CRUDSchema under ADR-0049 and rewrites that section around shapes that render, so it is scheduled for DELETION rather than re-spelling, crud has no renderer at all, and re-spelling it would be work that gets deleted plus a collision in the one file both cards edit.",
      "tests": "All at final commit 455bbec9c, the tree the PR ships; the union below was run after that commit and the PR body cites the same sha. NEW: packages/components/src/renderers/complex/__tests__/table-column-contract.test.tsx, 5 tests, verbose run shows all 5 green. 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 columns the declared way and stays green (11 passed across both files in a verbose confirmation run) — behaviour the retirement must not move. TYPECHECK: 'pnpm --filter @object-ui/components type-check' -> exit 0; output echoes 'tsc --noEmit && tsc -p tsconfig.test.json', so this is a real pass, not a zero-match --filter exiting 0. BUILD: 'pnpm build --concurrency=2' -> 'Tasks: 43 successful, 43 total' (precondition for the docs gate, which hard-fails on an unbuilt workspace). GATES derived from the diff (scripts/pm/dispatch-gates.mjs is objectstack-only): check-control-bytes OK (4526 tracked files), check-doc-component-types OK, check-doc-snippet-types OK ('Semantic phase: 88 of 88 block(s) judged, 0 failed'), check-doc-links OK, check-changeset-presence OK, check-changeset-no-major OK, check-changeset-fixed OK. Plus my own control-byte grep over the changed files and the PR body: clean. PER-SHAPE MATRIX, measured by rendering through SchemaRenderer and reading back actual thead/tbody text on BOTH legs (row = {name:'Ada', role:'Engineer'}), BEFORE -> AFTER: {accessorKey} headers ''/'' cells 'Ada'/'Ada' (unmoved); {name} headers ''/'' cells 'Ada'/'' (CELLS moved); {header,accessorKey} 'Name'/'Name' + 'Ada'/'Ada' (unmoved); {label,accessorKey} headers 'Name'/'' cells 'Ada'/'Ada' (HEADERS moved); {name,label} (the retired docs spelling) headers 'Name'/'' cells 'Ada'/'' (BOTH moved); {label,accessorKey} (the skills-guide spelling) headers 'Name'/'' cells 'Ada'/'Ada' (HEADERS moved). The only two shapes that move nothing are the two spelled the declared way. LEGIBILITY, measured not assumed: an unresolvable column is NOT dropped and does NOT throw — it keeps its slot, neighbours unaffected, header or cells simply empty — and unlike data-table this renderer keys cells by INDEX rather than by accessor, so it does not even emit React's generic missing-key warning. It is FULLY SILENT. Pinned as behaviour, no diagnostic implemented; that is #5349's question. REVERSE VERIFICATION with direction predicted before the run: no rebuild was needed or done on either leg, and that was checked rather than assumed — vitest.config.mts:264 aliases @object-ui/components to packages/components/src, so both legs transform source with no dist/ in the path. 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 because the latter asserts only slot counts and its neighbour column. OBSERVED: 'Tests 3 failed | 3 passed (6)' (6 = 5 contract tests + the probe), failures being exactly 'does NOT resolve a cell through the retired name alias', 'does NOT resolve a header through the retired label alias', 'renders nothing for a fully alias-spelled column', and every matrix shape flipped as predicted. PREDICTED AND OBSERVED MATCH EXACTLY. Tree restored with 'git checkout HEAD -- table.tsx' and proven clean: git status --short empty, git diff --stat HEAD empty, both lines confirmed back to post-fix bytes. CENSUS (two independent methods over 4174 files across content/docs, skills, examples, apps, e2e, packages, incl. package READMEs): method 1 located all 70 type:'table' occurrences repo-wide and read each one's columns; method 2 bracket-matched all 194 columns arrays in the corpus and classified by spelling and nearest enclosing type. Both agree: exactly FOUR columns arrays sit on a type:'table' node carrying name/label — schema-reference.md:402 (the defect, fixed), skills/objectui/guides/schema-expressions.md:500 (real blast radius, reported not migrated, filed #5473), and the two exclusions above. COUNTER-PROBE: the same method-2 pass found 39 column arrays spelled the declared header/accessorKey way, SIX of them on type:'table' nodes (apps/site/app/playground/page.tsx:751, examples/schema-catalog/.../basic-table.json:3, packages/plugin-dashboard/README.md:496, packages/types/examples/data-display-examples.json:70, StaticTableWidget.test.tsx:131, and this renderer's own defaultProps), so the method demonstrably finds table-enclosed columns 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 schema catalog holds exactly one, already declared-spelled.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5473: BLAST RADIUS of this PR — 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'), which this change makes false, and its worked example at L500 authors { label: 'Name', accessorKey: 'name' } on a type:'table' node, which renders an empty heading once this lands (measured: headers '' cells 'Ada'). Reported and filed rather than silently migrated — skills/** is a governed, customer-published surface outside this card's declared file surface. Sequencing (before/with/after this PR) is triage's call; the data-table hits in the same file at L410 and data-integration.md:212 belong to #5120/#5351, not this.",
        "filed as #5474: the static table renderer reads only 9 of the 20 keys TableSchema/TableColumn declare — derived from the file, not recalled. hoverable, striped, minWidth, align, fixed, type, sortable, filterable, resizable, editable and cell are all inert, and schema-reference.md documents hoverable/striped as working while the example sets both true. Same declared-but-unenforced class as #5373, one level down. NOT fixed here because the routes (implement / retire under ADR-0049 / split the type) are a contract decision, and TableColumn is shared with DataTableSchema so a retirement must be checked against those consumers first. This is also why the corrected example keeps hoverable/striped: deleting them would have pre-empted that decision.",
        "NOT filed, overlaps existing open #4787: LegacyReportRenderer.tsx:67 falls back to ComponentRegistry.get('table') when 'grid' is absent, but hands it a grid-shaped schema ({ type:'grid', rowData, columnDefs }) with no columns and no data. Searched before filing and found 'grid' IS registered — as the LAYOUT grid at packages/components/src/renderers/layout/grid.tsx:50 — so the table fallback is effectively dead and the layout grid receives rowData/columnDefs instead, which is exactly the class #4787 already tracks. Unaffected by this change either way (the alias was never reached, columns being undefined)."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  5. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    CollaboratorAuthor

    os-dev-report

    (Supersedes the previous comment — identical payload. That one opened with the HTML-comment marker, which GitHub's sanitizer stripped on write, leaving it invisible to a marker scan. Reposted with the literal-text marker per the fallback protocol.)

    {
      "issue": 5350,
      "status": "done",
      "branch": "claude/issue-5350-table-alias-fourth-site",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5475",
      "premise_still_valid": true,
      "summary": "Both halves landed in one PR, per the family ruling recorded on #5120 (2026-08-20, maintainer verbatim 「其他接受你的建议。」). CODE: renderers/complex/table.tsx retires both undeclared aliases — the header now resolves as col.header (was col.header || col.label, :33) and the cell as row[col.accessorKey] (was row[col.accessorKey || col.name], :43). DOCS: schema-reference.md TableSchema now authors accessorKey/header in its copyable example and its property row states the declared contract, resolving the docs-vs-types contradiction the ruling settled in favour of packages/types. Both columns.map callbacks are typed TableColumn instead of any, so reintroducing an alias here is a type error rather than a reviewer's catch. PREMISE REFINEMENT, not a refutation: the card's two load-bearing claims both held and were re-verified on origin/main, but one supporting claim in the issue body did not — content/docs/core/report-schema.mdx:327 is cited there as authoring 'the same spelling on a table node', and it is not the same defect. That node is a report SECTION rendered by ReportViewer.tsx:345's own markup against ReportField, and packages/types/src/reports.ts:57-66 DECLARES name and label on ReportField. It is correctly spelled for its own contract, so it was excluded rather than migrated. Same for schema-reference.md:1131 (DetailViewSchema related[], forwarded to a RelatedList by DetailView.tsx:1526-1533, never the component-registry table key). SCOPE FENCES HELD: data-table.tsx / ObjectDataTable.tsx / ObjectGrid.tsx and the object-grid doc examples untouched (#5120+#5351 in flight). CRUD DECISION, as instructed: checked #5373 before deciding and left the type:'crud' examples alone — its maintainer ruling (2026-08-20) retires CRUDSchema under ADR-0049 and rewrites that section around shapes that render, so it is scheduled for DELETION rather than re-spelling, crud has no renderer at all, and re-spelling it would be work that gets deleted plus a collision in the one file both cards edit.",
      "tests": "All at final commit 455bbec9c, the tree the PR ships; the union below was run after that commit and the PR body cites the same sha. NEW: packages/components/src/renderers/complex/__tests__/table-column-contract.test.tsx, 5 tests, verbose run shows all 5 green. 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 columns the declared way and stays green (11 passed across both files in a verbose confirmation run) — behaviour the retirement must not move. TYPECHECK: 'pnpm --filter @object-ui/components type-check' -> exit 0; output echoes 'tsc --noEmit && tsc -p tsconfig.test.json', so this is a real pass, not a zero-match --filter exiting 0. BUILD: 'pnpm build --concurrency=2' -> 'Tasks: 43 successful, 43 total' (precondition for the docs gate, which hard-fails on an unbuilt workspace). GATES derived from the diff (scripts/pm/dispatch-gates.mjs is objectstack-only): check-control-bytes OK (4526 tracked files), check-doc-component-types OK, check-doc-snippet-types OK ('Semantic phase: 88 of 88 block(s) judged, 0 failed'), check-doc-links OK, check-changeset-presence OK, check-changeset-no-major OK, check-changeset-fixed OK. Plus my own control-byte grep over the changed files and the PR body: clean. PER-SHAPE MATRIX, measured by rendering through SchemaRenderer and reading back actual thead/tbody text on BOTH legs (row = {name:'Ada', role:'Engineer'}), BEFORE -> AFTER: {accessorKey} headers ''/'' cells 'Ada'/'Ada' (unmoved); {name} headers ''/'' cells 'Ada'/'' (CELLS moved); {header,accessorKey} 'Name'/'Name' + 'Ada'/'Ada' (unmoved); {label,accessorKey} headers 'Name'/'' cells 'Ada'/'Ada' (HEADERS moved); {name,label} (the retired docs spelling) headers 'Name'/'' cells 'Ada'/'' (BOTH moved); {label,accessorKey} (the skills-guide spelling) headers 'Name'/'' cells 'Ada'/'Ada' (HEADERS moved). The only two shapes that move nothing are the two spelled the declared way. LEGIBILITY, measured not assumed: an unresolvable column is NOT dropped and does NOT throw — it keeps its slot, neighbours unaffected, header or cells simply empty — and unlike data-table this renderer keys cells by INDEX rather than by accessor, so it does not even emit React's generic missing-key warning. It is FULLY SILENT. Pinned as behaviour, no diagnostic implemented; that is #5349's question. REVERSE VERIFICATION with direction predicted before the run: no rebuild was needed or done on either leg, and that was checked rather than assumed — vitest.config.mts:264 aliases @object-ui/components to packages/components/src, so both legs transform source with no dist/ in the path. 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 because the latter asserts only slot counts and its neighbour column. OBSERVED: 'Tests 3 failed | 3 passed (6)' (6 = 5 contract tests + the probe), failures being exactly 'does NOT resolve a cell through the retired name alias', 'does NOT resolve a header through the retired label alias', 'renders nothing for a fully alias-spelled column', and every matrix shape flipped as predicted. PREDICTED AND OBSERVED MATCH EXACTLY. Tree restored with 'git checkout HEAD -- table.tsx' and proven clean: git status --short empty, git diff --stat HEAD empty, both lines confirmed back to post-fix bytes. CENSUS (two independent methods over 4174 files across content/docs, skills, examples, apps, e2e, packages, incl. package READMEs): method 1 located all 70 type:'table' occurrences repo-wide and read each one's columns; method 2 bracket-matched all 194 columns arrays in the corpus and classified by spelling and nearest enclosing type. Both agree: exactly FOUR columns arrays sit on a type:'table' node carrying name/label — schema-reference.md:402 (the defect, fixed), skills/objectui/guides/schema-expressions.md:500 (real blast radius, reported not migrated, filed #5473), and the two exclusions above. COUNTER-PROBE: the same method-2 pass found 39 column arrays spelled the declared header/accessorKey way, SIX of them on type:'table' nodes (apps/site/app/playground/page.tsx:751, examples/schema-catalog/.../basic-table.json:3, packages/plugin-dashboard/README.md:496, packages/types/examples/data-display-examples.json:70, StaticTableWidget.test.tsx:131, and this renderer's own defaultProps), so the method demonstrably finds table-enclosed columns 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 schema catalog holds exactly one, already declared-spelled.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5473: BLAST RADIUS of this PR — 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'), which this change makes false, and its worked example at L500 authors { label: 'Name', accessorKey: 'name' } on a type:'table' node, which renders an empty heading once this lands (measured: headers '' cells 'Ada'). Reported and filed rather than silently migrated — skills/** is a governed, customer-published surface outside this card's declared file surface. Sequencing (before/with/after this PR) is triage's call; the data-table hits in the same file at L410 and data-integration.md:212 belong to #5120/#5351, not this.",
        "filed as #5474: the static table renderer reads only 9 of the 20 keys TableSchema/TableColumn declare — derived from the file, not recalled. hoverable, striped, minWidth, align, fixed, type, sortable, filterable, resizable, editable and cell are all inert, and schema-reference.md documents hoverable/striped as working while the example sets both true. Same declared-but-unenforced class as #5373, one level down. NOT fixed here because the routes (implement / retire under ADR-0049 / split the type) are a contract decision, and TableColumn is shared with DataTableSchema so a retirement must be checked against those consumers first. This is also why the corrected example keeps hoverable/striped: deleting them would have pre-empted that decision.",
        "NOT filed, overlaps existing open #4787: LegacyReportRenderer.tsx:67 falls back to ComponentRegistry.get('table') when 'grid' is absent, but hands it a grid-shaped schema ({ type:'grid', rowData, columnDefs }) with no columns and no data. Searched before filing and found 'grid' IS registered — as the LAYOUT grid at packages/components/src/renderers/layout/grid.tsx:50 — so the table fallback is effectively dead and the layout grid receives rowData/columnDefs instead, which is exactly the class #4787 already tracks. Unaffected by this change either way (the alias was never reached, columns being undefined)."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions