Skip to content

finding(types): the rich TableColumnSchema zod mirror omits editable — a key data-table reads at three sites is silently stripped by validation, so a column locked with editable: false re-opens after parse #5821

Description

@claude

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

Metadata

Metadata

Assignees

Labels

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions