Skip to content

ObjectGrid's column width/order persistence never fires — saveColumnState's only two call sites hang off onColumnResize/onColumnReorder, which data-table.tsx never invokes #6175

Description

@yinlianghui-tw

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.

  • onColumnResizezero occurrences in data-table.tsx. Repo-wide, the only non-declaration, non-comment occurrence is the ObjectGrid producer above.
  • onColumnReorderzero 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:

  1. 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.
  2. 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).

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions