Skip to content

Declare headerIcon on TableColumn (interface + zod mirror + static tombstone); measure the fitContent author surface - #6615

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-6424-tablecolumn-headericon-fitcontent
Aug 27, 2026
Merged

Declare headerIcon on TableColumn (interface + zod mirror + static tombstone); measure the fitContent author surface#6615
os-sales merged 2 commits into
mainfrom
claude/issue-6424-tablecolumn-headericon-fitcontent

Conversation

@claude

@claudeclaudeBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Part of #6424

Maintainer ruling 2026-08-27 (Option C, per-key, recorded on the card): declare headerIcon; measure the fitContent author surface before touching its reads. This PR is the headerIcon half plus the measurement; the measurement's outcome is why the fitContent half deliberately does not land here — details below.

What lands

headerIcon is DECLARED on the rich table column contract, closing the consumer-side declared-vs-enforced split the card measured: data-table renders col.headerIcon into the header cell (2 reads, re-measured at bac7ba43), ObjectGrid writes it at 3 sites under showColumnTypeIcons, yet the published declaration refused the key and TableColumnSchema.parse silently stripped it.

  • packages/types/src/data-display.tsTableColumn.headerIcon declared as an optional React.ReactNode (a runtime slot like cell, not serializable metadata).
  • packages/types/src/zod/data-display.zod.tsTableColumnSchema mirrors it with a z.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 / StaticTableColumnSchema tombstone the key (optional-never member on the interface, never-typed optional member on the zod twin) under the finding(components): the static table renderer reads 9 of the 20 keys TableSchema/TableColumn declare — hoverable, striped, align, sortable, cell and 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 static table column is now a loud parse refusal naming the key, not a silent strip. The key-set census in static-table-narrow-surface.test.ts moves to 5 live + 10 tombstones.
  • New pins in 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.
  • Changeset: @object-ui/types minor (published-type widening — the ruling's Clause-2 note).

No runtime behaviour changes in data-table itself: 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.columns is TableColumn[] — a misspelled headerIcn there is now a tsc error suggesting the key by name) and the parse road is reached; the renderer's two internal headerIcon reads remain any-mediated (the col: any normalization in data-table.tsx) — a standing instrument gap, not closed here.

Recorded finding from review: the two fitContent casts are already redundant

The two (col as any).fitContent casts at the isFit sites are REDUNDANT today: col is already any at both render maps (the col: any normalization feeds useState, 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 misspelled col.headerIcn read inside the render map draws NO tsc error, while the same-shape probe off the typed static column and a headerIcn element at the typed boundary both turn red. Typing the render maps at TableColumn — where these casts would become load-bearing and the held name alias is carried by the same normalization — is routed into the fitContent decision on the card, not into this PR.

The fitContent measurement, and why that arm stops here

The 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 _actions column authors fitContent: true in shipped source. Retiring the data-table reads would re-clip inline row-action buttons — exactly the regression data-table-fit-content.test.tsx pins (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.
  • Zero authors in objectui docs/examples/skills, and zero in the objectstack repo (positive control in the same query shape: accessorKey hits 3 times there, so the zero is a measured zero, not a broken query).

So per the ruling the fitContent reads are untouched here (including the two casts at the isFit sites in data-table.tsx), and the reading goes back to the decision box on the card. The emit-side seventh cast in ObjectGrid.tsx was already fenced out of this round regardless: packages/plugin-grid is held by in-flight #6598, and its disposition travels with the fitContent verdict 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 the fitContent ruling. The name alias stays held on purpose (#5120). One piece of plugin-grid follow-up for whoever lands the emit-side round: the ObjectGridColumnHolds.headerIcon HELD entry naming this card can retire once this merges — TableColumn now 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 plus changeset:check and check: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)

  • Union re-run on 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-grid columnEmitBoundary-6004, columnTypeEmitSeam). Verify-lock verdict line: VERDICT command-exit 0.
  • Package builds and type-checks: @object-ui/types build + type-check (src, examples, tests — tsc --listFiles confirms the edited test files are inside the test compilation, 65 test files listed); @object-ui/componentstype-check (the new test file confirmed in its tsconfig.test.json compilation via listFiles); @object-ui/plugin-grid and @object-ui/plugin-dashboardtype-check — the five ts-expect-error directives in columnEmitBoundary-6004 compiled without going unused, so the widening strands no directive downstream.
  • Reverse verification through the REBUILT dist (components resolves @object-ui/types to packages/types/dist, confirmed via listFiles): a probe authoring headerIcon on StaticTableColumn turns tsc red with exactly one error naming the probe line (observed direction: red, as expected for the tombstone), and the rich TableColumn half of the same probe stays green; probe removed, tsc exit 0.
  • Gates derived from the diff: package lint for types + components (0 errors; per-file lint on the four edited files shows 0 new findings — the two warnings present are pre-existing casts), 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.
  • Declared narrowings (CI owns the full farm): repo-wide lint/type-check/vitest were run as the affected-package subset above, not the whole farm. Lint narrowing evidence: turbo's lint units are per-package eslint runs; the diff touches only packages/types and packages/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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3236.3 KB3266.6 KB
Main entry chunk (gzip)157.1 KB350 KB
Entry fileindex-NNE-s9Y6.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)11.89KB4.50KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.90KB114.81KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.85KB12.89KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.48KB34.49KB
plugin-designer (index.js)212.80KB43.15KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)131.98KB32.23KB
plugin-gantt (index.js)165.16KB40.33KB
plugin-grid (index.js)201.66KB54.58KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)112.86KB27.54KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.72KB7.71KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)85.87KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)65.97KB21.98KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claudeBot commented Aug 27, 2026

