Found while implementing #5474 (splitting the static table's narrow column type off the rich TableColumn). Filed unassigned and deliberately NOT fixed there: #5474's ruling scope is the STATIC surface — "TableColumn remains the rich shared shape that data-table honours" — so a rich-surface mirror fix would have been scope creep on a contract-review card.
Measured
The rich TableColumn interface (packages/types/src/data-display.ts) declares 14 keys, including:
/**
* Whether column is editable (for inline editing)
* @default true
*/
editable?: boolean;
Its zod mirror TableColumnSchema (packages/types/src/zod/data-display.zod.ts) declares 13 — every key except editable:
grep -c "z\." packages/types/src/zod/data-display.zod.ts # inspect the object: header, accessorKey, className, cellClassName, width, minWidth, align, fixed, type, sortable, filterable, resizable, cell — no editable
And the key is LIVE on the renderer — packages/components/src/renderers/complex/data-table.tsx reads it at three sites to gate inline editing per column:
1374: if (column?.editable === false) return;
1618: if (column?.editable === false) return;
2089: const isEditable = editable && col.editable !== false;
Why it matters
z.object is non-strict, so the mirror doesn't refuse editable — it silently strips it. Declared on the interface, honoured by the renderer, dropped by the validator: any pipeline that parses metadata through the mirror (@object-ui/types/zod is a published surface; packages/cli's validate command routes through AnyComponentSchema) emits a column with editable gone — and since the renderer treats absence as true (@default true, col.editable !== false), a column the author locked with editable: false comes out of the parse editable again. Same class as #4605 (BaseSchema mirror narrower than its declaration: "the type says yes, the validator says no"), with the strip variant instead of the refusal variant.
Note
#5474's static-table-narrow-surface.test.ts pins the rich surface's acceptance of the interactive keys but deliberately excludes editable from that loop, with a comment pointing at this drift — closing this card should fold editable into that loop (or pin whatever direction triage rules).
The narrow StaticTableColumnSchema introduced by #5474 is unaffected: it tombstones editable explicitly (z.never().optional()).
Reproduce
node -e "const {TableColumnSchema}=require('./packages/types/dist/zod/index.zod.js'); const r=TableColumnSchema.parse({header:'A',accessorKey:'a',editable:false}); console.log('editable' in r)" # false — stripped
Related: #5474 (the split that measured this), #4605 (the mirror-drift class), #5350 (the same file family's alias retirement).
Generated by Claude Code
Generated by Claude Code
Found while implementing #5474 (splitting the static table's narrow column type off the rich
TableColumn). Filed unassigned and deliberately NOT fixed there: #5474's ruling scope is the STATIC surface — "TableColumnremains the rich shared shape thatdata-tablehonours" — so a rich-surface mirror fix would have been scope creep on a contract-review card.Measured
The rich
TableColumninterface (packages/types/src/data-display.ts) declares 14 keys, including:Its zod mirror
TableColumnSchema(packages/types/src/zod/data-display.zod.ts) declares 13 — every key excepteditable:And the key is LIVE on the renderer —
packages/components/src/renderers/complex/data-table.tsxreads it at three sites to gate inline editing per column:Why it matters
z.objectis non-strict, so the mirror doesn't refuseeditable— it silently strips it. Declared on the interface, honoured by the renderer, dropped by the validator: any pipeline that parses metadata through the mirror (@object-ui/types/zodis a published surface;packages/cli'svalidatecommand routes throughAnyComponentSchema) emits a column witheditablegone — and since the renderer treats absence astrue(@default true,col.editable !== false), a column the author locked witheditable: falsecomes out of the parse editable again. Same class as #4605 (BaseSchemamirror narrower than its declaration: "the type says yes, the validator says no"), with the strip variant instead of the refusal variant.Note
#5474's
static-table-narrow-surface.test.tspins the rich surface's acceptance of the interactive keys but deliberately excludeseditablefrom that loop, with a comment pointing at this drift — closing this card should foldeditableinto that loop (or pin whatever direction triage rules).The narrow
StaticTableColumnSchemaintroduced by #5474 is unaffected: it tombstoneseditableexplicitly (z.never().optional()).Reproduce
Related: #5474 (the split that measured this), #4605 (the mirror-drift class), #5350 (the same file family's alias retirement).
Generated by Claude Code
Generated by Claude Code