Filed unassigned, no pm:* label — triage owns grading and routing. Found while measuring the callback subset of #6152 (the 23 on* keys in UnmirroredDeclared); out of scope there and deliberately not fixed in that round.
The defect
ObjectGrid's column-state persistence has a fully wired inbound half and a dead outbound half.
Inbound (works, and is pinned): ObjectGrid.tsx:632-659 seeds columnState from (schema as any).columnState, falls back to localStorage, and re-syncs on external change. The docblock at :610-630 records the 2026-08-18 maintainer ruling on #5091 that this is user-state, not authoring surface, and __tests__/gridNonAuthorKeys.test.tsx pins it.
Outbound (dead): saveColumnState (ObjectGrid.tsx:661) writes localStorageand notifies the host via onColumnStateChange, which app-shell/src/views/ObjectView.tsx:1867 turns into dataSource.updateViewConfig. It has exactly two call sites, both in the same object literal:
packages/plugin-grid/src/ObjectGrid.tsx:3084 onColumnResize: (columnKey, width) => { saveColumnState({ ...columnState, widths: {...} }) }
packages/plugin-grid/src/ObjectGrid.tsx:3090 onColumnReorder: (newOrder) => { saveColumnState({ ...columnState, order: newOrder }) }
Both are written into const dataTableSchema: any = { … } (:2937) and handed to the renderer via SchemaRenderer schema={dataTableSchema} (:3735). packages/components/src/renderers/complex/data-table.tsx invokes neither.
onColumnResize — zero occurrences in data-table.tsx. Repo-wide, the only non-declaration, non-comment occurrence is the ObjectGrid producer above.onColumnReorder — zero occurrences in data-table.tsx. The renderer calls the near-duplicate key onColumnsReorder (with the s) at data-table.tsx:1346-1347, and that is a different declared key with a different signature: DataTableSchema declares both onColumnReorder?: (newOrder: string[]) => void (data-display.ts:796) and onColumnsReorder?: (columns: TableColumn[]) => void (data-display.ts:718).
Net user-visible effect: a user drags a column wider or reorders columns, and nothing is persisted — not to localStorage, not through onColumnStateChange to dataSource.updateViewConfig. The saved state is read back correctly forever; it is simply never written.
packages/plugin-grid/CHANGELOG.md:713 states the intent that this contradicts: "the table vocabulary reports per-event onColumnResize / onColumnReorder rather than the merged { order, widths } layout this reports."
Why it stayed invisible
Same class as #6004 and #5453, and the same enabling cause: the handoff object is untyped.#6004 is about generateColumns() returning any[]; this is its sibling one level up — const dataTableSchema: any at ObjectGrid.tsx:2937. With any on the producer side, writing a key the consumer never reads is not a type error, and DataTableSchema declaring both spellings means even a typed producer would have accepted the wrong one.
Ruled out before filing: no dynamic handler dispatch in data-table.tsx (no schema[...] indexing, no Object.keys(schema) walk), and data-table.tsx has no column-width/order persistence of its own.
Suggested shape of a fix (not decided here)
Two independent questions, and they should probably be answered together:
- Which spelling survives —
onColumnReorder(newOrder: string[]) or onColumnsReorder(columns: TableColumn[])? Two declared keys for one event is itself the bug that let this land; one of them should be retired under the usual route. - Wire
onColumnResize in data-table.tsx (the resize handler currently persists nothing), or move persistence to the merged onColumnStateChange payload the CHANGELOG describes.
Repro is static — the grep above is the whole reproduction. A behavioural repro would be: drag a column in any object-grid, reload, observe the width is not retained.
Refs: #6152 (where this was found, and where it is out of scope) · #6004 / #5453 (same untyped-handoff class) · #5091 (the ruling that columnState is user-state, not authoring surface).
Filed unassigned, no
pm:*label — triage owns grading and routing. Found while measuring the callback subset of #6152 (the 23on*keys inUnmirroredDeclared); out of scope there and deliberately not fixed in that round.The defect
ObjectGrid's column-state persistence has a fully wired inbound half and a dead outbound half.Inbound (works, and is pinned):
ObjectGrid.tsx:632-659seedscolumnStatefrom(schema as any).columnState, falls back tolocalStorage, and re-syncs on external change. The docblock at:610-630records the 2026-08-18 maintainer ruling on #5091 that this is user-state, not authoring surface, and__tests__/gridNonAuthorKeys.test.tsxpins it.Outbound (dead):
saveColumnState(ObjectGrid.tsx:661) writeslocalStorageand notifies the host viaonColumnStateChange, whichapp-shell/src/views/ObjectView.tsx:1867turns intodataSource.updateViewConfig. It has exactly two call sites, both in the same object literal:Both are written into
const dataTableSchema: any = { … }(:2937) and handed to the renderer viaSchemaRenderer schema={dataTableSchema}(:3735).packages/components/src/renderers/complex/data-table.tsxinvokes neither.onColumnResize— zero occurrences indata-table.tsx. Repo-wide, the only non-declaration, non-comment occurrence is the ObjectGrid producer above.onColumnReorder— zero occurrences indata-table.tsx. The renderer calls the near-duplicate keyonColumnsReorder(with thes) atdata-table.tsx:1346-1347, and that is a different declared key with a different signature:DataTableSchemadeclares bothonColumnReorder?: (newOrder: string[]) => void(data-display.ts:796) andonColumnsReorder?: (columns: TableColumn[]) => void(data-display.ts:718).Net user-visible effect: a user drags a column wider or reorders columns, and nothing is persisted — not to
localStorage, not throughonColumnStateChangetodataSource.updateViewConfig. The saved state is read back correctly forever; it is simply never written.packages/plugin-grid/CHANGELOG.md:713states the intent that this contradicts: "the table vocabulary reports per-eventonColumnResize/onColumnReorderrather than the merged{ order, widths }layout this reports."Why it stayed invisible
Same class as #6004 and #5453, and the same enabling cause: the handoff object is untyped.#6004 is about
generateColumns()returningany[]; this is its sibling one level up —const dataTableSchema: anyatObjectGrid.tsx:2937. Withanyon the producer side, writing a key the consumer never reads is not a type error, andDataTableSchemadeclaring both spellings means even a typed producer would have accepted the wrong one.Ruled out before filing: no dynamic handler dispatch in
data-table.tsx(noschema[...]indexing, noObject.keys(schema)walk), anddata-table.tsxhas no column-width/order persistence of its own.Suggested shape of a fix (not decided here)
Two independent questions, and they should probably be answered together:
onColumnReorder(newOrder: string[])oronColumnsReorder(columns: TableColumn[])? Two declared keys for one event is itself the bug that let this land; one of them should be retired under the usual route.onColumnResizeindata-table.tsx(the resize handler currently persists nothing), or move persistence to the mergedonColumnStateChangepayload the CHANGELOG describes.Repro is static — the grep above is the whole reproduction. A behavioural repro would be: drag a column in any
object-grid, reload, observe the width is not retained.Refs: #6152 (where this was found, and where it is out of scope) · #6004 / #5453 (same untyped-handoff class) · #5091 (the ruling that
columnStateis user-state, not authoring surface).