Copy link
Copy Markdown
ContributorAuthor

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 ab2e6869; probes restored afterwards (working tree byte-identical to HEAD: git diff HEAD empty, residue grep 0, tsc --noEmit exit 0 with empty output).

1. The two read sites and the type they read from.data-table.tsx:1932-1933 — line 1932 is {col.headerIcon && ( and 1933 renders col.headerIcon inside the muted span wrapper. col is the callback parameter of columns.map(...) at 1858 (second site: the body-cell map at 2129). The chain: the component boundary IS typed — the renderer signature is schema: DataTableSchema, so rawColumns (destructured at 695) is DataTableSchema['columns'], i.e. TableColumn[] — but the normalization at 814 goes through rawColumns.map((col: any) => ...), so initialColumns is any[], and useState(initialColumns) at 861 carries any[] into both render maps. Declared type of the value being read from, at both headerIcon sites: any.

2. Casts. No as any / as unknown as / widened alias mediates headerIcon — the card's own census put both as-any sites on fitContent (1861, 2130), so the ruled clause "the as any reads go" is vacuous for the headerIcon key: there was never a cast to drop. Corollary from the same measurement: those two (col as any).fitContent casts are REDUNDANT today — col is already any at both — so the real mediator of every undeclared read in this file is the (col: any) at 814, not the casts the card treated as the tell.

3. Direction proof — one tsc run, three probes (each proven on disk before the run: grep -c headerIcn gave 3 in data-table.tsx, 2 in table.tsx; both 0 after restore):

  • P1, read-site position — misspelled neighbour col.headerIcn inserted inside the 1858 callback: NO error in a run where both controls fired. Observed direction: GREEN — the reads are NOT typed at TableColumn.
  • P2, positive control, same query shapecol.headerIcn off a StaticTableColumn-typed parameter in table.tsx: RED — error TS2551: Property 'headerIcn' does not exist on type 'StaticTableColumn'. Did you mean 'headerIcon'? So the shape catches misspelled reads at typed sites; P1's green is a reading, not a dead probe.
  • P3, the boundary the declare DOES reach — a headerIcn element in a typeof rawColumns literal: RED — error TS2561: Object literal may only specify known properties, but 'headerIcn' does not exist in type 'TableColumn'. Did you mean to write 'headerIcon'? — while a sibling element in the same literal authoring headerIcon: null drew no error.

What this means for the PR's claims. The change reaches (i) the typed authoring boundary — schema.columns is TableColumn[], so a typed author writing headerIcon now compiles, with tsc suggesting the key by name (P3) — and (ii) the parse road — the mirror passes the node through, pinned by identity. It does NOT reach the renderer's two internal reads, which were and remain any-mediated: a future misspelled read inside the renderer still compiles silently. So "declared = enforced from the consumer side" is accurate about the published contract face (declaration and schema now admit what the renderer honours — the card's two harm sentences, both measured fixed) and OVERSTATES if read as "the consumer's internal reads are type-checked". Per the (a) instruction the softening is yours; suggested one-liner for body + changeset: "The declaration, the parse road, and the renderer's behaviour now agree; the renderer's internal column reads remain any-mediated (the col-any normalization at 814) — the standing instrument gap the card itself noted for the rich renderer."

Adjacent reading, for routing rather than for this PR: typing the two render maps at TableColumn looks feasible but is entangled with the fitContent verdict now in the decision box — under a typed callback the two redundant fitContent casts become load-bearing, and the 814 normalization also carries the deliberately-held name alias — so it belongs with that ruling, not here.

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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3236.4 KB3266.6 KB
Main entry chunk (gzip)157.1 KB350 KB
Entry fileindex-DWFb0bIl.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)11.89KB4.50KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.90KB114.81KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.85KB12.89KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.48KB34.49KB
plugin-designer (index.js)212.80KB43.15KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)131.98KB32.23KB
plugin-gantt (index.js)165.16KB40.33KB
plugin-grid (index.js)201.66KB54.58KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)112.86KB27.54KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.72KB7.71KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)85.87KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)65.97KB21.98KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)9.30KB3.22KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-salesClaude

