docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected - #7202

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census
Sep 1, 2026
Merged

docs(plugin-grid): re-derive ObjectGrid's held-key censuses — 16 claims checked, 7 corrected#7202
os-warren merged 1 commit into
mainfrom
claude/issue-7196-stale-held-key-census

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7196

Comments and one test docblock/title only. No runtime code, no exported type, no declared member moves. Clause ② is not engaged and was not approached — packages/types is untouched.

The headline number

Sixteen census claims re-derived; seven were defective. The card was filed on ONE measured entry and explicitly did not assert the others were correct, only that nobody had checked. The re-derivation is what produced the other six.

Four had drifted since they were written. Three were wrong on the day they were written — which the card could not have predicted, and which is the more interesting half.

The card's entry, re-measured (A2.1)

ObjectGridDataTableSchemaHolds listed renderCellEditor as an undeclared-but-live HELD key and called the packages/types ruling on it pending. #6882 landed that ruling on 2026-08-30. Verified against origin/main at c97752d2f, each with a live control in the same query shape:

surfacelocation
the memberpackages/types/src/data-display.ts:945
Zod mirrorpackages/types/src/zod/data-display.zod.ts:270
Equal exact-shape pinpackages/types/src/__tests__/data-table-declared-keys-6882.test.ts:119

The (schema as any).renderCellEditor cast the census cites went with the declaration: data-table.tsx:2318 reads schema.renderCellEditor directly. (Probed for the presence of the replacement, not the absence of the removed token — the line above it still spells the old cast as a quotation inside its own correction, which is exactly the false negative that probe order avoids.)

A2.2 — behaviour and soundness, verified rather than inherited

Confirmed, and it holds. Measured with a type-level probe carrying a must-fail control (the control was the only diagnostic where one was expected):

  • each hold's shape is Equal — not merely extends — to the upstream declared member;
  • the seam member and the declared member are mutually assignable, measured in both directions;
  • the printed types differ only in that the seam's renderCellEditor is the identical signature intersected with itself (cellClassName reduces outright, string & string being string). Inert, and the one visible trace the now-redundant hold leaves.

⇒ Nothing mis-renders, no type is unsound. This stayed a documentation card.

A2.3 — the rest of the census, which nobody had checked

Stale (correct when written, drifted since):

  1. renderCellEditor — above.
  2. cellClassName — declared by the SAME ruling. The card named only the first key; this one carried "same pending ruling as above" and is false twice over. Not asserted by the card, found by re-deriving.
  3. "leaves exactly TWO undeclared keys" — now ZERO. Re-derived with the TypeScript checker: the flat literal writes 46 keys and the group literal 8 (both counts still exact), diffed against DataTableSchema's 75 declared members with the index signature excluded. Control: a bogus key returns absent on the same instrument that returns present for columns.
  4. the consumer read set named fourteencol. reads in data-table.tsx including name; fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963 retired that alias on 2026-08-31, so it is thirteen. Measured by AST over the whole file, not by grep.

