Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-grid): read the declared prefix column key without a cast - #6587
fix(plugin-grid): read the declared prefix column key without a cast#6587claude[bot] wants to merge 2 commits into
prefix column key without a cast#6587Conversation
`ObjectGrid.generateColumns()` read five keys off the authored `ListColumn` through `(col as any)`. Four of them — `format`, `options`, `appearance`, `essential` — are genuinely undeclared: `ListColumnSchema` is a `strictObject`, so an author who writes one is refused at publish with `unrecognized_keys` while this renderer honours it. Those four are NOT touched here; each needs a declare-on-spec vs stop-reading verdict, the declare leg widens a published `@objectstack/spec` contract, and AGENTS.md #0.1 forbids answering either one renderer-side. The fifth, `prefix`, is the opposite defect and free to fix: `ListColumn` DECLARES it (measured from `ListColumnSchema.shape`), so the cast bought nothing and cost twice — it made a schema-admitted key look exactly like the four that are not, and it discarded `ColumnPrefix`'s typing, leaving `prefixConfig.field` as `any` at every use in the prefix cell renderer. `columnReadBoundary-6458.test.ts` is the read-side counterpart to `columnEmitBoundary-6004.test.ts`. The defect is that a read is UNCHECKED, and nothing observable at runtime distinguishes `(col as any).prefix` from `col.prefix`, so the pin is a scoped source scan with its anchors asserted first. It refuses any cast read of a key the schema declares (this fix, made mechanical) and bounds the undeclared set to the four escalated ones, so a fifth cannot arrive anonymously while the adjudication is open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
…ng nothing The cast erased at runtime, so the same property is read before and after — no published behaviour moves. Empty frontmatter is this repo's explicit "releases nothing" spelling (objectui has no `skip-changeset` label), and `check-changeset-presence.mjs` accepts it as a complete answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
os-support-ai
commented
Aug 26, 2026
Blocker, stated once: the two non-green checks here are
This is the repository-wide Actions runner starvation recorded in #6588. Nine such cancellations are currently visible across this PR, #6589 and ⛔ No push, no re-run, and no change to this PR — there is nothing here to fix. ⛔ And explicitly not a case for weakening anything: a fifteen-minute infrastructure cancellation is not a gate to relax. This seat is holding the PR and will land it once the checks can actually run. The card's four maintainer decisions on Generated by Claude Code |
Part of #6458 — this lands only the free, unconditional half. The card stays open:
the four-key adjudication below is not answered here and must not be closed by this merge.
Why this is
Part of, notFixesThe card's triage recorded two unlock notes: (1) the
prefixcast drop is free andunconditional, and (2) the per-key adjudication has a cross-repo leg. This PR is
note (1) and nothing else. Note (2) needs a maintainer verdict per key and a
domain:speccard inobjectstackfor any "declare the key" outcome — AGENTS.md #0.1forbids answering it renderer-side either way.
The card's premise is LIVE — correcting the unlock note
The unlock comment said "the
(col as any)mechanism this card is named for is gonefrom that path."Re-measured on
main@6a7893d57, that is not so.#6461 typedthe function's return; the reads inside its body are untouched:
ListColumndeclares it?ListColumnSchema.safeParse:1815(col as any).formatunrecognized_keys:1840,:1841(col as any).optionsunrecognized_keys:1846(col as any).appearanceunrecognized_keys:1925(col as any).prefix:1951(col as any).essentialunrecognized_keysMeasured at runtime from
ListColumnSchema.shape, not read off a.d.ts.ListColumndeclares exactly 14 keys:
field, label, width, align, hidden, sortable, resizable, wrap, type, pinned, summary, prefix, link, action.(A sixth cast at
:3418—(col as any).fitContent— is a differentcol: ititerates emitted
orderedColumns, not the authored input. It is deliberately outsidethis PR's guard region.)
Fixed or relocated? For this card's keys, neither
RetiredListColumnKey = Exclude<keyof ListColumn, keyof TableColumn | 'wrap' | 'pinned'>computes to the 7-key band
field, label, hidden, summary, prefix, link, action.That band provably cannot reach this card's four keys — it is derived from
keyof ListColumn, andformat/options/appearance/essentialare notListColumnmembers at all. Nor is any of them inObjectGridColumnHolds, which holdsexactly
headerIcon,pinned,wrap. So #6461's fence is structurally incapable ofcovering the read side: it is an excellent fence on the emit, and the read defect
is untouched, not relocated.
What this PR changes
One line:
(col as any).prefix->col.prefix. The cast cost twice — it made aschema-admitted key look exactly like the four that are not, and it discarded
ColumnPrefix's typing, leavingprefixConfig.fieldasanyat every use in the prefixcell renderer. No runtime behaviour moves; the cast erased at runtime.
columnReadBoundary-6458.test.tsis the read-side counterpart tocolumnEmitBoundary-6004.test.ts. It refuses any cast read of a key the schema declares,and bounds the undeclared set to the four escalated ones so a fifth cannot arrive
anonymously while the adjudication is open. It is a source scan because nothing
observable at runtime distinguishes
(col as any).prefixfromcol.prefix; every anchoris asserted present, by count, before any "no such key" claim is made.
Verification — all at
a789bc49fAblation (the test would have failed before). Mutation proven on disk before reading
any result: injected-text count 1, removed-text count 0, blob
7d775f1f0!= HEAD blob1488f7b1e. Result:1 failed | 6 passed—expected [ 'prefix' ] to deeply equal []on the ⭐ assertion. Restore leg proven byte-identical to HEAD (
git diff HEAD= 0 bytes,hash back to
1488f7b1e).plugin-grid type-check(tsc --noEmit && tsc -p tsconfig.test.json)Test Files 94 passed (94)·Tests 873 passed (873)check:control-bytesOK (scanned 5445 tracked text file(s))check:phantom-depsEvery in-scope import is declaredcheck:spec-symbolscheck:self-importNo package names itself inside its own src/check-changeset-presence.mjstsc -p tsconfig.test.json --listFilesconfirms the new test is in the typecheckprogram (1 hit, positive control: the sibling
columnEmitBoundary-6004also 1 hit), soits compile-time
IsAnyassertion is genuinely measured rather than silently excluded.Lint — declared narrowing. Repo-wide
eslint .is CI's run. Narrowed here to the twochanged TS files, and the narrowing is measured, not assumed: type-aware linting is not
enabled (
tseslint.configs.recommended, noparserOptions.project/projectService),so this diff cannot move any untouched file's verdict.
--format jsonreports 2 fileslinted, 0 errors. Ratchet moves the right way:
ObjectGrid.tsx219 -> 218 warnings,no-explicit-any186 -> 185 — exactly the one cast removed, nothing else.Out of scope
packages/types/src/data-display.tsis untouched. #6424, #6425, #6457 and #6459 remainopen and are not addressed here — all four name this same file. Note that #6424's body is
stale in the same way this card's was.
Generated by Claude Code
Generated by Claude Code