feat(types)!: refuse the content channels on the nineteen public-block arms and metric-card (objectui#9256) - #11020
Conversation
…k arms and metric-card (objectui#9256) Each renderer reads neither `children` nor `body` off the node, so both keys are refused by name on the zod arm, each kept a member: sixteen arms in public-blocks.zod.ts and object-metric / object-master-detail-form in objectql.zod.ts. record:alert refuses `children` only (its `body` is the message text). page:tabs / page:accordion refuse the node's own `children` while each item's `children` stays live. metric-card: `children?: never` (and `body` restated) on DashboardWidgetSlotComponentSchema, and both members on the private slot arm; the refusal surfaces inside the widget slot's invalid_union. Text repairs: the stale chatbot comment and test name in content-channel-family-d-9256.test.ts, and CodeEditorSchema.children's docblock (it omitted onChange). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
|
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. Which half objected:
📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Isolated adversarial at-tier review of PR objectui#11020 (card objectui#9256, family D, the public-block slice after the re-measure; ① Derived judgments1. The twenty narrowed types — renderer readership, re-derived.
⇒ no row is a FAIL. 2. The exceptions. 3. Producers. 4. Shape. (a) No TypeScript declaration under 5. Refusal messages, all read. Each of the eighteen 6. Pins and ablations. The 230-test pin covers all twenty: 18 rows × 2 channels × 6 assertions (216) + the population guard + 7. 8. Beyond-brief text edits. The family-D pin's header and rows now say 9. Downstream. Nothing under 10. CI and commits. Head ② Semver level
③ Boundary flags
Defects, for the record. One, text only: the duplicated clause in Implemented-by: VERDICT: PASS Generated by Claude Code |
…osure Picks up PR #11020 (objectui#9256), whose metric-card refusal texts the merge queue ran this branch's closure walk against. Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm Co-authored-by: Claude <noreply@anthropic.com>
… the next sentence (objectui#10981) The walk required "the parser tier's" within 80 characters after every "no … error or warning" phrasing. objectui#9256's `metric-card` refusal (PR #11020) names the parser tier correctly, but in the sentence after, to record why it stays silent in a widget slot. So the merge queue went red on a true text. The bound is now the end of the sentence after the phrasing. `MAX_WINDOW` only guards a text with no sentence end. Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm Co-authored-by: Claude <noreply@anthropic.com>
Fixes #9256
Clause-②: yes
Clause-②
yes, as the claim declared: nineteen published zod arms and one TypeScript face plus its private zod twin stop accepting an authoredchildren. Each renderer reads neither content channel off the node, so the key rendered nothing; it is now refused by name. A published accept set narrows, so a contract review is owed before landing.This is the slice ACCEPT
5873860373, release5874185838and correction5874664390on objectui#9256 carried: the nineteen public-block arms,metric-card, and two text repairs. It saysFixesbecause a re-run of the card's instrument on this branch finds no OPEN family-D registration left (see "WhyFixes" below).What changed
childrentombstone andbodyrestated with the neither-channel guidance, both kept MEMBERS (retirementTombstone, oneneitherContentChannelGuidancestring per arm):zod/public-blocks.zod.ts:page:header,page:tabs,page:accordion,record:details,record:highlights,record:related_list,record:path,record:activity,record:discussion,record:history,record:quick_actions,record:reference_rail,element:text,element:number,element:button,element:divider;zod/objectql.zod.ts:object-metric,object-master-detail-form.record:alert(the nineteenth arm):childrenonly, with its own string. Its renderer reads a key namedbodyas the message TEXT, so the builder's sentence "no renderer read consumesbodyorchildren" would be false there;bodyis left toBaseSchema.page:tabs/page:accordion: the NODE's channels only. Each renders thechildrenof every ITEM in itsitemsbag member; that item-level key is the spec row's and stays live (pinned).metric-card:children?: neveron the TypeScript faceDashboardWidgetSlotComponentSchema, withbody?: neverrestated beside it (it was alreadyneverthroughBaseSchema; the restatement carries the docblock that says what the card renders), and both members on the private slot arm inzod/complex.zod.ts. Its message is its own string, not the builder's: the builder says the parser tier'snot-a-containerwarning noticed the key, and in a widget slot it does not, because that tier walkschildren, neverwidgets.packages/types(source or built.d.ts) carries any of the nineteen literals.@object-ui/plugin-form'sMasterDetailFormSchemais the type ofMasterDetailForm'sschemaprop, has no index signature and nochildrenmember, and is the renderer's post-hoist reading, not an authoring face.content-channel-family-d-9256.test.ts(it saidbodyis ACCEPTED onchatbot-enhanced/chatbot-floating), the same stale sentence in that file's header, and the name of its LIVE CONTROL test, which said the family "still acceptsbody" while its body asserts the refusal. And theCodeEditorSchema.childrendocblock inform.ts, which said six keys go to Monaco "and nothing else": it now namesonChangetoo. Text only.public-blocks.zod.tsmodule docblock (a new "content channels" section; the flat-key paragraph no longer sayschildrenis judged by the base type on every arm), the twoobjectql.zod.tsarm docblocks, the slot arm's docblock, thezod/README.mdsections, and the parity census's exclusion reasons for these arms inzod-mirror-parity.test.ts.packages/types/src/__tests__/content-channel-public-blocks-9256.test.ts(230 tests) and one changeset.changeset/9256-public-blocks-content-channels.md:@object-ui/typesminorwith an explicit BREAKING note and a migration line, the family's spelling under the no-major rule.Re-derivation on
origin/main5d689c3f6(before any edit)Instrument. The re-measure's compiler-API walk, re-run: TypeScript 6.0.3, one program per
tsconfig.json(44 programs: every workspace package,apps/console, the examples), 2022 non-test source files, on a BUILT tree (turbo run build, 42 / 42), 0 unresolved-module diagnostics. It files every.body/.childrenread (property access, string element access, destructuring) under its receiver's declared type with the enclosing function, and this run also records every whole-node spread (...schema,...(schema as any),...bound,...node). Readings: 237 registration sites, 525 channel reads, 4children || bodypairs, 27 whole-node spreads. The set of channel reads (file, key, enclosing function) is identical to the re-measure's run on1ac8cb627.children/bodyrecord:details,record:highlights,record:related_list,record:path,record:activity,record:history,record:quick_actions,record:reference_rail@object-ui/plugin-detail,recordnamespace,skipFallback→ the matchingRecord…Rendererrecord:activitythrough aread(key)helper called with literal feed keys only)record:discussionRecordChatterRenderer(shared withrecord:chatter)position,width,collapsible,defaultCollapsedandfeedonlyrecord:alertRecordAlertRendererchildren;bodyis read as the message TEXTreadPropsmerges the node withproperties; the result is read forseverity,title,body,icon,action,dismissible,dismissKey,visiblepage:header@object-ui/components,pagenamespace →PageHeaderRenderer(any-typed)page:tabs,page:accordionPageTabsRenderer/PageAccordionRenderer(any-typed)item.children(the tab strip's count badge also walks item descendants to count, and renders nothing)element:text,element:number,element:button,element:dividerelementnamespace →Element…Renderer(any-typed)readProps:propsandproperties) andclassName;element:numberalso itsdataSourcebinding. The onebodyhit in that file is theVARIANT_CLASS.bodylookupobject-metricplugin-dashboard:object-metric→ObjectMetricBlock→ObjectMetricWidgetobject-master-detail-formplugin-form:object-master-detail-form→MasterDetailFormRenderer→MasterDetailFormobject-formnode key by key)metric-cardplugin-dashboard:metric-card→MetricCard;DashboardRendererhands a widget toSchemaRendereras its own keysCardas DOM attributes)SchemaRendererdestructureschildrenandbodyout of the props bag it spreads, so a channel reaches a component only throughschema.*or a whole-node spread, and the two spreads above end in named-key readers. No registration of the twenty declares achildrenslot input (objectui#9910), so the parser tier'snot-a-containerwarning fires for each of them where that tier walks (it does not walkwidgets).Faces before the change (built dist,
AnyComponentSchemaparsed behaviourally): all nineteen arms parse{ type }and{ type, children }green and refuse{ type, body }withBaseSchema's "Did you meanbody→children?" message;metric-cardin a widget slot parses withchildrenand type-checks with it.Producers, with lit controls in the same pass:
pnpm census:body-dialect --keysover the twenty keys plusdiv,card,page,page:card, whole repository (9144 files): no node authorschildrenon any of the twenty;bodyappears only asrecord:alert's text prop, three times, all in tests. Controls:childrenondiv179,card183,page54. The same census over the sibling objectstack checkout (9497 files; a stale local checkout, so a supplementary reading): nochildrenorbodyon any of the twenty; its only lit control ispagewith 3children. Nothing needed migrating.How a
metric-cardrefusal surfaces (measured before choosing the pin)DashboardComponentSchema.widgetsisz.union([slot arm, strict DashboardWidgetSchema]), and the strict schema'stypeenum also admitsmetric-card. A widget the slot arm refuses therefore falls through to the strict schema, which refuses the same key asunrecognized_keys. The author gets ONEinvalid_unionatwidgets.0("Invalid input"), and the slot arm's by-name message sits in itserrors, at the arm-relative pathchildren.objectui validateprints it as[arm 1/2], beside[arm 2/2] Unrecognized keys: "value", "children"(measured through the built CLI, before and after). So the pin asserts the refusal INSIDE the union'serrors, plus the strict arm'sunrecognized_keys, rather than as a top-level issue, and the union is not restructured. The TypeScript face refuses the key at the authoring site (@ts-expect-error).Red on base, then green
Predictions were written to a file before any mutation. Each leg went through
ablation-replace.mjsagainst committed HEAD42d3ea57e(anchor count and blob hash proven on disk, restore proven by blob equal to HEAD and an emptygit diff HEAD; tree clean after all four). The pin imports the arms by relative path, so vitest andtscread source and no leg needed a rebuild.tsc -p packages/types/tsconfig.test.jsonRecordDetailsBlockSchema'schildrenmemberrecord:details.childrenrows and the nested-in-pagecase; the member row stays green, as predicted, becauseBaseSchemadeclares the keyDashboardWidgetSlotComponentSchema'schildren?: nevermetric-cardpinchildrenmembermetric-cardchildrencaseRecordAlertBlockSchema'schildrenmemberrecord:alertown-message caseConsumer-side reverse validation. A probe inside
packages/plugin-dashboard, compiled with that package's options, resolves@object-ui/typestopackages/types/dist/complex.d.ts:childrenon aDashboardWidgetSlotComponentSchema,bodyon one, andchildrenon ametric-cardinsideDashboardComponentSchema.widgetsgive exactly 3 × TS2322; the two controls without a channel compile. Probe deleted, tree clean.Gates (HEAD
42d3ea57e; heavy runs through the shared verify lock; exit codes by redirect-then-capture)@object-ui/typestype-check+vitest run packages/types/type-check: every package downstream of@object-ui/typeswith the script, except@object-ui/siteand the repo root (38 packages, three batches)type-check: Done, exit 0vitest runpackages/cli/,packages/sdui-parser/, and the six other test files that parse one of the twenty types through the zod face (consolepublic-contractandregistry-inputs-spec-parity, two app-shell block-config pins,MasterDetailForm.i18nLabels,ObjectTree.schemaTyped-8655)registered-types-validate-ratchet-10859) green and uneditedvitest run scripts/vitest run examples/schema-catalog/vite build, thencheck:sdui-registration-pinscheck:eager-closuremain— see Bundle Analysischeck:control-bytes·check:new-line-citations(0 new) ·changeset:check·check-changeset-presence·check:changeset-claims·check:pending-changeset-literalscheck:component-surface-parity·check:registry-bare-names·check:handler-key-reads·check:spec-symbols·check:readme-exports·check:element-data-source-declaration·check:prompt-keys·check:test-path-roots·pnpm checkcheck:doc-types·check:doc-snippets·check:doc-examples·check:skill-examples·check:doc-fences·check:doc-example-ids--testover the 10 pathsAGENTS.md: exit 3)Bundle Analysis: measured, and the red is
main's. Same box, same install, two console builds that differ only inpackages/types/src(the five source files at5d689c3f6, then at HEAD; the console aliases@object-ui/typesto source): eager closure 3,179,055 vs 3,179,056 gzip bytes, 10,758,424 raw bytes in both, 330 eager chunks in both. The one eager file that differs is the entryindexchunk, same raw bytes, +1 gzip byte: it names the lazytypes-zodchunk by content hash. Both builds are over the 3,179,000-byte ceiling, andmain's ownBundle Analysison5d689c3f6concludedfailure(objectui#10996). The newmetric-cardandobjectql.zod.tsstrings land only in the lazytypes-zodchunk, which is not in the eager-closure report; thepublic-blocks.zod.tsstrings are in no emitted chunk. The TypeScript members emit no JavaScript. No eager import is added.NOT MEASURED, left to CI: the
pnpm testshards beyond the suites above (app-shell, components, core, the plugins and the rest),@object-ui/site's type-check,test:dist, E2E, and the repo-widepnpm lint.Why
FixesThe card's instrument, re-run on this branch after the change: the published faces read from the built
packages/typesdist (the TypeScript.d.tswith the compiler API; the zod mirror behaviourally, with lit controlsdivacceptschildren,divrefusesbody,dialogrefuseschildren, an unknowntypeis refused), joined to the enumerator's claims and to the read-site walk above. The population is unchanged from the re-measure: 189 published literals that are registry keys, same set. Face changes against the re-measure: exactly the twenty in this PR, each now refusingchildrenon every face that carries it.bodyis accepted on no published face of any of the 189. The 68 literals whose face still acceptschildrenare all readers or disposed: 42 html / semantic tags whose factory readsschema.children, 19 typed family A / C readers with achildrenread filed under their declared type, the fourpage:containers with a livechildren || bodyfallback (out of this card), and the three void tags disposed on the card. No OPEN family-D registration remains.Serial constraints
Read at branch time and again before opening:
origin/mainhas not moved since5d689c3f6.git merge-tree --write-treeof this head is clean against each open PR that touches a file here: objectui#11006 (docblocks inbase.ts,data-display.ts,zod/form.zod.ts,zod/objectql.zod.tsatSPEC_EXPORT_OPTIONS_OBJECT_SHAPE; this PR edits none of those sites), objectui#10930 (theGlobalFilterSchemadocblock inzod/complex.zod.ts, below the slot arm this PR edits), and objectui#10990 (zod-mirror-parity.test.tsandzod/README.md, different sites). objectui#10872's flat-props batch on the same arms is unclaimed; by ACCEPT5873860373whichever lands second runs serial behind the other. No rebase, no force-push.Acceptance notes — out of scope, not fixed here
record:alert's flatbodyis still refused withBaseSchema's message, which nameschildrenas the remedy, while the renderer reads that key as its message text. Carrier: objectui#10872's flat-props batch (unchanged by this PR; the newchildrenmessage points atproperties).metric-cardnode inside the legacy{ id, component, layout }envelope still parses withchildren:DashboardWidgetSchema.componentis plainBaseSchema, deliberately (the objectui#8344 note on that member), so it acceptschildrenon any node type, not onlymetric-card. It is not a face that carries themetric-cardliteral, so it is not a row of the card's instrument. Carrier: none.StrictAnyComponentSchemarefuses everymetric-cardwidget that carriesvalue(its derived strict slot arm closes the passthrough whose members are the card's registry inputs). Measured on both sides of this PR: at5d689c3f6the strict slot arm already reportsvalueas unrecognized, and at this head a card carrying onlytypeandvalueis refused. The strict face has no non-test consumer in this repository. Carrier: none.Generated by Claude Code