Wrong when written, not drift — each dated against the commit that wrote the prose:

  1. the schema-level cellClassName was described as folded "into every body cell's className". It reaches exactly three UTILITY cells — the selection checkbox, the row-number cell, the row-actions cell — and never a data cell, which folds col.cellClassName instead. The two class slots style disjoint cells. So the stated failure mode is wrong too: data cells keep their density (applyDensity puts it on every column); what breaks without the schema key is the utility cells falling out of height alignment. Verified at beccf1c6b, the commit that wrote the sentence: the same three sites, already utility-only. [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882's declaration carries the authoritative version; this makes the local copy agree with it.
  2. "the 7-literal union TableColumn declares" — eight since fix(types): make the 8-literal union the one canonical TableColumn.type #6370 on 2026-08-25, one day before the docblock was written, and that commit's own subject line reads "make the 8-literal union the one canonical TableColumn.type". Both numbers are now measured: TABLE_COLUMN_TYPES has 8 members, @objectstack/spec's FieldType enum has 49 (that one was right).
  3. the downstream read list omitted four keys the chrome passes read in order to re-express them — className and cellClassName (applyDensity), sortable (withSortability), cell (the mobile card renderer). All three passes already existed at the census commit, so this is incompleteness, not drift. No verdict moves: each is declared on TableColumn and read by data-table.tsx too.

Re-derived clean (9) — recorded because "the others are correct" is a real result: the 46/8 literal counts · the eight already-declared keys from the card's speculative list · BaseSchema's index signature · headerIcon DECLARED with its 2 syntactic reads · pinned HELD (undeclared by TableColumn, 5 reads here, 0 in data-table.tsx against a 28-hit control) · wrap RETIRED with no wrap affordance and no density/rowHeight read · options RETIRED and declared on TableColumn · name emitted by nothing here · essential a member of neither type.

A2.4 — is any of it mechanically derived? Partly, and that is the finding

RetiredListColumnKeyis derived (Exclude over keyof ListColumn), and it re-derives correctly. But the censuses themselves are hand-maintained, and the two are guarded asymmetrically:

  • the column-level hold's entry condition is pinned — columnHoldsExpiry-6424.test.ts asserts TableColumn does NOT declare pinned;
  • the schema-level one is not. dataTableSchemaSlot-6459.test.ts pins that the seam ACCEPTS both keys — green whether they are held here or declared upstream. An acceptance pin cannot express an entry condition, so it could never have gone red at the moment of loss.

That asymmetry is why this census rotted and its twin did not. Recorded in both docblocks, and filed as #7201. ⛔ Not built here, per the order.

What is deliberately NOT done

The holds type is kept. Both members are redundant with DeclaredDataTableSchema now, but deleting a member of an exported type is a different kind of change and gets its own card — the order #6615#6424 took for headerIcon, which measured before it deleted. The docblock now states why it survives and carries the three measurements that card would start from.

⚠️Overlap for triage, not acted on here:#6919 is open and covers this same type. Its scope is (1) retire the seam hold or state in its docblock why it survives as a deliberate redundancy, and (2) correct the docblock either way — the standing prohibition against declaring these keys had to go regardless. This PR does both in the "keep and state why" branch: the ⛔ prohibition is gone and the deliberate redundancy is recorded. Whether #6919 stays open for the hold's actual removal is triage's call — ⛔ this PR does not touch its state, and no keyword here acts on it. Refs: #6919.

Verification

Everything below ran on d20267a93, the branch head, and git diff HEAD was empty at that point — so the tree measured is byte-identical to the tree committed.

gateresult
pnpm --filter '@object-ui/plugin-grid^...' buildexit 0 — dependency closure built first
pnpm --filter @object-ui/plugin-grid type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
tsc -p tsconfig.test.json --listFilesboth edited files ARE program inputs — 1 hit each, control 0. The package's own tsconfig.json excludes __tests__, so without this the green would say nothing about the test file
pnpm exec vitest run packages/plugin-grid/105 files, 952 tests, all passed
the census suite, verbose7/7, including the renamed accepts both schema-level keys
pnpm --filter @object-ui/plugin-grid linteslint exit 0 — 0 errors, 749 pre-existing warnings
node scripts/check-control-bytes.mjs✅ OK (5964 tracked text files scanned)
node scripts/check-changeset-presence.mjs✅ empty frontmatter accepted as the explicit exemption
node scripts/check-changeset-no-major.mjs✅ no changeset declares major
node scripts/check-shell-escape-residue.mjs✅ OK

Exit codes were captured before any pipe, and each row quotes the gate's own verdict line rather than a bare $?.

Declared narrowing. Repo-wide pnpm lint was not run; the affected package's own eslint . was, which is a strict superset of the diff. Three pieces of evidence, not two: (1) the population came from eslint's own config resolution, not a guess about which files count; (2) --format json reports exactly 2 files for the diff — ObjectGrid.tsx 0 errors / 209 pre-existing warnings, the test file 0 / 0; (3) type-aware linting is not enabled — eslint.config.js declares no projectService and no parserOptions.project, against a control confirming the same grep reads that file — so every verdict is per-file and a comment-only diff in two files cannot move a verdict on an untouched file. The farm runs in full on CI regardless.

No ablation was run and none is owed. Nothing here can fail differently: the diff adds no assertion and removes none. The one assertion touched keeps its exact shape and only its name and docblock change.

Changeset

.changeset/7196-plugin-grid-held-key-census.md, empty frontmatter — the explicit "releases nothing" declaration this repo's gate accepts, since the diff is comments in src/ and publishes no behaviour. ⛔ The skip-changeset label is not this repo's mechanism and was not applied.


Generated by Claude Code

…aims corrected
The card was filed on ONE measured entry: the schema-level census still listed
`renderCellEditor` as an undeclared-but-live HELD key and called the
`packages/types` ruling on it "pending", when objectui#6882 declared it on
2026-08-30. Because a hold list that is stale in one entry has no claim to be
current in the others, all sixteen claims across both censuses in the file were
re-derived against the tree rather than read. Seven were defective; nine hold.
Stale -- correct when written, drifted since:
- `renderCellEditor` -- declared by #6882 on three surfaces (the member in
data-display.ts, the Zod mirror, an `Equal` exact-shape pin). The
`(schema as any)` cast the census cites went with the declaration.
- `cellClassName` -- declared by the SAME ruling. The card did not name this
one; only the re-derivation found it. It carried "same pending ruling as
above", which is now false twice over.
- "leaves exactly TWO undeclared keys" -- re-derived by diffing the flat
literal's 46 keys plus the group literal's 8 against `DataTableSchema`'s
declared members: ZERO today.
- the consumer read set named fourteen `col.<key>` reads in `data-table.tsx`
including `name`; objectui#6963 retired that alias on 2026-08-31, so it is
thirteen.
Wrong when written, not drift:
- the schema-level `cellClassName` was described as folded "into every body
cell's `className`". It reaches exactly three UTILITY cells (selection,
row-number, row-actions) and never a data cell, which folds the per-column
twin instead; the failure mode the census names is wrong the same way. The
three fold sites were already utility-only at the commit that wrote this.
- "the 7-literal union `TableColumn` declares" -- eight since objectui#6370
(2026-08-25), a day before the docblock was written, and that commit's own
subject line reads "8-literal".
- the downstream read list omitted four keys the chrome passes read in order to
re-express them (`className`, `cellClassName`, `sortable`, `cell`); all three
passes predate the list.
Also recorded: nothing guards this census mechanically, which is how it went
stale unseen. `dataTableSchemaSlot-6459.test.ts` pins that the seam ACCEPTS both
keys -- green whether they are held here or declared upstream -- so an
acceptance pin cannot express a hold's entry condition. The column-level twin IS
guarded (`columnHoldsExpiry-6424.test.ts` asserts `TableColumn` does not declare
`pinned`). Filed as a separate finding, deliberately not a rider.
The holds type is kept, not deleted: removing a member of an exported type is a
different kind of change and gets its own card, the order #6615 -> #6424 took
for `headerIcon`. Its docblock now carries what was measured toward that.
Comments and one test docblock/title only. No runtime code, no exported type, no
declared member moves; the changeset declares an empty frontmatter accordingly.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-D_htOe6-.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)68.45KB19.20KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 7196,
"status": "done",
"branch": "claude/issue-7196-stale-held-key-census",
"pr": "https://github.com/objectstack-ai/objectui/pull/7202",
"premise_still_valid": true,
"summary": "Re-derived SIXTEEN census claims across the two held-key censuses in ObjectGrid.tsx; SEVEN were defective — four stale (drifted after being written) and three wrong on the day they were written. The card named one; the re-derivation found the other six. Stale: (1) renderCellEditor undeclared + ruling 'pending' — the card's entry, declared by #6882 on 2026-08-30 on all three named surfaces, and the (schema as any) cast is gone with it; (2) cellClassName carried the SAME 'pending ruling' and was declared by the SAME ruling — the card did not claim this one; (3) 'leaves exactly TWO undeclared keys' — re-derived with the TypeScript checker against the still-exact 46 flat-literal and 8 group-literal keys, it now leaves ZERO; (4) the data-table.tsx consumer read set listed fourteen col.KEY reads including 'name', retired by #6963 on 2026-08-31, so it is thirteen. Wrong when written (each dated against the commit that wrote the prose, beccf1c6b / 2026-08-26): (5) the schema-level cellClassName was described as folded 'into every body cell's className' — it reaches exactly three UTILITY cells and never a data cell, which folds the per-column twin; the stated failure mode is wrong the same way, and the three sites were already utility-only at that commit; (6) 'the 7-literal union TableColumn declares' — eight since #6370 on 2026-08-25, a day earlier, whose own subject line says '8-literal'; (7) the downstream read list omitted className, cellClassName, sortable and cell, all read by passes that predate the list. NINE claims re-derived clean and are recorded as such, including every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 — 'the others are correct' is a real result and it is now measured rather than assumed. A2.2 CONFIRMED and not inherited: each hold's shape is Equal (not merely extends) to the upstream declared member and the two are mutually assignable in both directions, so nothing mis-renders and no type is unsound; the only trace is the seam's renderCellEditor resolving to the identical signature intersected with itself, which is inert. A2.4 answered: RetiredListColumnKey IS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream and so could never have gone red at the moment of loss. That asymmetry is why this census rotted and its twin did not. Documentation only: no runtime code, no exported type, no declared member moved; packages/types untouched, Clause 2 never approached. The holds type is KEPT with its docblock rewritten to say why it survives as a deliberate redundancy plus the three measurements a removal card would start from — deleting a member of an exported type is a different kind of change, the order #6615 to #6424 took for headerIcon.",
"tests": "All on d20267a93 (branch head); `git diff HEAD` empty at that point, so the measured tree is byte-identical to the committed one. Dependency closure built FIRST: `pnpm --filter '@object-ui/plugin-grid^...' build` exit 0. `pnpm --filter @object-ui/plugin-grid type-check` exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` (note the hyphenated script name this repo uses). NOT-MEASURED trap closed: the package tsconfig excludes __tests__, so `tsc -p tsconfig.test.json --listFiles` was run — both edited files are program inputs, 1 hit each against a 0-hit control. `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` (from the repo ROOT — the package-dir invocation is refused by a repo guard, which is a PREREQUISITE failure and not a red test) => 105 files, 952 tests, ALL PASSED. Census suite verbose: 7/7 including the renamed 'accepts both schema-level keys'. `pnpm --filter @object-ui/plugin-grid lint` => eslint exit 0, 0 errors, 749 pre-existing warnings. Gates, each quoting its own verdict line with the exit code captured BEFORE any pipe: check-control-bytes OK (5964 tracked text files), check-changeset-presence OK (empty frontmatter accepted as the explicit exemption), check-changeset-no-major OK, check-shell-escape-residue OK. Re-run on the final HEAD after committing. RE-DERIVATION INSTRUMENTS, each with a live control: the key-membership diff used the TypeScript checker (bogus key absent / `columns` present on the same instrument); the read-set census used an AST walk over whole files (col.pinned = 0 in data-table.tsx against col.accessorKey = 28 in the same command and path scope); the A2.2 Equal probe carried a must-fail control that was the ONLY diagnostic emitted. Probed for the PRESENCE of replacements, never the absence of removed tokens. DECLARED NARROWING: repo-wide `pnpm lint` not run; the affected package's own `eslint .` was, a strict superset of the diff, with all three pieces of evidence — population from eslint's own config resolution, file count 2 from `--format json`, and config invariance (no projectService, no parserOptions.project in eslint.config.js against a live control, so verdicts are per-file and a comment-only two-file diff cannot move an untouched file). NO ABLATION was run and none is owed: the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.",
"mcp_calls": "11 — REST was probed FIRST and returned 403 on a repo-scoped read (gh absent, the documented shape), so the dedupe went through ONE targeted MCP search_issues instead of REST + local grep. Channel switch declared. Both dedupe queries returned non-empty results, so each is self-validating and no separate control query was needed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7201: the schema-level held-key census has NO gate — its only pin (dataTableSchemaSlot-6459.test.ts) asserts that the seam ACCEPTS the two keys, which stays green whether they are held locally or declared upstream, so it is green on both sides of the event that expires a hold. The column-level twin IS guarded (columnHoldsExpiry-6424.test.ts pins that TableColumn does not declare pinned). Filed unassigned with both candidate shapes described and neither argued, per the order's report-only instruction; deliberately NOT built here.",
"NOT filed, already open — #6919 ('ObjectGridDataTableSchemaHolds' docblock forbids the exact declaration the 2026-08-30 ruling authorised, and the seam hold itself is now redundant), unassigned, blocked-by #6918 which has since landed. Caught by the dedupe search, so no duplicate was created. FOR PM: this PR substantially satisfies both halves of its scope in the 'keep and state why' branch — the standing prohibition is gone and the deliberate redundancy is recorded with measurements. Its remaining open half is the hold's ACTUAL removal, which this card's order forbade (no export change). Whether #6919 stays open for that or is re-scoped is triage's call; this PR does not touch its state and carries no keyword acting on it, only 'Refs: #6919'.",
"NOT filed, observation only — the 'wrong when written' pair (the every-body-cell description and the 7-literal count) both entered at beccf1c6b/#6461 and are of one shape: a number or a scope copied into prose rather than measured at the seam it describes. Both were checkable in under a minute by the instruments used here. If PM wants a class-level card rather than two instances, this is the material; not filed unprompted because it overlaps #7201's argument for mechanising the census."
]
}

