fix(types): content-channel refusals name the parser tier's not-a-container warning (objectui#10928) - #10956
Conversation
…tainer warning (objectui#10928) The family-D / E3 content-channel tombstone messages, their `.describe()` text, the two builders that compose them (`neitherContentChannelGuidance` and ai.zod.ts's `neitherChannel`) and the matching TypeScript docblocks said an authored value rendered NOTHING with "no error, no warning, no element". The render path is right, but the parser tier's `validateTree` warns `not-a-container` for a child list in either key on a registration that declares no `children` input. Only that clause changes, to the wording the objectui#10907 changesets settled on; every other byte of each string stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…ectui#10928) The shared builder, one arm per message source (a literal, the shared `neitherContentChannelGuidance`, ai.zod.ts's `neitherChannel`) read through its refusal, and the population of family-D refusals all carry the clause that names the parser tier's `not-a-container` warning. `@object-ui/types` patch changeset for the published message text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
|
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: Inputs read: card #10928 (body and all three comments: triage ① Derived judgmentsAccept set: no change. Public surface: wording only. Each item named, right or wrong:
② Semver level
③ Boundary flagsDeviations (6), each answered:
Open question: add a live-registry behavioural pin in Out-of-scope findings (2), filed as one card objectui#10959. Verified: open, label Check-runs on the head, read at 2026-09-28T11:04Z right before this record was written: 43 total, all completed; 40 success, 3 skipped by design (the two coverage lanes and dependabot), 0 failures, 0 in progress. Type Check, all eight test shards and their Implemented-by: VERDICT: PASS |
Fixes #10928
Clause-②: no — message and
.describe()text only; every accept or refuse verdict stays byte-identical (auxiliary face).Implemented by the
os-devdispatched from thedomain:specseat, sessionhttps://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm(mode:subagent). Base5c94589f0, head5833df79f.What changed
Only the false clause changes. It changes on every refusal of a node whose renderer reads NEITHER content channel (family D and the E3 residual, objectui#9256). "no error, no warning, no element" now reads:
This is the wording the objectui#10907 changesets settled on. Every site is in the past tense, so the clause is too. Every other byte of each string is unchanged; the proof is below.
.describe()text are one string. There are 131 literal sites in the 12*.zod.tsfiles, plus the two builders that compose the rest:neitherContentChannelGuidanceinzod/tombstone.zod.ts(13 consts, 26 arms);ai.zod.ts's localneitherChannel(6 arms).body?: never/children?: neverdocblocks in the 12 matchingpackages/types/src/*.tsfiles. They ship in the emitted.d.ts.git diff -U0, filtered).packages/types/src/__tests__/content-channel-parser-tier-clause-10928.test.ts..changeset/10928-content-channel-parser-tier-clause.md,@object-ui/types: patch.The parser tier, measured before the clause was written
Instrument. A one-shot probe, deleted and not committed. It builds the manifest from the live registry the way
container-declaration-ratchet.test.tsxdoes: every KNOWN key, throughmanifestFromConfigs.components,fieldsand 18 of the 19plugin-*packages (all butplugin-designer) are imported. It then runsvalidateTreeonce per leg.Population. Every registry key, bare and namespaced, of the 81 node types whose refusal says "reads NEITHER content channel". That is 177 keys: 164 real registrations and 13 lazy stubs that have no inputs yet. None of the 177 declares a
childreninput.children: [node]not-a-container(warning)body: [node]orbody: nodenot-a-container(warning), fromcheckRetiredBodyDialectover the same containment predicatebody: 'hi'(a scalar)unknown-prop(warning)Controls.
div,ui:div,spanandui:spandeclarechildren. On them, addingchildrenadds nothing, and adding abodylist addsunknown-prop.Reading. The dispatch assumed that a node "with
body(notchildren)" stays silent. It does not: abodychild list draws the samenot-a-container. So the settled clause is true for both members, which share one string. A scalarbodydrawsunknown-propinstead; see the Acceptance notes.Census, base
5c94589f0vs head5833df79fThe card's 126 and 139 come from a line-bound grep. The same clause also sits on 25 sites where a line break or a
'…' + '…'join falls inside it: 15 zod (the shared builder among them) and 10 TS docblocks. So the count below joins those first.NEITHER content channel, base / headqqzz_absent_token_9999reads 0 in every cell.HeaderBarSchema:title,logo,nav,left,center,right,stickyandheight.unknown-prop, notnot-a-container.dist, afterpnpm --filter @object-ui/types buildat head, joined the same way:.d.ts: old 0, new 149.HeaderBarSchemastrings, twice (zod/navigation.zod.jsandzod/index.zod.js).The command, exactly as run. The enumeration and the read both come from REF, and
__tests__is excluded. JSDoc continuations and'…' + '…'joins are removed, and an escaped quote is unescaped.Verdicts unchanged, and every other byte of every message
Instrument. A one-shot probe, deleted and not committed. It records 777 keyed entries, taken once at base and once at head:
{}, achildrenlist, abodylist, a scalarbody, a scalarchildren, and both keys;.describe()text of every direct shape member of 196 object schemas;safeValidateSchemaover 85 node types, each over 4 legs: none,children, abodylist and a scalarbody.Result.
HeaderBarSchemarefusals.dist,safeValidateSchema({ type: 'accordion', children: [...] })answersinvalid_typeatchildren, and its message carries the new clause.Reverse verification (ablation), after the fix was committed
Instrument. objectstack's
scripts/ablation-replace.mjs, in WRAP mode. The anchor must hit the declared number of times, and the on-disk blob must move. The restore is proven by blob == HEAD and an emptygit diff HEAD. Each leg puts the old clause back in one message source and runs the new pin.8ff6af3b9(the base blob)object-grid× 2, the populationaccordion's two literal arms751bf2d6faccordion× 2, the populationai.zod.ts'sneitherChannel, back to its base text1fd44f9e0(the base blob)ai-form-assist× 2, the populationda56e22f5,f7d88b6c5,a40e67ff6) with an emptygit diff HEAD.../zod/*source directly, so no build sits between a mutation and the run.The pin, and why it holds the prose rather than the parser
@object-ui/typesdeclares no workspace dependency, and its tests import none. So@object-ui/sdui-parsercannot be imported from here without declaring it (check:phantom-deps). The claim's file surface also put the pin underpackages/types/src/__tests__/.What the pin holds:
accordion), the shared builder (object-grid) and the local builder (ai-form-assist). Each answers codeinvalid_typeat the key's own path, and its message carries the clause.The parser behaviour the clause describes stays pinned where the parser and the registry live, and the new file's header names those pins:
containment-declared-slot-9910.test.tsandbody-dialect-6771.test.ts, inpackages/sdui-parser;container-declaration-ratchet.test.tsx, inpackages/components. It holds declared-slot ⇔ rendered-list over the live registry.Whether to add a live-registry behavioural pin in
packages/componentsas well is left as an open question for the seat.Gates, head
5833df79fThe gate list was derived by hand from objectui's root
package.jsonand.github/workflows/, against this diff.pnpm --filter @object-ui/types build(tsc, vite, dist completeness)pnpm --filter @object-ui/types type-check(tsc, examples, tests)tsc -p tsconfig.test.json --listFileslists the new pinpnpm --filter @object-ui/types lintno-explicit-any, none on the new filepnpm exec vitest run packages/types/check-changeset-presence·changeset:check·check-changeset-overwritecheck:pending-changeset-literals·check:control-bytes·check:new-line-citations(0 new)check:handler-key-reads·type-check:coverage·lint:coverage·check:test-path-roots·check:unreferenced-sources·check:phantom-deps·check:published-tsconfig-exclude·check:esm-specifiers·check:spec-symbols·check:component-surface-parity·check:doc-typescheck:changeset-claims--testover the diff's pathsAGENTS.md: exit 3)check:readme-exports. It judges README self-imports against every package'sdist/*.d.ts. This tree built only@object-ui/types, so the gate printed "could not be judged … runpnpm buildfirst" and exited 1. That is a prerequisite, not a finding. This diff touches no README and no export.pnpm testbeyondpackages/types, thedom/dom-heavyprojects, E2E andtest:dist.Serial constraints
zod/complex.zod.ts, in theGlobalFilterSchemaJSDoc.git merge-treeof this head against its head1daf7edf7is clean.mainmoved two commits since the base, to06a96e948. Neither touchespackages/typesorpackages/sdui-parser, andgit merge-treeagainst it is clean.Acceptance notes: out of scope, not changed here
HeaderBarSchema's nine non-channel refusals (zod/navigation.zod.ts).title,logo,nav,left,center,right,stickyandheight.variantsays "no error, no warning, no class".unknown-prop(measured, same probe).safeValidateSchema({ type: 'header-bar', title: 'T' })hands back the "no warning" message (measured on the builtdist).unknown-prop. So it is reported to the seat, not swept here.body?: neverdocblocks onbox,span,container,flex,stack,grid,scroll-area,formandtoggle(inlayout.tsandform.ts).body"rendered an EMPTY element with no error and no warning".children, and abodylist on it drawsunknown-prop(measured on every bare andui:key).bodyon a reads-neither node drawsunknown-prop, notnot-a-container. The clause speaks of an authored content value, which in both channels is a child list or a node. The scalar case is not written into it.Generated by Claude Code