Skip to content

fix(plugin-grid): read the declared prefix column key without a cast - #6587

Draft
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-6458-objectgrid-held-keys
Draft

fix(plugin-grid): read the declared prefix column key without a cast#6587
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-6458-objectgrid-held-keys

Conversation

@claude

@claudeclaudeBot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

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, not Fixes

The card's triage recorded two unlock notes: (1) the prefix cast drop is free and
unconditional
, 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:spec card in objectstack for any "declare the key" outcome — AGENTS.md #0.1
forbids 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 gone
from that path."
Re-measured on main @ 6a7893d57, that is not so.#6461 typed
the function's return; the reads inside its body are untouched:

linereadListColumn declares it?ListColumnSchema.safeParse
:1815(col as any).formatnoREFUSED — unrecognized_keys
:1840,:1841(col as any).optionsnoREFUSED — unrecognized_keys
:1846(col as any).appearancenoREFUSED — unrecognized_keys
:1925(col as any).prefixyesACCEPTED — the cast was noise
:1951(col as any).essentialnoREFUSED — unrecognized_keys

Measured at runtime from ListColumnSchema.shape, not read off a .d.ts. ListColumn
declares 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: it
iterates emitted orderedColumns, not the authored input. It is deliberately outside
this 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, and format / options / appearance / essential are not
ListColumn members at all. Nor is any of them in ObjectGridColumnHolds, which holds
exactly headerIcon, pinned, wrap. So #6461's fence is structurally incapable of
covering 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 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. No runtime behaviour moves; the cast erased at runtime.

columnReadBoundary-6458.test.ts is the read-side counterpart to
columnEmitBoundary-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).prefix from col.prefix; every anchor
is asserted present, by count, before any "no such key" claim is made.

Verification — all at a789bc49f

Ablation (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 blob
1488f7b1e. Result: 1 failed | 6 passedexpected [ 'prefix' ] to deeply equal []
on the ⭐ assertion. Restore leg proven byte-identical to HEAD (git diff HEAD = 0 bytes,
hash back to 1488f7b1e).

gateverdict (exit captured before any pipe)
plugin-grid type-check (tsc --noEmit && tsc -p tsconfig.test.json)exit 0
plugin-grid suite, from repo rootTest Files 94 passed (94) · Tests 873 passed (873)
check:control-bytesexit 0 — OK (scanned 5445 tracked text file(s))
check:phantom-depsexit 0 — Every in-scope import is declared
check:spec-symbolsexit 0
check:self-importexit 0 — No package names itself inside its own src/
check-changeset-presence.mjsexit 0 — empty frontmatter accepted as "releases nothing"

tsc -p tsconfig.test.json --listFiles confirms the new test is in the typecheck
program (1 hit, positive control: the sibling columnEmitBoundary-6004 also 1 hit), so
its compile-time IsAny assertion is genuinely measured rather than silently excluded.

Lint — declared narrowing. Repo-wide eslint . is CI's run. Narrowed here to the two
changed TS files, and the narrowing is measured, not assumed: type-aware linting is not
enabled
(tseslint.configs.recommended, no parserOptions.project / projectService),
so this diff cannot move any untouched file's verdict. --format json reports 2 files
linted, 0 errors. Ratchet moves the right way: ObjectGrid.tsx 219 -> 218 warnings,
no-explicit-any 186 -> 185 — exactly the one cast removed, nothing else.

Out of scope

packages/types/src/data-display.ts is untouched. #6424, #6425, #6457 and #6459 remain
open 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

`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-aiClaude

Copy link
Copy Markdown
Collaborator

Blocker, stated once: the two non-green checks here are cancelled, not failures, and they are not this PR's.

Skill Guide Path Check and Doc Component Type Check both report steps: 0 — no step ever executed — and both ran exactly ~15 minutes before being cancelled (15:33:44→15:48:45 and 15:35:30→15:50:32). A job that never executed a step did not fail; it never started.

This is the repository-wide Actions runner starvation recorded in #6588. Nine such cancellations are currently visible across this PR, #6589 and main itself (5 on the base branch's own HEAD), with zero genuine failure conclusions anywhere.

⛔ 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 format / options / appearance / essential are unaffected and stay open on #6458, which is why this PR is titled "Part of #6458" rather than "Fixes".


Generated by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-support-ai@claude