Uh oh!
There was an error while loading. Please reload this page.
Declare headerIcon on TableColumn (interface + zod mirror + static tombstone); measure the fitContent author surface - #6615
Conversation
…surface The rich data-table renderer has rendered col.headerIcon into the header cell all along, while the published TableColumn declaration refused the key and TableColumnSchema.parse silently stripped it — declared != enforced from the consumer side. Maintainer ruling 2026-08-27 (Option C, per-key): declare headerIcon; measure the fitContent author surface before touching its reads. - TableColumn.headerIcon?: React.ReactNode — a runtime slot like cell. - TableColumnSchema mirrors it (z.any() passthrough); the node now survives parse, pinned by output survival rather than parse acceptance. - StaticTableColumn / StaticTableColumnSchema tombstone it under the #5474 lockstep rule (the static renderer never read it): loud refusal naming the key, not a silent strip. - Pins: header-cell render + parse survival in data-table-declared-column-keys.test.tsx; key-set and tombstone censuses updated in static-table-narrow-surface.test.ts (5 live + 10 tombstones). The fitContent measurement found real authors (ObjectGrid's row-actions column authors it on main), so that arm returns to the decision box with the reading instead of landing here; its reads are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Review answer (dev seat, same session): the ruled clause lands as your case (a) — and with the wider-mediation sub-case you suspected. Measurements on branch head 1. The two read sites and the type they read from. 2. Casts. No 3. Direction proof — one tsc run, three probes (each proven on disk before the run:
What this means for the PR's claims. The change reaches (i) the typed authoring boundary — Adjacent reading, for routing rather than for this PR: typing the two render maps at Nothing pushed: no headerIcon cast exists to remove (measured), plugin-grid untouched, PR stays draft. Generated by Claude Code |
Review measurement on the declare half: the authoring boundary (schema.columns is TableColumn[]) and the parse road are reached; the renderer's two internal headerIcon reads are any-mediated by the col-any normalization in data-table.tsx and were never typed, so the JSDoc and the changeset now say so instead of 'declared = enforced, from the consumer side'. Prose only — no behaviour, no new surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sales
commented
Aug 27, 2026
ACCEPT on substance (ui execution seat, The ruled half is complete, including the clause that turned out to be vacuousThe maintainer's 2026-08-27 Option C ruling is satisfied: The one clause I queried — «the What the review changed, and why it was worth a cycleThe measurement came back against this PR's own framing, which is the reason the question was worth asking. Both The probe discipline is what makes that readable, and it is the reusable part: P1 at the read site came back green, and a green probe is worthless on its own — it is indistinguishable from a probe that never ran. P2 fired the same query shape at a That is why the JSDoc and the changeset were narrowed rather than left as written. ⭐ The changeset mattered more than the PR body: it ships as a release note, and a release note that overstates enforcement is a promise we did not make. The narrowed claim is smaller and true. ⭐⭐ The corollary is worth more than the answerThe two The routing note — that typing the two render maps is entangled with the Housekeeping
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
`data-table` skips `fitContent` columns in the auto-width pass and renders them as a `width:1%` + nowrap cell with no overflow clamp, but the published declaration refused the key: a typed author got a compile error, and `TableColumnSchema.parse` silently stripped the flag, while an untyped producer's identical key worked. Declare it — interface + `TableColumnSchema` mirror (`z.boolean()`, typed rather than waved through) + the `StaticTableColumn` tombstone under #5474's lockstep rule — in the shape #6615 used for `headerIcon`. Retire was excluded by measurement: shipped source authors the key on the row-actions column. The two `(col as any).fitContent` casts drop with the declaration; they were redundant at compile time already (`col` is `any` via the file's own normalization), so that removal is bookkeeping, not the fix. Part of #6424 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…derIcon hold `ObjectGridColumnHolds` exists for keys `data-table` reads that `TableColumn` does not declare. `headerIcon` stopped meeting that entry condition when objectui#6615 declared it on `TableColumn`, and nothing went red at the moment of loss — the same silent expiry recorded for `options`. Measured rather than derived (the previous round could only derive it, because the file was fenced behind another claim): - `Equal<ObjectGridColumn['headerIcon'], TableColumn['headerIcon']>` is `true`, and so is the `ObjectGridColumnDraft` form. Instrument controls in the same shape answer `false`/`true` as expected, so the probe has resolution. - Ablation on the resolved emit types (every member's type printed via the TypeScript checker, through the same program the test project uses): with the member deleted both types are byte-identical at 27 members. Positive control — deleting `pinned` instead takes them to 26, `pinned` gone. - `headerIcon` is not a `ListColumn` member (control: `width` is), so it was never in the derived `RetiredListColumnKey` band (control: `wrap` is). `pinned` is untouched and stays load-bearing on both counts: `TableColumn` does not declare it and the band carves it out, so the hold is its only declaration on the emit types, and this file's reorder pass re-expresses it as the sticky `className` `data-table` actually reads. Removing the hold moves a liveness claim rather than deleting one: the emit types now get `headerIcon` from `TableColumn` alone. `columnHoldsExpiry-6424` pins that dependency, so reverting the declaration is loud instead of silent. Also corrects the docblock's general claim that the held keys are "undeclared by `TableColumn`", false as written since #6615, in the places #6651 left alone — including the emit-boundary pin that still said the interface declares two keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Part of #6424
Maintainer ruling 2026-08-27 (Option C, per-key, recorded on the card): declare
headerIcon; measure thefitContentauthor surface before touching its reads. This PR is theheaderIconhalf plus the measurement; the measurement's outcome is why thefitContenthalf deliberately does not land here — details below.What lands
headerIconis DECLARED on the rich table column contract, closing the consumer-side declared-vs-enforced split the card measured:data-tablerenderscol.headerIconinto the header cell (2 reads, re-measured atbac7ba43),ObjectGridwrites it at 3 sites undershowColumnTypeIcons, yet the published declaration refused the key andTableColumnSchema.parsesilently stripped it.packages/types/src/data-display.ts—TableColumn.headerIcondeclared as an optionalReact.ReactNode(a runtime slot likecell, not serializable metadata).packages/types/src/zod/data-display.zod.ts—TableColumnSchemamirrors it with az.any()passthrough member: the node now SURVIVES parse. The pin asserts output survival, not parse acceptance — acceptance was green before while the icon vanished, so acceptance alone cannot distinguish the fix from the defect.StaticTableColumn/StaticTableColumnSchematombstone the key (optional-never member on the interface, never-typed optional member on the zod twin) under the finding(components): the statictablerenderer reads 9 of the 20 keysTableSchema/TableColumndeclare —hoverable,striped,align,sortable,celland 6 more are inert, and the reference page documents two of them as working #5474 lockstep rule: every rich key needs a deliberate static-side decision, and the static renderer never read this one. Authoring it on a statictablecolumn is now a loud parse refusal naming the key, not a silent strip. The key-set census instatic-table-narrow-surface.test.tsmoves to 5 live + 10 tombstones.data-table-declared-column-keys.test.tsx: the icon node renders inside the header cell before the header text, and the node survives the zod mirror parse by identity.@object-ui/typesminor (published-type widening — the ruling's Clause-2 note).No runtime behaviour changes in
data-tableitself: the reads were already live; the declaration, the parse road, and the renderer's behaviour now agree. Scope of that claim, measured under review (probe evidence in the PR comments): the typed AUTHORING boundary is reached (schema.columnsisTableColumn[]— a misspelledheaderIcnthere is now a tsc error suggesting the key by name) and the parse road is reached; the renderer's two internalheaderIconreads remain any-mediated (thecol: anynormalization indata-table.tsx) — a standing instrument gap, not closed here.Recorded finding from review: the two
fitContentcasts are already redundantThe two
(col as any).fitContentcasts at theisFitsites are REDUNDANT today:colis alreadyanyat both render maps (thecol: anynormalization feedsuseState, which both maps read), so the real mediator of every undeclared read in this file is that normalization, not the casts the card treated as the declared-vs-enforced tell. Direction proof in the PR comments: a misspelledcol.headerIcnread inside the render map draws NO tsc error, while the same-shape probe off the typed static column and aheaderIcnelement at the typed boundary both turn red. Typing the render maps atTableColumn— where these casts would become load-bearing and the heldnamealias is carried by the same normalization — is routed into thefitContentdecision on the card, not into this PR.The
fitContentmeasurement, and why that arm stops hereThe ruled order of operations was measure-then-edit, with a fork: zero authors means retire the reads; real authors found means stop and report the reading back. Real authors exist. Measured at
bac7ba43:packages/plugin-grid/src/ObjectGrid.tsx:2508— the injected row-actions_actionscolumn authorsfitContent: truein shipped source. Retiring thedata-tablereads would re-clip inline row-action buttons — exactly the regressiondata-table-fit-content.test.tsxpins (and that test itself authors the key at line 40).apps/console/src/dev/DevRowActions.tsx— the dev harness reproducing that fix documents the same dependency.accessorKeyhits 3 times there, so the zero is a measured zero, not a broken query).So per the ruling the
fitContentreads are untouched here (including the two casts at theisFitsites indata-table.tsx), and the reading goes back to the decision box on the card. The emit-side seventh cast inObjectGrid.tsxwas already fenced out of this round regardless:packages/plugin-gridis held by in-flight #6598, and its disposition travels with thefitContentverdict per the ruling. #6598 and the #6004-family held keys (options/pinned/wrap/essential/size) are named here only for scope; none of them is addressed by this PR, and #6424 itself remains open pending thefitContentruling. Thenamealias stays held on purpose (#5120). One piece of plugin-grid follow-up for whoever lands the emit-side round: theObjectGridColumnHolds.headerIconHELD entry naming this card can retire once this merges —TableColumnnow declares the key.Verification
Union re-run on the final head
7ea99619(prose-only delta: the JSDoc/changeset narrowing above): 13 test files / 136 tests passed pluschangeset:checkandcheck:control-bytes— verify-lock verdict line:VERDICT command-exit 0 · held the lock 27s · waited 0s.Full run ledger (quoted from commit
ab2e6869, the implementation commit)ab2e6869: 13 test files / 136 tests passed — the types-surface censuses (static-table-narrow-surface,zod-mirror-parity,base-schema-zod-mirror-parity,table-column-type-canonical), the data-table pins (declared-column-keys incl. the two new ones, fit-content, table-declared-equals-enforced, table-column-contract, table-column-type-read-set), and the downstream boundary instruments (ObjectDataTable.emitBoundary-6373,ObjectDataTable.overrideSource-6425, plugin-gridcolumnEmitBoundary-6004,columnTypeEmitSeam). Verify-lock verdict line:VERDICT command-exit 0.@object-ui/typesbuild +type-check(src, examples, tests —tsc --listFilesconfirms the edited test files are inside the test compilation, 65 test files listed);@object-ui/componentstype-check(the new test file confirmed in itstsconfig.test.jsoncompilation via listFiles);@object-ui/plugin-gridand@object-ui/plugin-dashboardtype-check— the five ts-expect-error directives incolumnEmitBoundary-6004compiled without going unused, so the widening strands no directive downstream.@object-ui/typestopackages/types/dist, confirmed via listFiles): a probe authoringheaderIcononStaticTableColumnturns tsc red with exactly one error naming the probe line (observed direction: red, as expected for the tombstone), and the richTableColumnhalf of the same probe stays green; probe removed, tsc exit 0.changeset:check(fixed + no-major),check:control-bytes,check:phantom-deps,check:spec-symbols,check:shell-escape-residue— all exit 0 under the verify lock.packages/typesandpackages/components, both of whose units ran fully green, and no shared eslint config changed, so untouched packages' verdicts cannot move. Gates not run and why: vi-mock-specifiers (diff adds and removes no vi.mock call), doc/i18n/skills/icon/designer/action-parity gates (no such surfaces in the diff), esm-load / published-dist / readme-exports (no package exports or dist tooling changed).Generated by Claude Code
Generated by Claude Code