Repository navigation
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
Description
Activity
Triage first-touch grading (finding centralized round, 2026-08-20, triage seat session
session_016A4EBi3ky1mjTi6vD2kCvA):finding→pm:blocked— same single ruling as its siblings: #5120 owns the data-table alias decision, and this card'slabeldimension (the spec's ownListColumnSchemaword crossing the adapter boundary) is evidence for that ruling, not a separate one. Re-verified onorigin/main:data-table.tsx:776/:785-786still reads both undeclared aliases.Blocked-by: #5120
Generated by Claude Code
Triage unlock: the family ruling landed on #5120 (2026-08-20). For this card's
label→headernuance: the producer maps the spec'sListColumnSchema.labelintoheaderat delivery — metadata vocabulary does not cross the adapter boundary as a tolerated alias. Returned to the queue; same-file coordination with #5120/#5350 (fold-or-serial is the dispatching seat's to answer).Blocked-by: #5120 — discharged (ruling landed).
Generated by Claude Code
Claim: PM loop round 21
Session:session_01RV6yuVCxymHYE16PL9vQkE
Branch:claude/issue-5120-column-identity-unify
Worktree:objectui-issue-5120
Domain:repo:objectui
File surface:packages/components/src/renderers/complex/data-table.tsx(:777, :786),packages/plugin-dashboard/src/ObjectDataTable.tsx,packages/plugin-grid/src/ObjectGrid.tsx, the sharedcolumnIdentityresolution + tests (stop on breach; explain in the report)
Container & model:L,mode:subagent
Clause-②: yes — removes the undeclaredcol.name/col.labelconsumer tolerance, narrowing the accepted set. Covered by the recorded family ruling on #5120.Fold-or-serial answered by the dispatching seat, as triage asked:
- finding(components/plugin-dashboard): data-table 的访问键归一化读一个自身类型未声明的
col.name别名,而唯一在交付前解析列身份的生产者只有 RelatedList #5120 + finding(components): data-table's column normalization also reads an undeclaredcol.labelalias forheader— the sibling alias on the same two lines ascol.name#5351 FOLDED into this one dispatch — the ruling namesdata-table.tsx:777and:786, and finding(components): data-table's column normalization also reads an undeclaredcol.labelalias forheader— the sibling alias on the same two lines ascol.name#5351 is the:777line (header: col.header || col.label). Same file, same two adjacent lines, one ruling. Splitting them guarantees a conflict. - finding(components): the static
tablerenderer is a fourth site of the undeclared column-alias family, and its published docs declarename/labelasTableColumnwhilepackages/typesdeclaresheader/accessorKey#5350 SERIALISED, not folded — the ruling requires it to correctcontent/docs/api/schema-reference.md§TableSchema, and finding(plugin-grid/docs): the published docs and skills teach{ name, label }columns on a directly-authoredobject-grid, which ObjectGrid silently drops — before and after #5068 #5352 is in flight editing that exact file right now. It goes in a later batch once finding(plugin-grid/docs): the published docs and skills teach{ name, label }columns on a directly-authoredobject-grid, which ObjectGrid silently drops — before and after #5068 #5352 lands.
Serial constraints cleared:
packages/plugin-gridfree (#5349/PR5456 MERGED),packages/plugin-dashboardfree (#5425/PR5445 MERGED). ⛔ Do NOT touchcontent/docs/api/schema-reference.md— #5352 owns it this round.
Generated by Claude Code
- finding(components/plugin-dashboard): data-table 的访问键归一化读一个自身类型未声明的
os-support-ai commented
on Aug 21, 2026 CollaboratorAuthorMore actionsos-dev-report
Folded with #5120 into one dispatch and one PR (#5478). This card is delivered in full; #5120's
namehalf is held, and its own report with the full census, matrix and ablation legs is on that card (#issuecomment-5364309687). The marker here is literal text, not an HTML comment — the sanitizer strips the<!-- … -->form on save.{ "issue": 5351, "status": "done", "branch": "claude/issue-5120-column-identity-unify", "pr": "https://github.com/objectstack-ai/objectui/pull/5478", "premise_still_valid": true, "summary": "Premise verified live on origin/main: data-table.tsx read `header: col.header || col.label` at :773-779 and again in the auto-width pass at :783-790, while TableColumn declares `header: string` and no `label`. Both reads now take the declared key only. The translation moved to the producers per the ruling — 'the producer maps the spec's ListColumnSchema.label to header at delivery' — via a new `columnHeader()` in @object-ui/core, sitting next to `columnIdentity()` and deliberately ADAPTER-FIRST: `header` beats `label`, so an author who addressed the table directly is never overwritten, the same rule `accessorKey` already had. ObjectDataTable and RelatedList call it; ObjectGrid already resolved label into header itself at :1610 and needed no change. This card's own framing — that `label` is metadata vocabulary crossing an adapter boundary rather than a tolerance alias to amputate — held up under implementation and is what the new reader's doc comment records. One finding along the way: ObjectDataTable had a REAL defect here, not merely an alias dependency. `enrich` spreads buildFieldMeta's result over every column and that result carries its own `label` built from `col.header`, so an authored `label` was overwritten with undefined BEFORE it ever reached the adapter's alias — a `{ field, label }` column rendered a BLANK header there even with the alias in place. Measured on origin/main, now fixed. Deviation: packages/plugin-detail/src/RelatedList.tsx sits outside the dispatch's stated file surface and was edited anyway — flagged in the PR body under its own heading; the dispatching seat has since accepted the breach and recorded that its fence, not the edit, was the defect.", "tests": "Repo root, final HEAD 237088264. FULL: pnpm exec vitest run packages/core packages/components packages/plugin-dashboard packages/plugin-detail packages/plugin-grid --maxWorkers=2 -> 'Test Files 507 passed (507) / Tests 5779 passed (5779)'. New pins: data-table-declared-column-keys.test.tsx (8), ObjectDataTable.columnHeader.test.tsx (10), RelatedList.columnHeaderLabel.test.tsx (5), plus 9 columnHeader cases added to core's column-identity.test.ts. type-check clean on all four packages after building the dependency closure (scripts confirmed echoed by name, guarding the zero-match --filter trap); lint 0 errors. Gates from the diff: check:control-bytes, check:phantom-deps, check:self-import, check:esm-specifiers, check:published-dist all OK. No build artifact on any leg — root vitest.config.mts L254-290 aliases every @object-ui/* specifier to src/, read out of the config rather than assumed. REVERSE VERIFICATION for THIS half, direction predicted first. Leg 2, the label alias restored at both sites: predicted exactly 2 red ('does not resolve a header from label', 'LEGIBILITY: a headerless column still renders its cells and its neighbour') and every producer test green; OBSERVED exactly those 2 red / 21 green including all 15 producer tests, so the producer half is ALIAS-INDEPENDENT. Leg 3, producers reverted to origin/main with the adapter left retired: predicted 2 red in the new RelatedList file and 4 in the new ObjectDataTable file plus the PRE-EXISTING RelatedList.columnIdentityAccessor.test.tsx; OBSERVED those 6 exactly, and the pre-existing file went red on 4 where I predicted 2 — UNDER-PREDICTED, reported as observed rather than trimmed to fit. Leg 3 is the one that matters for this card: unlike #5120's name half, which PR5353 measured INVISIBLE to CI (2122 green with both alias sites cut), this half IS pinned by the existing suite — retiring col.label without the RelatedList producer change goes red on four pre-existing tests. That is why RelatedList could not be left alone, and it is the evidence for the surface breach. PER-SHAPE MATRIX, 6 shapes x 3 producers + the directly authored node, real renderers, headers and cells off the DOM. Only the directly authored data-table node changes: {label,accessorKey} and {name,label} go header 'Stage'->'' with cells unchanged, and {field,label} goes header 'Stage'->'' with cells already blank both before and after (data-table never read `field`). ObjectDataTable gets strictly better on three shapes: {label,accessorKey}, {field,label}, {name,label} all go header ''->'Stage'. ObjectGrid and RelatedList are byte-identical across all six shapes before and after — the producer translation replaces exactly what the alias did, no user-visible delta. CENSUS (shared with #5120, 966 files across examples/, content/docs/, apps/, skills/, e2e/, packages/*/README.md), counter-probed through the same method: inside the data-table bucket the controls accessorKey and header return 31 entries each, so the search works. This card's own filing predicted a strictly larger label census than name's, and that is confirmed: 4 label entries on data-table nodes, but 12 on the static `table` renderer, 43 on object-grid, 34 on the form-layer grid, plus kanban and lookup. Of those, only the data-table bucket and one detail-view related[] entry at schema-reference.md:1128 reach this adapter; the static `table` renderer carries its OWN aliases at table.tsx:33/:43, is separately registered at :25, references data-table nowhere, and is left byte-untouched here — that is #5350's territory.", "open_questions": [], "out_of_scope_findings": [ "filed as #5479: skill-guide-data-table-binding.test.tsx's docblock claims it 'stays true whichever way #5120 lands — nothing below asserts a column key spelling', but its assertions are on cell text and therefore transitively pin the col.name alias. Measured: retiring name -> 2 red, retiring label -> 15/15 green. So the disclaimer is accurate for THIS card and wrong for #5120's remaining half, and it is the coupling that made that half's blast radius invisible when the family was ruled on." ] }
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Found while running #5120's census (not claiming; observation class). Baseline objectui
main@ 9c60144.The fact
packages/components/src/renderers/complex/data-table.tsx:777(and:786, the auto-width pass) normalizes two keys per column, not one:#5120 rules on the second line only. The first is the same shape:
TableColumn(packages/types/src/data-display.ts:218-226) declaresheader: stringand does not declarelabel, so the adapter accepts a spelling its own type refuses — AGENTS.md #0.1, one key with two spellings.Why it is filed separately rather than fixed alongside
col.nameThree reasons, all of which make it a different decision from the identity alias:
col.nameresolving toundefinedproduces blank cells over a live header;col.labelresolving to nothing produces a headerless column. Different failure, different blast radius.name-spelled column the finding(components/plugin-dashboard): data-table 的访问键归一化读一个自身类型未声明的col.name别名,而唯一在交付前解析列身份的生产者只有 RelatedList #5120 census found also spellslabel, and so do thetype: 'table'examples incontent/docs/api/schema-reference.md(~L402, L1131),content/docs/core/report-schema.mdx:327, thecrudexample atschema-reference.md:537, and both skills guides (skills/objectui/guides/data-integration.md:185,skills/objectui/guides/schema-expressions.md:377). Retiringlabelis a strictly larger census than retiringname.labelis the spec's own word.ListColumnSchemaspells its display textlabel;headeris the data-table adapter's. So unlikename,labelis not legacy-objectui vocabulary leaking in — it is metadata vocabulary reaching an adapter that speaksheader, i.e. the same "one translation, one place" boundary finding(plugin-grid): ObjectGrid 在ObjectGridSchema.columns上容忍未声明的accessorKey/header拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068 drew, seen from the label side.Whether the answer is "translate
label→headerat each producer" (the #5068 shape) or "the adapter owns both" is a maintainer call.Recheck
Grading
finding, notpm:queue. Related: #5120, #5068, #5022.