…ng them (objectui#9880)
`normalize-list-view`'s two view-kind tables could not be total against
both the `@objectstack/spec` this repository resolves and one built from
objectstack `main`, which retired the list-view kind `page`. The
undrawable table was annotated `Record<Exclude<ViewType,
ListViewVisualization>, string | null>`, and an annotated object literal
is exact in both directions: the retired kind's row became an excess
property (TS2353) whose only repair under that annotation is deleting
the row — which the resolved published spec forbids, because an author
can still write a `page` view against it.
The table now carries `satisfies Record<string, string | null>` and the
undrawable union is `Extract<ViewType, keyof typeof
UNDRAWABLE_VIEW_KINDS>`, so a row for a retired kind goes inert at type
level while staying live at runtime. `ListViewVisualization` subtracts
that derived union instead of the literal `'list' | 'detail' | 'page'`;
its members are unchanged under either spec.
The add-direction guard is unchanged and still mechanical: a kind
neither table classes stays inside `ListViewVisualization`, so the total
`Record<ListViewVisualization, true>` on `LIST_VIEW_KINDS` fails the
build until it is classed as drawable there or explained here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
Fixes #9880
Clause-②: no — the exported
ListViewVisualizationresolves to the same nine members under either spec (compiled both ways below, and every downstream consumer re-compiled against the rebuilt declarations); no key on a published payload moves and no accept set is relaxed. What moves is how that type is DERIVED, plus a non-exported alias and table shape now visible in@object-ui/core's emitted declaration.⭐ The judgement the card asked for first: YES, a both-legs spelling exists
The card's first deliverable was the judgement, not a delete — and triage's mechanical re-grade switch (
pm:retriage→pm:on-hold) was to be tripped only if a both-legs spelling were measurably impossible. It is not impossible, so the switch is not tripped and no re-grade is asked for.Why the old spelling could not survive the retirement. Two literals were cited on the card; only ONE of them was ever a compile error, and this is a correction to the card's framing:
mainlegListViewVisualizationExclude<ViewType, 'list' | 'detail' | 'page'>Excludeof a non-member is a no-op no compiler readsUNDRAWABLE_VIEW_KINDSRecord<Exclude<ViewType, ListViewVisualization>, string | null>page:row is an excess propertyAn annotated object literal is exact in BOTH directions. That is what makes the retirement unfixable under it: the addition direction wants a missing key to be an error (correct), and the retirement direction then makes a stale row an error too, whose only repair is deleting the row — which this repository cannot do while it resolves a published spec that still accepts
type: 'page'views. The second literal was not a second error; it was a claim that had quietly stopped being true.The spelling that is total against both. Keep the value constraint, drop the exactness, and derive the other half of the partition from the table's own keys intersected with the live vocabulary:
Extractis the load-bearing operator: a row for a kind the spec has RETIRED drops out of the type while staying live in the table at runtime, which is exactly what an author who can still write apageview against the resolved spec needs.The guard objectui#8127 installed is unchanged, it just lands in one place instead of two. A kind the spec ADDS is classed by neither table, so it is not extracted, so it stays inside
ListViewVisualization— andLIST_VIEW_KINDSis still a totalRecord<ListViewVisualization, true>, which goes red until the new kind is classed as drawable there or explained in the undrawable table. A MISSPELLED key in the undrawable table fails the same way, because the kind it was meant to class is not extracted either. ⛔ objectui#9860's gate is not weakened, narrowed or disabled anywhere in this diff; ⛔ the@objectstack/specpin is untouched; ⛔ nothing here proposes reversing the upstream retirement.The two legs, compiled — same mechanism, same command
Injection is objectui#9877's own
scripts/spec-main-shape-gate.mjs inject(remove-then-copy into pnpm's virtual store,npm packbytes, 29 consumers verified), run from that branch's copy of the script; the script itself is not part of this diff. Command on every leg:TURBO_FORCE=true pnpm exec turbo run type-check --filter=@object-ui/core --concurrency=2, serialized behind the shared verify lock.Readings taken 2026-09-18T18:20Z–18:35Z. objectui base
f5e2fcb4a(origin/mainat 2026-09-18T18:15Z, which had already moved past the02bc27b08this card was dispatched against). Injected spec built fromobjectstack-ai/objectstack@07c6f822edd9c4c48a7ce34767de420c6096b9c3(origin/mainat 2026-09-18T18:19Z), packed as 2,039 files / 28,923,936 B.@objectstack/spec@17.4.007c6f822src/utils/normalize-list-view.ts(205,3): error TS2353: Object literal may only specify known properties, and 'page' does not exist in type 'Record<"list" | "detail", string | null>'@objectstack/spec@17.4.007c6f822The two vocabularies were read directly, so "both legs" is not an inference: the published spec's
ListViewSchema.shape.typeanswers["grid","kanban","gallery","calendar","timeline","gantt","map","chart","tree","page"]and themain-built one answers the same list withoutpage.⭐ Measured, and it changes what objectui#9877 is waiting for. With the
main-built spec injected,@object-ui/corealone has three diagnostics, not one. The gate reported one because each package'stype-checkscript istsc --noEmit && tsc -p tsconfig.test.json: the first half failed, so the TESTS project never ran. Running the tests project ALONE against the pristine (unchanged) source withmain's spec injected prints all three at once —— which is how the other two are proven to be pre-existing rather than introduced here (that control reverted this file by blob hash, ran, and restored by hash equality). Neither is this card's:
pageResidual-8429pin is the deliberate tripwire for objectui#8429: its own header says "narrowViewType⇒ the published faces stop acceptingpage⇒ RED". It is red for the reason it was written to be red, and it cannot be made both-legs-total without deleting the thing it pins — its assertions (SPEC_LIST_VIEW_TYPES.lengthis 10,ViewTypeSchema.safeParse('page').successis true) are TRUE today on the published leg. It is a pin-bump conversion, not a repair.object-validation-enginediagnostic is an unrelated upstream shape move (an expression wire that now requiressource).⇒ landing objectui#9877 still puts a red advisory check on every pull request. This diff removes one of the three reasons.
Tests
New pin
packages/core/src/utils/__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts. Any single run installs only ONE spec, so the type-level half lifts the source's two operators over a SIMULATED vocabulary — published-shaped,main-shaped, and grown — and asserts the derived drawable set is the same under the first two, that a total record over it compiles under both, and that a grown vocabulary forces the new kind to be classed.The pins fire. Ablating the two
@ts-expect-errordirectives (mutation proven on disk by blob hashf6189d5c…→7e392423…, restored tof6189d5c…by hash equality,trapon EXIT/INT/TERM with absolute paths) makestsc -p packages/core/tsconfig.test.jsonprint exactly the two diagnostics they hold down, on the published leg, where the source itself cannot produce them:The first is the card's own diagnostic, reproduced inside ordinary CI. One runtime assertion also needed a fresh module instance (
vi.resetModules()+ dynamic import): the undrawable warning is once-per-kind-per-module, and theunitproject runsisolate: false, so a sibling suite in the same worker had already spent it — the first draft of that test failed for exactly that reason before it was corrected.pnpm exec vitest run packages/core/(published spec)turbo run type-check --filter=@object-ui/core(published)turbo run type-check --filter=...@object-ui/core(published, core + every downstream consumer)turbo run build --filter=@object-ui/corenode scripts/check-changeset-presence.mjsnode scripts/check-control-bytes.mjsnode scripts/check-new-cross-file-line-citations.mjsnode scripts/check-type-check-coverage.mjspackages/core/tsconfig.test.jsonnode scripts/check-test-path-roots.mjs·node scripts/check-spec-symbol-derivation.mjspnpm exec eslinton both changed filesnode scripts/check-governed-queue-guard.mjs --teston the 3 changed pathspnpm type-checkon themain-built leg」 — is replaced rather than left standing. A repo-wideTURBO_FORCE=true turbo run type-check --concurrency=3 --continueagainst themain-built spec at objectstack07c6f822prints 20 unique diagnostics across 7 packages — core 2, plugin-view 3, types 6, react 1, console 1, plus 7 cascade inapp-shellandexample-byo-backend-consolefromplugin-view's failed build ⇒ 13 non-cascade across 5 packages.@object-ui/plugin-viewcarries three more hard-codedpagerows of exactly this card's class. ⭐ Without--continue, turbo cancels the dependents and the same census collapses to 1. ⇒ this belongs to objectui#9877's decision and is carried there and on its own cards; it is recorded here because a reader of #9877 would otherwise use the replaced sentence to decide whether the gate's day-one break is cleared.Body edited by the
domain:ui#2execution seat at 2026-09-18T19:00Z (clock read by this write), on the dev's own hand-back and its recommendation A — a dev writes its PR body once and ⛔ never PATCHes it, so a falsified NOT-MEASURED claim is the seat's to repair. ⛔ Nothing else in this body was changed.Acceptance notes — out of scope, noted, not fixed here
type-checkistsc && tsc -p tsconfig.test.json, so on a red leg the tests project never runs and its diagnostics never reach the annotation step. objectui#9877's "one file fails" is therefore an artifact of&&, not a census. Worth its own card against the gate. Dedupe words:spec-main-shape-gate short-circuit tests project,type-check && tsc -p tsconfig.test.json masks diagnostics.normalize-list-view.pageResidual-8429.test.tsmust be converted at the pin bump, not repaired: it is single-leg BY CONSTRUCTION and red onmaintoday. Dedupe words:pageResidual 8429 pin ViewType narrowed,page residual conversion pin bump.object-validation-engine.test.tsexpression-wire drift —{ dialect, ast }withoutsourceno longer satisfies the spec's expression union onmain. Unrelated file, unrelated mechanism. Dedupe words:object-validation-engine dialect source expression wire,spec main expression requires source.LIST_VIEW_KINDSand the undrawable table are still two separate literals a reader must hold together; the partition is enforced, so this is ergonomics, and the carrier that would touch it is the pin-bump PR.🤖 Generated with Claude Code
https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
Generated by Claude Code
Generated by Claude Code