Generated by Claude Code

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. The #6919 disposition is ruled below.

⭐ The Zone 1 ruling earned itself six times over

I ruled "re-derive the WHOLE census, do not just fix the one entry — a hold list that is stale in one entry has no claim to be current in the others." Result:

16 claims re-derived. SEVEN defective. The card named ONE.

And the seven split into two genuinely different kinds, which is the part I did not anticipate:

kindcountexamples
stale — true when written, drifted since4renderCellEditor (the card's entry); cellClassName carrying the SAME "pending ruling" prose, declared by the SAME #6882 ruling; "leaves exactly TWO undeclared keys" → now ZERO; a 14-item read set that is 13 since #6963 retired name
wrong when written — false on the day, dated to beccf1c6b3schema-level cellClassName described as folding into "every body cell's className" when it reaches exactly three utility cells and never a data cell; "the 7-literal union" which was eight since #6370 landed a day earlier, whose own subject line says "8-literal"; a downstream read list omitting four keys read by passes that predate it

⚠️The second kind is the more alarming finding and it was not in the card at all. "Stale" implies a document that was once accurate — an ordinary, forgivable decay. Three of these were never accurate. A census that was wrong on the day it was written has no moment at which a reader could have trusted it, and no drift to detect.

⭐ And the root cause is diagnosed, not just the symptoms

A2.4 asked whether the census is hand-maintained. The answer is sharper than the question:

RetiredListColumnKeyIS mechanically derived and re-derives correctly, but the censuses are hand-maintained and guarded asymmetrically — the column-level hold's entry condition is pinned (columnHoldsExpiry-6424.test.ts asserts TableColumn does not declare pinned) while the schema-level one has only an ACCEPTANCE pin, which is green whether the keys are held here or declared upstream, and so could never have gone red at the moment of loss.

⇒ That is why this census rotted and its twin did not, and it is a general lesson worth more than the fix: a pin that stays green on both sides of the event it is supposed to catch is not a guard. The acceptance pin looks like coverage, runs on every CI, and is structurally incapable of failing when a hold expires. Filed as #7201, report-only per the order, with both candidate shapes described and neither argued.

The nine clean claims are a real result, and recording them is the point

Every column-level verdict (headerIcon, pinned, wrap, options, essential, name) and the FieldType count of 49 re-derived correctly. ⭐ "the others are correct" is now measured rather than assumed — which is exactly the epistemic state the card asked for and the one a future reader needs. A card that fixed only the named entry would have left the other fifteen in the same unverified condition they started in.

A2.2 confirmed properly, not inherited

Each hold's shape is Equal (not merely extends) to the upstream declared member, and the two are mutually assignable in both directions — so nothing mis-renders and no type is unsound. The only trace is the seam's renderCellEditor resolving to its own signature intersected with itself, which is inert. Checking assignability both ways is what makes "identical" a measurement rather than a one-directional subtype check that would pass on a narrower hold.

No ablation, and none owed — correctly reasoned rather than silently skipped

the diff adds and removes no assertion; the one assertion touched keeps its exact shape and only its name and docblock change.

⭐ Stating why an ablation would measure nothing is better than running a ceremonial one. An ablation proves a pin can move; where no pin's behaviour changed, there is nothing to move.

Scope held exactly

Documentation only — no runtime code, no exported type, no declared member moved, packages/types untouched, Clause ② never approached. The holds type is kept, with its docblock rewritten to say why it survives as a deliberate redundancy, plus the three measurements a removal card would start from.

⚖️ Ruling on #6919: it stays open, re-scoped to the removal alone

The lane correctly did not touch it and carried only Refs:. My disposition:

This PR discharges #6919's first half — the standing prohibition that forbade the exact declaration #6882 authorised is gone, and the redundancy is now recorded with measurements rather than asserted. What remains is the hold member's actual removal, which my order forbade (no export change) and which is Clause ②: deleting a member from an exported type narrows published surface, so it needs CONTRACT_REVIEW_TIER — currently exhausted.

#6919 stays open, narrowed to the removal, and joins the tier-blocked pile rather than being closed as substantially satisfied. ⛔ Closing it would lose the only tracker for a removal this PR deliberately did not make. The order it should follow is the one #6615#6424 took for headerIcon, as the lane notes.

On the class-level card offered but not filed

The two "wrong when written" claims entered at the same commit and share one shape — a number or a scope copied into prose rather than measured at the seam it describes, both checkable in under a minute by the instruments used here. ⭐ Declining to file it unprompted was right, because it overlaps #7201's argument for mechanising the census. I am not filing it separately either: the remedy for both is the same gate, and a second card would split the evidence for one fix across two threads. It is recorded here and on #7201.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-warren@claude