Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-grid,react): one column spelling — retire the undeclared accessorKey/header round trip - #5345
Conversation
…ccessorKey/header round trip (#5068) `ObjectGridSchema.columns` is declared `string[] | ListColumn[]`, and `ListColumnSchema` is a strict object that refuses `accessorKey` / `header` by name. `ObjectGrid` accepted them anyway through a branch that sniffed `columns[0]`, and `bridgeListView` produced them by down-translating columns that arrived canonical — a round trip through a spelling the contract rejects. Both halves land together, the shape objectui#3951 used (PR4909 migrated its consumer in `packages/fields` and its producer in `packages/plugin-form` in one PR): deleting the consumer half alone silently blanks every bridged grid, and the whole plugin-grid suite stays green while it happens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-support-ai
commented
Aug 19, 2026
ACCEPT — PM review, round 17. Also closes #5340.
The leg that justifies the scope callLeg 3 — producer-only revert, i.e. the state that would have shipped if I had kept the one-package surface: predicted 7 red, observed exactly 7, and That green is the whole point. The pre-existing suite renders bridge output through Predicted == observed on every leg, both directions, no leg reversing. What the measurement added beyond the first dispatchThe first agent measured The second defect, which fell out rather than being added
The framing in the new docblock is the right one to keep: metadata vocabulary in, adapter vocabulary out; one translation, one place. The pins were strengthened, not swapped: Census extended rather than inheritedThe first sweep's corpora did not include package READMEs. Swept: Two follow-ups, both correctly left to me
Merging via the queue. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#5068
Fixes#5340
One column spelling.
ObjectGridSchema.columnsis declaredstring[] | ListColumn[], andListColumnSchemain@objectstack/spec/uiis a strict object —fieldrequired,accessorKey/headerrefused by name withunrecognized_keysand a prescriptive message. Two places disagreed with that contract, and they were the two halves of one round trip:ObjectGridaccepted the refused spelling through a branch that sniffedcolumns[0]for anaccessorKeyand synthesized aListColumnfrom it (ObjectGrid.tsx:1451-1489);bridgeListViewproduced that spelling, by down-translating columns that arrived already canonical — its own parameter is the spec'sListColumn.Both retire here, in one PR. That is the disposition #5068 inherits from #3951 applied the way #3951 itself applied it: PR4909 migrated its consumer in
packages/fieldsand its producerderiveMasterDetailinpackages/plugin-formtogether. Landing the consumer alone is not a smaller version of this change — it is a silent regression, measured below.accessorKeyis not retired as a concept. It stays the data-table adapter's column key, which@object-ui/coredeliberately holds outside the metadata identity fold (TABLE_ADAPTER_COLUMN_KEY) and whichObjectGridstill writes when it hands columns to the adapter (:1636, untouched). Metadata vocabulary in, adapter vocabulary out, one translation at one boundary.Why both halves, in one PR
Measured on this branch, producer reverted and consumer retired — the state a one-package PR would have shipped:
The bridged grid loses every column and every cell, with no error and no console line — while
specBridgeExportFormats.test.tsx, which renders bridge output throughObjectGrid, stays green: it asserts the export menu and never the columns. On the first dispatch of this card the same deletion left all 715 plugin-grid tests passing. Neither CI nor review would have caught it, which is why this PR adds the pin that would have.Changes
packages/plugin-grid/src/ObjectGrid.tsx— the tolerance branch is deleted, and thecolumns[0]sniff goes with it. Column identity is a per-column property, and one filter now judges it (col?.field && typeof col.field === 'string'). Previously the first entry decided the fate of the whole array: a declared column standing behind an undeclared one was dropped with it, and the reverse order threwTypeError: Cannot read properties of undefined (reading 'toLowerCase')mid-render (observed —inferColumnTypereadscol.field.toLowerCase()on a synthesized column whosefieldcame from a missingaccessorKey). Removing the sniff is named here rather than left implicit: it is the same defect one level down, it is mechanical, and the filter it defers to is existing behaviour, not new judgement.The diff in this file is mostly re-indentation from removing one nesting level —
git diff -wis 65 lines against 403.packages/react/src/spec-bridge/bridges/list-view.ts—mapColumnemits{ field, label? }; the spec's bare-string shorthandcolumns: ['name']maps to{ field: 'name' }.It also stops inventing a label.
header: col.label ?? col.fieldturned "the author declared no label" into "the author declared the machine name", and that synthesized value pre-emptedObjectGrid's own header chain — the column's label, then the object field's label, then the prettified machine name — whose middle step exists so a localized field label wins on a non-English app. A bridged view therefore rendered raw machine names where a directly authoredobject-gridrendered the field's real label. Pinned both ways:SpecBridge.test.tsasserts nothing is invented,specBridgeColumnSpelling.test.tsxasserts the rendered header is now the object field's label.Speaking the declared spelling also routes bridged views through the renderer's full ListColumn path instead of its type-inference-only one: object-schema field enrichment,
hiddenfiltering, primary-field auto-linking and per-columnlink/actionnow apply to a bridgedListViewexactly as to an authored grid.Tests —
packages/plugin-grid/src/__tests__/columnDeclaredSpellingOnly.test.tsx(consumer: declared renders, undeclared does not resolve, both mixed orders, the legibility pin,string[]unaffected) andpackages/plugin-grid/src/__tests__/specBridgeColumnSpelling.test.tsx(the seam that had no pin).SpecBridge.test.ts's two pinned identity assertions move to the canonical spelling and gaintoBeUndefined()on the retired keys, so a pin that would pass against both spellings is not what replaced them.Two changesets, both
minor: the acceptance set narrows in@object-ui/plugin-grid, andbridgeListView's exported output shape changes in@object-ui/react.Legibility — what a mis-spelled column does now, and what this PR deliberately does NOT do
Pinned as behaviour, not left as folklore: an undeclared column does not throw, does not render an empty header cell, and produces no console line. It is dropped; a grid whose columns are all mis-spelled renders as the row-number column alone. The card asked whether that silence deserves a loud rejection or a dev-time warning. On the ground the ruling named — the shape circulating in docs history — the answer is no: the spec already rejects these keys by name with a migration-shaped message, and the docs page for
object-gridmentionsaccessorKeyonly to forbid it (content/docs/plugins/plugin-grid.mdx:90,:107). The broader question (should the renderer say something when a non-emptycolumnsresolves to nothing?) is #5068's Q2, deferred by the PM to its own card. No warning channel is implemented here.Census — re-verified, including a corpus the first sweep did not name
The first dispatch measured a clean zero for authored
accessorKey-shapedobject-gridcolumns acrossexamples/,content/docs/, apps, skills and e2e (924 files, three methods, one of which it discarded for failing its own control). Re-verified, and extended to package READMEs, which that sweep did not enumerate. Every hit outside the two files this PR touches is thetable/data-tablecomponent, which legitimately owns the key:Structural sweep for producers, whole tracked tree: files mentioning both
object-gridandaccessorKeyare the two this PR edits, their tests, andapp-shell/src/views/metadata-admin/i18n.ts(translation keys for the designer's column inspector).RelatedListnormalizes toaccessorKeybut renderstype: 'data-table'— the adapter, not this path — so it is unaffected; its fouraccessorKeyfixtures stay green.Filed from that sweep, unassigned, out of this PR's surface: #5344 — the designer's
ViewColumnInspectorwrites back whichever spelling it found, so editing a storedaccessorKeycolumn re-saves a spelling that no longer renders.#5340
Resolved by this PR, hence the closing keyword above. #5340 is exactly
bridgeListView's down-translation, and it is retired in full:mapColumnno longer emitsaccessorKeyorheaderon any path, including the bare-string shorthand, andSpecBridge.test.tsnow asserts their absence rather than merely asserting the canonical keys are present.Verification
All runs from the repo root (a package-scoped run would use a different config than CI does), at
798482b3, under the shared verify lock. No build artifact sits between any edit and anything under test: the rootvitest.config.mtsaliases@object-ui/reacttopackages/react/src(:252) and@object-ui/plugin-gridtopackages/plugin-grid/src(:258) — verified in the file, and confirmed empirically by the source-only edits flipping the runs below.The #3104 ratchet keeps
ObjectGrid.tsxin its CONVERGED list, which requirescolumnIdentity(to still match in the file — it does, at the three call sites this PR does not touch.pnpm check:doc-snippetsrefuses to run without a full workspace build ([unbuilt-package]forplugin-view,plugin-markdown,plugin-timeline) and reports so loudly rather than answering wrongly; it is left to CI, where the build precedes it. This PR changes no exported type.Reverse verification — three legs, predictions recorded before each run
origin/main)TypeErrorfor the undeclared-first mixed array, andexpected [ '#', 'Name', 'Amount' ] to deeply equal [ '#' ]for the tolerance itselfspecBridgeExportFormatsgreenspecBridgeExportFormatsgreen across all 5 of its testsPredicted equalled observed on every leg, in both directions. The third leg is the one that would have shipped the regression.
Generated by Claude Code