Copy link
Copy Markdown
Collaborator

ACCEPT on substance (ui execution seat, session_01CRJge11jso9TpXRWFt1Z49), at head 7ea99619 — the prose-only softening on top of the reviewed implementation commit ab2e6869. Landing is gated only on the farm finishing: 29/29 registered on the new head, 0 failures, shards and Type Check still running. ⛔ Not flipped yet.

The ruled half is complete, including the clause that turned out to be vacuous

The maintainer's 2026-08-27 Option C ruling is satisfied: headerIcon declared on TableColumn, mirrored in TableColumnSchema, tombstoned on the static twin under #5474's lockstep rule, changeset @object-ui/types minor. The fitContent arm executed the ruled order of operations — measure first — found real authors, and stopped exactly where the ruling said to stop. ⭐ Stopping at a ruled fork instead of finishing the card is the correct outcome here, not an incomplete one.

The one clause I queried — «the as any reads go» — is measured vacuous for this key, and I am satisfied it is vacuous rather than skipped. The card's own census had put both as any sites on fitContent, not headerIcon. No work was invented to satisfy it.

What the review changed, and why it was worth a cycle

The measurement came back against this PR's own framing, which is the reason the question was worth asking. Both headerIcon reads sit at col: any — widened by the col: any normalization in data-table.tsx, not by any cast. So the declaration reaches the typed authoring boundary and the parse road, but ⛔ not the renderer's internal reads: a misspelled read inside the renderer still compiles silently.

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 StaticTableColumn-typed site and went red, which is what licenses reading P1's green as a finding. P3 confirmed the boundary the change does reach, with a correctly-spelled sibling staying green so the red is attributable.

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 answer

The two (col as any).fitContent casts are redundant todaycol is already any at both sites. So the real mediator of every undeclared read in that file is the col: any normalization, not the casts the card treated as the tell. ⛔ Any card in this family that reasons from cast-count is reading the wrong instrument, and at least one open card does.

The routing note — that typing the two render maps is entangled with the fitContent verdict, because under a typed callback those redundant casts become load-bearing and the same normalization also carries the deliberately-held name alias (#5120) — is being folded into the fitContent decision filing on #6424 as an input to that ruling. It is not being dropped, and it is correctly not in this PR.

Housekeeping

Part of #6424 is correct and must stay: the fitContent half and the emit-side cast at ObjectGrid.tsx:3418 remain open on the card, so a Fixes here would close a card with half its ruled scope undone. packages/plugin-grid untouched. The #6004-family held keys and the name alias are correctly out of scope.


Generated by Claude Code

@os-sales
os-sales marked this pull request as ready for review August 27, 2026 14:37
@os-sales
os-sales added this pull request to the merge queueAug 27, 2026
Merged via the queue into main with commit 4e480f5Aug 27, 2026
30 checks passed
@os-sales
os-sales deleted the claude/issue-6424-tablecolumn-headericon-fitcontent branch August 27, 2026 14:50
os-sales pushed a commit that referenced this pull request Aug 28, 2026
`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
os-sales pushed a commit that referenced this pull request Aug 28, 2026
…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-sales@claude