fix(components,types): eight renderers stop reading undeclared node keys; input-otp and loading read their declared spellings (objectui#11347) - #11360
Conversation
… keys; loading and input-otp read their declared spellings (objectui#11347)
The display, feedback and form units of objectui#11347, the components
preparation card for objectui#8347's BaseSchema index-signature removal.
Each key was measured on its own: the installed @objectstack/spec (17.5.0)
has no row for `list`, `loading` or `input-otp`, and no catalog entry,
example, docs page, package README or objectstack-shipped document authors
any of the three read keys.
- list: the `title` heading read is retired (rule 3: no spec row, no
producer). Its registration input goes with it.
- loading: the undeclared `text` read moves to the declared
`LoadingSchema.label` ("Loading text/message" on both faces), which was
inert. One spelling, no alias.
- input-otp: the undeclared `maxLength` read moves to the declared
`InputOTPSchema.length`, the spelling all five catalog entries and the
docs page author. Measured through the real SchemaRenderer, `length: 4`
and `length: 8` drew six slots before and draw four and eight now.
The body/children tombstone texts on the zod mirror and the matching
docblocks are brought in line with what the renderers now read.
Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
…ive` (objectui#11347) The navigation unit of objectui#11347. `active` was read off a `schema` typed `BaseSchema` itself: no type declares it for `sidebar-menu-button`, the installed @objectstack/spec has no row for the node, and no catalog entry, example, docs page or README authors it (the five catalog sidebars author none). With no spec row and no producer, the read is retired along with its registration input; `isActive` is no longer driven by the node. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…reading undeclared keys (objectui#11347) The overlay unit of objectui#11347. The installed @objectstack/spec has no row for any of these four nodes, and no catalog entry, example, docs page, README or objectstack-shipped document authors any of the keys, so each named read is retired (rule 3): - drawer: `shouldScaleBackground`, `footer` and `showClose`. The drawer draws no footer and no Close button from the node any more. - dropdown-menu, popover, sheet: `modal`. Their registration inputs (and sheet's `modal` seed) go with them. Measured through the real SchemaRenderer: an authored `modal` or `shouldScaleBackground` still reaches the Radix / vaul root through the prop spread, which the strict authoring face owns; the named reads were redundant on that path. Collateral, each forced by a verdict here: - `node-slot-numeric-falsy.test.tsx` loses its `drawer footer` row (the slot no longer exists). - `CRUDDialogSchema.showClose`'s docblock and its pin in `layout-default-jsdoc-7361.test.ts` named the drawer's look-alike read as live; both now record it retired. - the pending objectui#8318 changeset gets a dated correction for the same sentence; its frontmatter is unchanged. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…objectui#11347) 64 of the 94 test errors objectui#8347's P1 mutation raises in this package are literals checked against `BaseSchema` (the `renderComponent` parameter, `SchemaRenderer`'s `schema`, or a `SchemaNode` child slot) that author keys their concrete node type declares. Each is now typed as that node through a small typed identity per file (`actionBar`, `textNode`, `selectNode`, ...), which keeps excess-property checking against the concrete type rather than switching it off. The button variant / size and input type tables are typed with the declared unions, which the concrete type now demands. Left as they are, on purpose: - 29 literals author a node no `@object-ui/types` declaration covers: the public blocks' `properties` bag (`element:text`, `element:text_input`, `element:record_picker`), `page:tabs`' `items`, `action:button`'s executor keys, the page kinds' `kind`, the legacy `props` alias bag and a test-registered probe type. Handed back on objectui#11347 rather than cast or widened. - `label`'s retired `content` spelling is a deliberate probe; it is objectui#8347's to flip. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…derers read (objectui#11347) The input-otp page's schema block shows `length` as the optional slot count it is (default 6) and says the renderer now reads it; the loading page gains the declared `label`. Plus the changeset for objectui#11347. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: Rendered 2026-10-01T08:46Z from card #11347 (body and all three comments), PR #11360 (body, the 32-file list, the diff against ① Derived judgmentsSpec row, all eight node types — RIGHT. At Producer census, re-grepped at
The two read moves — RIGHT. Each honours a declared key, and no fallback reads the old spelling (AGENTS.md §5 #0.1).
The retirements — what the renderer now draws for an authored value.
Published-surface narrowing — judged on all of it, not on two rows. Seven
Pins — RIGHT, not vacuous.
Docs — RIGHT for the three named faces. Tests — no widening. The 23 typed-identity helpers are monomorphic, each annotated with one concrete node type and returning its argument; the diff adds no ② Semver level
③ Boundary flags
Remedy for the one FAIL item (small; nothing else need move). On the same branch: (a) correct the producer under AGENTS.md #0.1 — drop Implemented-by: VERDICT: FAIL |
…a.title (objectui#11347, round 2) The at-tier record on PR #11360 found a producer the census missed: the published `skills/objectui` expressions guide authors `{ "type": "list", "title": "Team", ... }` and says `title` is read off the node. The seat ruled under the card's rule 2, by 未声明键随实现, 文档随之: `list` has no row in the installed @objectstack/spec, the renderer reads `title` and the registration publishes it, so the key is declared rather than retired. - `list` draws `schema.title` as its heading again, and the registration's `title` input returns. - `ListSchema.title?: string` is declared on the TypeScript face and on the zod mirror. A non-string `title` is now refused by name where `.passthrough()` used to keep it unjudged. Both faces gain the key together, so no zod-mirror-parity row opens. - The list docs page documents `title`. The refusal messages and docblocks no longer call it retired. - `list-title-declared-11347.test.tsx` pins it on three readers: the guide's literal drawn through the real SchemaRenderer (with a no-title control), the zod mirror, and the compiler (`@ts-expect-error` on a numeric title). - The changeset names the guide as `list.title`'s producer. It also stops claiming that `dropdown-menu` lost a `modal` input it never published. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
… a fresh literal (objectui#11347) `satisfies` keeps a literal fresh, so under objectui#8347's P1 mutation the control's literal was still checked against `SchemaRenderer`'s `BaseSchema` and `title` read as an excess key. A typed const carries the declaration. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…rrection to its tree (objectui#11347) The `changeset-claim-re-read` comment flagged `renderers/overlay/drawer.tsx:38` in `.changeset/8318-jsdoc-default-tag-corrections.md`, because this branch rewrites that line. The flagged address is in the original bullet, inside the append-only prefix, so it stays byte-identical. The appended correction now binds the same address, in its own sentence, to `33f4a1980` (the commit that added this changeset), where line 38 is the `drawer` registration's `showClose` read. It also names that read by what it is, so the published CHANGELOG line is anchored. The first 2879 bytes still `cmp` equal to origin/main. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
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
Size Limits
|
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: Delta record, round 2, rendered 2026-10-01T09:49Z. It supersedes record ① Derived judgments1. The round-1 FAIL item is closed; docs and snippets agree with 2. The declaration is right on both faces, matches the registration's published type, and is not a declaration against the spec. 3. Every round-2 changed line, by commit.
4. The 5. The append-only prefix still holds. 6. Pending changesets naming ② Semver level
③ Boundary flags
Remedy for the one FAIL item (one word and one sentence; nothing else on this head need move): in Implemented-by: VERDICT: FAIL |
…a member (objectui#11347, round 3)
Round 2 declared `ListSchema.title` on the shipped `.d.ts` and on the
mirror's `.shape`, and the mirror now refuses a non-string `title` by
name. objectui#7722 graded that class on this same schema `minor` ("the
published face gains ... members"), so `patch` was wrong. The Types
section now says so, in the same words 7722 uses.
Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: Delta record, round 3, rendered 2026-10-01T10:06Z. It supersedes record ① Derived judgments1. The delta is exactly one file. 2. The level and the new sentence are right, and every other sentence in the file still holds. Line 3 now reads 3. The PR body is unchanged and states no types level. Its ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Takes c82ff39 (objectui#11347, PR #11360), which touches none of this branch's files. Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
Refs #11347
Clause-②: yes (narrowing)
This PR is the
@object-ui/componentspreparation unit of objectui#8347. objectui#8347 is the parent: it removesBaseSchema's index signature, and it lands last. This PR settles the twelve un-cast reads in eight renderers that objectui#8347's P1 mutation reddens, measuring each key on its own:list.title(two reads), is kept and declared.The Clause-② arm covers both directions:
inputsand the keys the renderers honour.ListSchema.titledeclaration widens the TypeScript face. On the zod mirror it newly refuses a non-stringtitle.64 of the package's 94 test errors are retyped to their concrete node types. The other 30 stay:
Refs.Round 2 answers the at-tier record (FAIL, one item) and the seat's ruling on the card. My census missed a producer of
list.title: the publishedskills/objectuiexpressions guide (guides/schema-expressions.md, underos:check). It authors{ "type": "list", "title": "Team", ... }, and it saystitleis read off the node. Under the card's rule 2 the seat ruled to keep the read and declare the key, by 「未声明键随实现,文档随之」.listhas no spec row, the renderer readstitle, and the registration publishes it. Nothing underskills/changes.This body was refreshed at 2026-10-01T09:41Z. It answers every comment posted on this PR since the first body:
5927953448, in this section;changeset-claim-re-readcomment5927656616, under Files outside the claimed surface and Gates;5927699774,5928383897,5928726380). They report PASS and ask nothing.Per-key verdicts
Spec row. The installed
@objectstack/specis 17.5.0. ItsComponentPropsMaphas 55 entries, and none of them is any of the eight node types. A grep of the whole installed package forshouldScaleBackground,showClose,input-otpanddropdown-menureturns zero hits. The controlmodalCloseButtonhits three files, so the grep was reading.Producers. The census covered:
examples/schema-catalog, every entry of these eight node types, key by key;examples/*andcontent/docs/**;packages/types/examples;skills/;origin/main2742e5370, its examples and packages.One producer exists: the
skills/objectuiexpressions guide authorstitleon alistnode. This was missed in round 1 and caught by the at-tier record. None of the producers authors any of the other eight read keys. The objectstack hits fortype: 'list'andtype: 'drawer'are page types, flow variables and a form-view type, not component nodes. The registrations name these keys in their owninputs/defaultProps. None of the eight types is inPUBLIC_BLOCKS, so those names never reach the publishedsdui.manifest.json.Runtime. Each key was rendered through the real
SchemaRendererand registry, before and after, in a scratch probe that is not committed.SchemaRenderer, before then afterlisttitle(2)ListSchema.title?: stringon both facesloadingtext(2)LoadingSchema.label;textretired with no aliastext: a message, then none.label: none, then a messageinput-otpmaxLength(TS2551)InputOTPSchema.lengthlength: 4: 6 slots, then 4.length: 8: 6, then 8.maxLength: 4: 4, then 6sidebar-menu-buttonactive(read offBaseSchemaitself; no node type exists)data-active="true", then"false"drawerfooterdrawershowClosedrawershouldScaleBackgroundfalseboth before and after, through the prop spreaddropdown-menu,popover,sheetmodal(1 each)list.titleis declared on both faces at once, so nozod-mirror-parityrow opens. A non-stringtitlethat.passthrough()used to keep unjudged is now refused attitle. That follows thewrapperClassprecedent from objectui#7722.H3: the near spellings. For
input-otp, all five catalog entries and the docs page spell the keylength. The type declareslengthon both faces with@default 6, so the read moved and the declaration stayed. Before this change, the published 4-digit and 8-digit examples drew six slots. Forloading,LoadingSchema.labelwas declared but inert while an undeclaredtextwas read. The seat accepted moving the read tolabelover a plain retirement.Inputs. Where a registration published a retired key as an input, the input goes too:
activeonsidebar-menu-button;drawerkeys;modalonpopoverand onsheet.dropdown-menunever publishedmodal.sheet'sdefaultPropsalso loses itsmodalseed.list'stitleinput stays.An independent instrument.
pnpm check:component-surface-parity(objectui#4631, report-only) was read at base770cc5ba4in a detached control worktree, and again at the head:interface-missing-keyfalls from 59 to 49. The ten rows that go are exactly these keys;list.titlegoes because it is declared now.interface-extra-keyfalls from 121 to 120, becauseinput-otp.lengthis now read.P1 measurement (H1)
The un-chained recipe is
tsc --noEmit, thentsc -p tsconfig.test.json. It ran with objectui#8347's mutation applied throughablation-replace, and with@object-ui/typesrebuilt. The restore was proven after every run:git diff HEADonbase.tsis empty;770cc5ba4, mutated61be40730, mutatedlistreads compile againstListSchema.titleThe tests (H4)
64 literals are now typed as the nodes they are, each through a small typed identity per file (
actionBar,textNode,selectNode, ...). Excess-property checking therefore runs against the concrete type, not againstBaseSchema. The helpers are not generic, so they cannot widen anything. The button variant and size tables and the inputinputTypetable are typed with the declared unions.list-title-declared-11347.test.tsxpinslist.titleon three readers:SchemaRenderer, drawsTeamas the heading. A list with no title draws none.title.@ts-expect-errorsits on a numerictitle, andtsc -p tsconfig.test.jsonreads it.The 30 left, by file:
form-renderers.test.tsx, wherelabelauthors its retiredcontentspelling on purpose.propertiesbag ofelement:text,element:text_inputandelement:record_picker:page-variables.test.tsx(5, one of themdataSource)text-input.test.tsx(5)text-input-description-association.test.tsx(1)required-marker-markup.test.tsx(1)record-picker-label-association.test.tsx(1)itemsonpage:tabs:page-tabs-builtin-label-i18n-4645.test.tsx(2) andchrome-i18n-no-provider-fallback.test.tsx(1).action:button:action-params-templates-7867.test.tsx(5).homewithkind:html-page-lazy-blocks.test.tsx(1) andreact-page-scope.test.tsx(1).propsalias:alias-precedence-cross-channel.test.tsx(4).properties:degenerate-config-bag.test.tsx(2).Files outside the claimed surface
Each was forced by a verdict, and the at-tier record judged each one.
packages/types/src/crud.ts: theCRUDDialogSchema.showClosedocblock no longer names the drawer's retired look-alike read as live.packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts: the test now pins that retirement, with a firing control.packages/components/src/renderers/__tests__/node-slot-numeric-falsy.test.tsx: thedrawer footerrow is removed..changeset/8318-jsdoc-default-tag-corrections.md: a dated correction is appended, and the frontmatter is byte-identical.changeset-claim-re-readflaggedrenderers/overlay/drawer.tsx:38in this changeset. That address sits in the original bullet, inside the append-only prefix.33f4a1980, the commit that added this changeset. I read that tree through a REST contents read, and line 38 there is thedrawerregistration'sshowCloseread. The correction also names that read by what it is.origin/main(cmp -n 2879exits 0). The gate still reports the prefix occurrence, which is report-only.The branch merges cleanly with
origin/main(git merge-treeexit 0).Gates
The PR head is
57c757ffa. It differs from61be40730by one.changeset/*.mdfile and touches no TypeScript program.61be40730:pnpm --filter '@object-ui/components^...' buildexits 0.57c757ffa: the scopedturbo run buildthatcheck:skill-examples --build-filternames succeeds on all 29 tasks.61be40730,pnpm --filter @object-ui/types type-checkandpnpm --filter @object-ui/components type-checkboth exit 0, with 0error TSlines. That run also consumes the new pin's@ts-expect-error.61be40730:vitest run packages/types/+ the new pin passed 310 files and 7822 tests.57c757ffa:vitest run packages/components/gave 349 files passed and 1 skipped (350); 3481 tests passed and 24 skipped; the lock reported VERDICT command-exit 0.bba957503:examples/schema-catalog/+widget-dom-leak-sweep+registry-inputs-spec-paritypassed 38 files and 2677 tests.check:skill-examplesat57c757ffaexits 0 after the scoped build: 18 of 18 ts fences are judged with 0 failed, 70 json fences parse with 0 failed, and the gate reports "Every marked skill example holds up against the built types."check:new-line-citations,check:control-bytes,check:changeset-claims(report-only; see the note below),check:pending-changeset-literals,check:doc-types,check:doc-fences,check:doc-example-ids,check:component-surface-parity,check:test-path-roots,check:vi-mock-specifiers,check:phantom-deps,check:self-import,check:prompt-keys,check:handler-key-reads,check:skills-paths, changeset presence and no-major.check-governed-queue-guard --testreads NOT GOVERNED.list.tsx,drawer.tsx,data-display.ts,data-display.zod.ts). There are 13 of them: 5926, 6349 batch 1, 6881, 6940, 6951, 6972, 7352, 7694, 7722, 8331, 8478, 9256-list, and the 5350 table one. 8318 is flagged separately, by its line address. None is made false by this branch except 8318, which carries the correction.listrenderer readswrapperClassand that the key was undeclared; both still hold.body/childrenread files underListSchema; that still holds..describe()strings gain no line address..ts/.tsxfiles gave 0 errors and 73 warnings, against the same 0 and 73 for those files at base. Type-aware linting is off and no custom rule reads the disk, so the narrowed run speaks for the untouched files too. Round 2 adds one test file, which CI'sLintreads.Acceptance notes (observed, not filed)
SchemaRendererstill spreads unread node keys onto the component. An authoredmodalorshouldScaleBackgroundtherefore still reaches the Radix or vaul root. An authoredtitleon a list also lands as a DOMtitleattribute on theol/ul, andtext/labelonloadingland on its wrapper. These belong to the strict face (objectui#8345, objectui#11070) and to the bare-spread family.DialogSchemaandContextMenuSchemadeclaremodal, andSheetSchemaandDialogSchemadeclarefooter. Rule 3 retires the same keys on these four nodes, because no producer authors them there.LoadingSchema.size. The declared union is'sm' | 'default' | 'lg', and the renderer branches onsm/md/lg/xl. NOT MEASURED, carried for the next touch.input-otpseparatordraws nothing. It is filed as objectui#11365.Session:
https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERTGenerated by Claude Code