Skip to content

feat(types)!: refuse the content channels on the nineteen public-block arms and metric-card (objectui#9256) - #11020

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-9256-family-d-public-blocks
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-9256-family-d-public-blocks

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 authored children. 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, release 5874185838 and correction 5874664390 on objectui#9256 carried: the nineteen public-block arms, metric-card, and two text repairs. It says Fixes because a re-run of the card's instrument on this branch finds no OPEN family-D registration left (see "Why Fixes" below).

What changed

  • Eighteen zod-only arms, each given a children tombstone and body restated with the neither-channel guidance, both kept MEMBERS (retirementTombstone, one neitherContentChannelGuidance string per arm):
    • in 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;
    • in zod/objectql.zod.ts: object-metric, object-master-detail-form.
  • record:alert (the nineteenth arm): children only, with its own string. Its renderer reads a key named body as the message TEXT, so the builder's sentence "no renderer read consumes body or children" would be false there; body is left to BaseSchema.
  • page:tabs / page:accordion: the NODE's channels only. Each renders the children of every ITEM in its items bag member; that item-level key is the spec row's and stays live (pinned).
  • metric-card: children?: never on the TypeScript face DashboardWidgetSlotComponentSchema, with body?: never restated beside it (it was already never through BaseSchema; the restatement carries the docblock that says what the card renders), and both members on the private slot arm in zod/complex.zod.ts. Its message is its own string, not the builder's: the builder says the parser tier's not-a-container warning noticed the key, and in a widget slot it does not, because that tier walks children, never widgets.
  • No TypeScript face exists for the nineteen arms, measured: no declaration in packages/types (source or built .d.ts) carries any of the nineteen literals. @object-ui/plugin-form's MasterDetailFormSchema is the type of MasterDetailForm's schema prop, has no index signature and no children member, and is the renderer's post-hoist reading, not an authoring face.
  • Text repairs: the comment above the chatbot rows in content-channel-family-d-9256.test.ts (it said body is ACCEPTED on chatbot-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 accepts body" while its body asserts the refusal. And the CodeEditorSchema.children docblock in form.ts, which said six keys go to Monaco "and nothing else": it now names onChange too. Text only.
  • Docblocks kept true: the public-blocks.zod.ts module docblock (a new "content channels" section; the flat-key paragraph no longer says children is judged by the base type on every arm), the two objectql.zod.ts arm docblocks, the slot arm's docblock, the zod/README.md sections, and the parity census's exclusion reasons for these arms in zod-mirror-parity.test.ts.
  • New pin 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/types minor with an explicit BREAKING note and a migration line, the family's spelling under the no-major rule.

Re-derivation on origin/main 5d689c3f6 (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 / .children read (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, 4 children || body pairs, 27 whole-node spreads. The set of channel reads (file, key, enclosing function) is identical to the re-measure's run on 1ac8cb627.

key registration → renderer reads of the NODE's children / body whole-node spread
record:details, record:highlights, record:related_list, record:path, record:activity, record:history, record:quick_actions, record:reference_rail @object-ui/plugin-detail, record namespace, skipFallback → the matching Record…Renderer none; each reads its named keys (record:activity through a read(key) helper called with literal feed keys only) none
record:discussion same package → RecordChatterRenderer (shared with record:chatter) none yes: the node is copied into the panel config; that config is read for position, width, collapsible, defaultCollapsed and feed only
record:alert same package → RecordAlertRenderer no children; body is read as the message TEXT yes: readProps merges the node with properties; the result is read for severity, title, body, icon, action, dismissible, dismissKey, visible
page:header @object-ui/components, page namespace → PageHeaderRenderer (any-typed) none none
page:tabs, page:accordion same → PageTabsRenderer / PageAccordionRenderer (any-typed) none at node level; each renders item.children (the tab strip's count badge also walks item descendants to count, and renders nothing) none
element:text, element:number, element:button, element:divider same, element namespace → Element…Renderer (any-typed) none: they read the props bag (readProps: props and properties) and className; element:number also its dataSource binding. The one body hit in that file is the VARIANT_CLASS.body lookup none
object-metric plugin-dashboard:object-metric → ObjectMetricBlock → ObjectMetricWidget none (the widget destructures named props) none
object-master-detail-form plugin-form:object-master-detail-form → MasterDetailFormRenderer → MasterDetailForm none (it builds its parent object-form node key by key) none
metric-card plugin-dashboard:metric-card → MetricCard; DashboardRenderer hands a widget to SchemaRenderer as its own keys none (named props; the rest is forwarded to its Card as DOM attributes) none

SchemaRenderer destructures children and body out of the props bag it spreads, so a channel reaches a component only through schema.* or a whole-node spread, and the two spreads above end in named-key readers. No registration of the twenty declares a children slot input (objectui#9910), so the parser tier's not-a-container warning fires for each of them where that tier walks (it does not walk widgets).

Faces before the change (built dist, AnyComponentSchema parsed behaviourally): all nineteen arms parse { type } and { type, children } green and refuse { type, body } with BaseSchema's "Did you mean body → children?" message; metric-card in a widget slot parses with children and type-checks with it.

Producers, with lit controls in the same pass: pnpm census:body-dialect --keys over the twenty keys plus div, card, page, page:card, whole repository (9144 files): no node authors children on any of the twenty; body appears only as record:alert's text prop, three times, all in tests. Controls: children on div 179, card 183, page 54. The same census over the sibling objectstack checkout (9497 files; a stale local checkout, so a supplementary reading): no children or body on any of the twenty; its only lit control is page with 3 children. Nothing needed migrating.

How a metric-card refusal surfaces (measured before choosing the pin)

DashboardComponentSchema.widgets is z.union([slot arm, strict DashboardWidgetSchema]), and the strict schema's type enum also admits metric-card. A widget the slot arm refuses therefore falls through to the strict schema, which refuses the same key as unrecognized_keys. The author gets ONE invalid_union at widgets.0 ("Invalid input"), and the slot arm's by-name message sits in its errors, at the arm-relative path children. objectui validate prints 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's errors, plus the strict arm's unrecognized_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.mjs against committed HEAD 42d3ea57e (anchor count and blob hash proven on disk, restore proven by blob equal to HEAD and an empty git diff HEAD; tree clean after all four). The pin imports the arms by relative path, so vitest and tsc read source and no leg needed a rebuild.

leg vitest (new pin) tsc -p packages/types/tsconfig.test.json
A1: delete RecordDetailsBlockSchema's children member RED 6 failed / 230: the five record:details.children rows and the nested-in-page case; the member row stays green, as predicted, because BaseSchema declares the key GREEN (zod-only arm), as predicted
A2: delete DashboardWidgetSlotComponentSchema's children?: never GREEN 230 / 230, NOT MEASURED by vitest (types are erased) RED: exactly 1 × TS2578 at the metric-card pin
A3: delete the slot arm's children member RED exactly 1: the metric-card children case GREEN, as predicted
A4: delete RecordAlertBlockSchema's children member RED exactly 1: the record:alert own-message case GREEN, as predicted

Consumer-side reverse validation. A probe inside packages/plugin-dashboard, compiled with that package's options, resolves @object-ui/types to packages/types/dist/complex.d.ts: children on a DashboardWidgetSlotComponentSchema, body on one, and children on a metric-card inside DashboardComponentSchema.widgets give 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)

gate result
@object-ui/types type-check + vitest run packages/types/ exit 0; 274 files / 6359 tests
downstream type-check: every package downstream of @object-ui/types with the script, except @object-ui/site and the repo root (38 packages, three batches) 38 × type-check: Done, exit 0
vitest run packages/cli/, packages/sdui-parser/, and the six other test files that parse one of the twenty types through the zod face (console public-contract and registry-inputs-spec-parity, two app-shell block-config pins, MasterDetailForm.i18nLabels, ObjectTree.schemaTyped-8655) 45 files / 820 tests, exit 0; the cli ratchet (registered-types-validate-ratchet-10859) green and unedited
vitest run scripts/ 177 passed + 2 skipped of 179 files / 5350 tests, exit 0
vitest run examples/schema-catalog/ 34 files / 2198 tests, exit 0
console vite build, then check:sdui-registration-pins exit 0
check:eager-closure exit 1, shared with main — see Bundle Analysis
eslint over the 8 touched TS files (eslint's own JSON: 8 entries) 0 errors, 0 findings on an added line; the config enables no type-aware rule, so no untouched file's verdict can move
check:control-bytes · check:new-line-citations (0 new) · changeset:check · check-changeset-presence · check:changeset-claims · check:pending-changeset-literals exit 0 each
check: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 check exit 0 each
check:doc-types · check:doc-snippets · check:doc-examples · check:skill-examples · check:doc-fences · check:doc-example-ids exit 0 each
governed guard --test over the 10 paths NOT GOVERNED (lit control AGENTS.md: exit 3)

Bundle Analysis: measured, and the red is main's. Same box, same install, two console builds that differ only in packages/types/src (the five source files at 5d689c3f6, then at HEAD; the console aliases @object-ui/types to 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 entry index chunk, same raw bytes, +1 gzip byte: it names the lazy types-zod chunk by content hash. Both builds are over the 3,179,000-byte ceiling, and main's own Bundle Analysis on 5d689c3f6 concluded failure (objectui#10996). The new metric-card and objectql.zod.ts strings land only in the lazy types-zod chunk, which is not in the eager-closure report; the public-blocks.zod.ts strings are in no emitted chunk. The TypeScript members emit no JavaScript. No eager import is added.

NOT MEASURED, left to CI: the pnpm test shards 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-wide pnpm lint.

Why Fixes

The card's instrument, re-run on this branch after the change: the published faces read from the built packages/types dist (the TypeScript .d.ts with the compiler API; the zod mirror behaviourally, with lit controls div accepts children, div refuses body, dialog refuses children, an unknown type is 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 refusing children on every face that carries it. body is accepted on no published face of any of the 189. The 68 literals whose face still accepts children are all readers or disposed: 42 html / semantic tags whose factory reads schema.children, 19 typed family A / C readers with a children read filed under their declared type, the four page: containers with a live children || body fallback (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/main has not moved since 5d689c3f6. git merge-tree --write-tree of this head is clean against each open PR that touches a file here: objectui#11006 (docblocks in base.ts, data-display.ts, zod/form.zod.ts, zod/objectql.zod.ts at SPEC_EXPORT_OPTIONS_OBJECT_SHAPE; this PR edits none of those sites), objectui#10930 (the GlobalFilterSchema docblock in zod/complex.zod.ts, below the slot arm this PR edits), and objectui#10990 (zod-mirror-parity.test.ts and zod/README.md, different sites). objectui#10872's flat-props batch on the same arms is unclaimed; by ACCEPT 5873860373 whichever lands second runs serial behind the other. No rebase, no force-push.

Acceptance notes — out of scope, not fixed here

  • record:alert's flat body is still refused with BaseSchema's message, which names children as the remedy, while the renderer reads that key as its message text. Carrier: objectui#10872's flat-props batch (unchanged by this PR; the new children message points at properties).
  • A metric-card node inside the legacy { id, component, layout } envelope still parses with children: DashboardWidgetSchema.component is plain BaseSchema, deliberately (the objectui#8344 note on that member), so it accepts children on any node type, not only metric-card. It is not a face that carries the metric-card literal, so it is not a row of the card's instrument. Carrier: none.
  • StrictAnyComponentSchema refuses every metric-card widget that carries value (its derived strict slot arm closes the passthrough whose members are the card's registry inputs). Measured on both sides of this PR: at 5d689c3f6 the strict slot arm already reports value as unrecognized, and at this head a card carrying only type and value is refused. The strict face has no non-test consumer in this repository. Carrier: none.

Generated by Claude Code

…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
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types tests labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 52 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5903-objectgantt-declared-keys.md

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743); the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair — but at that time the ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction, not that nothing was there. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger requires every registered pair's mirrored-but-undeclared key set to equal that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/5928-classname-style-props-rename.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Where the non-pair is recorded now. zod-mirror-parity.test.ts keys its existing EXCLUSIONS entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to ClassNameStylePropsSchema. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for.

.changeset/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743) — the same retirement objectui#5903's entry carries, for the same clause and the same reason; the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair, at a time when that ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger reconciles every registered pair's mirrored-but-undeclared key set against that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/6150-undeclared-but-consumed-keys.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ AMENDED, and the amendment ships in this same release. The TreeViewSchema slice (604476d97) gave the key a zod arm after all — a NAMED REFUSAL (handlerKeyRefusal(key, 'runtime-slot', label)), never a shape — because "no mirror entry" is not neutral under BaseSchema.passthrough(): it meant an authored { "type": "tree-view", "onNodeClick": { "action": "toast" } } parsed GREEN, survived the parse, and reached a call site that expects a function. ⇒ the three clauses this bullet used to carry are no longer true of the code shipping beside it. The key is now a MEMBER of TreeViewSchema.shape and an authored value is refused BY NAME at path onNodeClick; it has LEFT zod-mirror-parity.test.ts's RuntimeOnlyDeclared for that file's KnownDrift; and it is no longer "the first pair to sit there without also sitting in UnmirroredDeclared" — draining it emptied that difference, so RuntimeOnlyDeclared is now a SUBSET of UnmirroredDeclared and the union of the two equals UnmirroredDeclared itself. ⛔ objectui#6152's ruling is untouched by any of this: what the key still does not have, and never will, is a z.function() shape — no serialized document could satisfy one.

.changeset/6175-column-state-persistence.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Nothing is retired. Both spellings remain declared on DataTableSchema; onColumnReorder stays declared and stays unwired, exactly as the RuntimeOnlyDeclared ledger in zod-mirror-parity.test.ts records it. Which of the two survives is a declared-surface ruling that stays open and is deliberately not settled here.

.changeset/6396-previous-values-dom-leak.md

  • names packages/types/src/form.ts → packages/types/src/form.ts — edited by this change

    Scope is the runtime leak only. The declared key stays exactly as declared (packages/types/src/form.ts, packages/types/src/zod/form.zod.ts are untouched): it has a live consumer, so there is nothing here for the enforce-or-remove channel.

.changeset/6397-partial-schema-collapse-pin.md

  • names packages/types/src/form.ts → packages/types/src/form.ts — edited by this change

    ⚠️ Dated note, 2026-09-28 — a generic re-spelling of this Omit does not collapse — objectui#9256. Later in this same release objectui#9256 declared a key-remapping OmitDeclared ({ [P in keyof T as P extends K ? never : P]: T[P] }, in packages/types/src/form.ts) and re-spelled the heritage of InputShorthandSchema and UiCalendarSchema with it. Such a mapped type iterates the named members and the index signature separately, so it drops exactly K, keeps every other named member with its modifiers, and keeps the index signature. The same spelling of this alias declares 28 properties at ButtonSchema and 62 at ObjectGridSchema in this release, where the shipped alias declares 1 at each; at this change's own commit, under the same TypeScript, it declares 27 and 61, the source counts in the table above. So "every generic re-spelling collapses" was not true when it was written. The alias is still left as written: re-spelling it would narrow what every instantiation accepts, a contract change and not a pin's business. The PartialSchema docblock and partial-schema-collapse-pin.test.ts carry the correction. The rest of this entry is kept as the reading of this change.

.changeset/6639-objectgrid-title-mirrored.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The gain is the typed refusal: the mirror's .passthrough() base was already admitting any title unexamined, and it now enforces the declared string. zod-mirror-parity.test.ts's UnmirroredDeclared ledger records the key as worked off — the ledger's first shrink by repair (97 + 1 mirrored + 23 reclassified is what the seeded "121" now means).

.changeset/6687-chatbot-surface-authorable.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    Measured on both declaration faces before the fix, each with a control that had to hit: schema.surface appeared 0 times in renderer.tsx against schema.placeholder at 3 (one per registration) and schema.processVisibility at 1; and ChatbotSchema (packages/types/src/complex.ts) declared 34 keys, not this one. Two faces agreeing is what made the zero a reading rather than a bad query.

.changeset/6938-checkbox-wrapper-class.md

  • names packages/types/src/form.ts → packages/types/src/form.ts — edited by this change

    packages/components/src/renderers/form/checkbox.tsx:36 reads cn("flex items-center space-x-2", schema.wrapperClass) — classes on the wrapper div around the box and its label — and neither the TypeScript interface in packages/types/src/form.ts nor the zod mirror in zod/form.zod.ts declared the key. It compiled through BaseSchema's index signature and parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on FileUploadSchema and FilterBuilderSchema (objectui#6150); the checkbox was left out only because its doc page's schema block is a six-line summary.

.changeset/6939-kanban-column-cards.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.changeset/6940-rowactions-boolean-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The list view's same-named rowActions in zod/objectql.zod.ts — z.array(z.string()), the legacy bare-name action list on ObjectGridSchema — is a different key that is correct as it stands, is in parity with its own TS twin (rowActions?: string[]), and is not touched.

.changeset/7113-chart-data-model.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7200-object-form-section-style-keys-undeclared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The authored-metadata type now agrees with @objectstack/spec, whose FormSectionSchema is a strict object declaring neither key, and with the ruling's rationale (maintainer 2026-09-01, verbatim): "retire the reads … Declaring the keys was weighed and not adopted: it would formally invite free Tailwind strings into authored metadata, the exact class the boundary exists to keep out." A ?: never tombstone was not used: ObjectFormSection has no zod mirror (ObjectFormSchema in zod/objectql.zod.ts does not declare sections), so there is no parse door to refuse at, and a tombstone is still a declaration in completion and in the published .d.ts.

.changeset/7265-types-user-filter-field-derives.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    zod/objectql.zod.ts declared two schemas under names @objectstack/spec/ui already exports. They were triaged separately, by reading their sites, and went different ways.

.changeset/7295-chat-message-avatar-keys.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    packages/plugin-chatbot/src/index.tsx:173–178 reads message.avatar || userAvatarUrl and message.avatarFallback || userAvatarFallback (and the assistant twins), the authoring-to-runtime seam spreads every unlisted key through (chatMessageAdapter.ts, ...passthrough), and the SDUI renderer feeds the authored messages[] straight in — a per-message avatar override renders, is documented, and no authoring-facing type declared it. ChatMessage in packages/types/src/complex.ts has no index signature (objectui#5155, deliberately — none is added here), so an author annotating ChatbotSchema.messages was told a value that renders is an error (TS2353); the zod mirror ChatMessageSchema is a plain strip-mode z.object, so the value parsed green and was silently DROPPED from the parsed output.

.changeset/7322-object-kanban-group-by-limit.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

.changeset/7344-handler-string-any-mirrors.md

.changeset/7352-drill-down-config-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7363-objectql-union-arms.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

.changeset/7654-floating-chatbot-trigger-icon-tombstone.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Every other tombstone in this package pairs ?: never with a retirementTombstone() refusal on the Zod twin. There is no twin here to carry one: FloatingChatbotConfig has no Zod mirror at all, and floatingConfig sits in the UnmirroredDeclared ledger (zod-mirror-parity.test.ts, complex.zod.ts#ChatbotSchema). BaseSchema is .passthrough(), so the whole floatingConfig object rides through unvalidated — before this change and after it. Minting a mirror to host a refusal would be the declared-but-UNMIRRORED axis (objectui#6152), a different defect: a key can be mirrored and inert, or unmirrored and live, and fixing one says nothing about the other. This change does not widen into it.

.changeset/7655-chatbot-registration-authoring-faces.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    New published symbol: ChatbotSharedKey, the string-literal union of the twenty keys all three registrations read. It is exported from complex.ts because an exported interface may not extend a Pick over a private name (TS4022), so it is emitted into dist/complex.d.ts and is reachable through the published @object-ui/types/complex subpath (it is not re-exported from the package entry). It is a census, not an authoring face.

.changeset/7697-combobox-option-root-barrel.md

  • names src/form.ts → packages/types/src/form.ts — edited by this change

    Additive only. ComboboxOption is added to the root barrel's existing named re-export list from ./form.js, next to the sibling option types that were already there (SelectOption, RadioOption). Nothing is removed, retyped or narrowed: the declaration stays in src/form.ts, its three members (value, label, disabled?) are unchanged, and the @object-ui/types/form subpath spelling keeps working exactly as before. Both spellings now resolve to the same declaration.

.changeset/7703-chatbot-dark-keys-retired.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

  • names packages/types/src/zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

.changeset/7722-wrapper-class-five-more.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (b74a8598d), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7804-tree-view-handler-slot.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The pair moves from zod-mirror-parity.test.ts's RuntimeOnlyDeclared to its KnownDrift, which empties the former of the one entry the latter did not also hold — so the two unmirrored ledgers are now in a containment relation, and the cross-ledger figure that recorded their difference states the containment instead.

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    AnyComponentSchema declares 107 node component types. 105 of them could be named on the ./zod barrel — ButtonSchema.safeParse(node), which is what a designer, a form builder or a targeted test needs. The arms declaring type: 'breadcrumb' (navigation.zod.ts) and type: 'object-tree' (objectql.zod.ts) could not: both were already export const in their own module, but index.zod.ts — the package's only zod entry point — did not re-export them, so the schemas existed, were maintained, and were applied by the union while no consumer could name them.

.changeset/8072-input-wrapper-class-mirrored.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    packages/components/src/renderers/form/input.tsx reads cn("grid w-full items-center gap-1.5", schema.wrapperClass) onto the wrapper div around the input and its label. The TypeScript face has declared the key all along (form.ts, docblock "Input wrapper CSS class"); the zod mirror never did, so the value rode through .passthrough() and { type: 'input', wrapperClass: 42 } validated GREEN — while the identical document on any of the other eight schema.wrapperClass readers (checkbox, file-upload, filter-builder — objectui#6150 / b74a8598d; switch, textarea, date-picker, select, list — objectui#7722) was refused at the key.

.changeset/8338-retire-toast-action.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The parity ledgers drain with it, every figure re-derived by zod-mirror-parity.test.ts's own AST and mirror instruments rather than stepped by hand: KnownDrift 42 entries / 64 keys → 41 / 63 (the entry's whole content, so the entry went too — the ledger's first loss by RETIRING a key rather than by moving either face toward the other), and WiderThanDeclared 23 / 36 / 47 arms, split 6 / 30 / 0 / 11 → 22 / 35 / 45, split 6 / 29 / 0 / 10. The pair itself stays registered, so EXPECTED_MIRROR_PAIRS does not move.

.changeset/8344-node-recursion-point-redirect.md

  • names zod/public-blocks.zod.ts → packages/types/src/zod/public-blocks.zod.ts — edited by this change

    ⚠️ Dated note, 2026-09-28 — a second category module — objectui#10872. Later still in this same release zod/public-blocks.zod.ts joins them as well and imports zod/base.zod.ts too, so both counts in the mechanical note above are two higher, not the one the objectui#10859 note says; the reason the arm cannot be an import is unchanged. The rest of this entry, and that note, are kept as their readings.

.changeset/8415-filter-builder-condition-id.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

  • names zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-complex.md

.changeset/8478-zod-pins-form-layout.md

  • names zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    The remaining 6 addresses (zod/complex.zod.ts) stayed out of scope for this PR and returned to the queue rather than riding this PR's scope — the card did not close here.

.changeset/8572-chatbot-body-retired.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Why this key and not another. It was the ONE place in this vocabulary where body did not mean "what goes inside this component": zod-mirror-parity.test.ts carried the pair under KnownDrift as "two different meanings of one key", and the same collision was the whole reason chatbot was the single arm of the component union whose output was not assignable to SchemaNode. Both ledger rows move with this change, and the two pins that recorded the old state are INVERTED rather than deleted (see below).

.changeset/8735-objectql-mirror-docblocks-not-defaulted.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Correct four zod/objectql.zod.ts docblocks that described the behaviour objectui#8317 removed. Since that change the zod mirrors strip imported @objectstack/spec defaults at this package's import boundary, but the docblocks on HttpRequestSchema, ListColumnSchema, SelectionConfigSchema and PaginationConfigSchema still said, in the present tense, that method, prefix.type, type and pageSize are defaulted on parse — the opposite of what each export does. Each now says the key is declared and accepted but NOT defaulted on parse.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.
  • names packages/types/src/zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8802-8257-8008-kanban-gantt-family-retirement.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    What each retirement was, measured. Three of the four were registration-only: no schema face in @object-ui/types ever declared kanban-ui, kanban-enhanced or gantt as a component node type, so unregistering is the whole retirement. The bare kanban key was the exception — it had a declared arm on both faces (KanbanSchema in complex.ts and its Zod mirror), and a plain deletion there would have been the objectui#7664 failure: BaseSchema is .passthrough(), so a document naming a dropped key validates green and renders nothing. It therefore retires as a named refusal: the Zod union keeps an arm claiming the literal and answers a { "type": "kanban" } document with a message naming object-kanban as the remedy, while the TypeScript half is the absence of the arm from ComplexSchema and of the key from SchemaRegistry, so tsc refuses it at the authoring site.

.changeset/8885-object-chart-drilldown-title-compareto.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

.changeset/8913-object-kanban-columns-declared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

.changeset/8992-user-actions-collapse-and-docblock.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    objectql.zod.ts's UserActionsSchema read stripImportedDefaults(Spec).extend({ group, hideFields, rowColor }), an extension that existed only because @objectstack/spec did not declare those three keys while normalizeListViewSchema folded objectui's legacy showGroup / showHideFields / showColor onto them. The protocol adopted all three in 17.3.0 (objectui#5435's ruling), so the extension is now a second local copy of a protocol declaration — the shape two faces start drifting from — and it collapses into the plain by-reference re-export its own note always said it would become.

.changeset/9309-object-gallery-filter-destination-typed.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

.changeset/9406-types-root-barrel-two-names.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    form-barrel-mirror-9406.test.ts keeps it closed, and it is DERIVED rather than a pair of presence assertions: it reads form.ts's export list and the root barrel's ./form.js re-export list on every run and names whatever is in the first and not the second. A pin asserting "these two names are present" would pass on the day the next declaration lands in form.ts and is forgotten, which is this class reopening yet again. Names deliberately left off the list get a ledger row carrying the reason instead, and a row goes red once its name reaches the barrel or stops being declared.

.changeset/9511-record-id-is-a-string.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ The declaration alone would not have been enough, and this is the reusable part. A TypeScript declaration does not run at parse time. The hand-written zod mirror is the only face in this repository that can refuse an authored number, so narrowing the declaration without the mirror would have shipped declared !== enforced on a published surface — and zod-mirror-parity.test.ts would have stayed GREEN through it, because that instrument asserts a mirror accepts everything its declaration declares and a mirror left WIDER passes. Both faces moved together for that reason.

.changeset/9526-types-root-barrel-command-element-types.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    The change is purely additive: the two names join the root barrel's existing named re-export list from ./form.js, beside CommandSchema. Nothing is removed, renamed or narrowed, the declarations stay in form.ts, and the /form subpath keeps working. It is the third instance of one class, repaired by the same route each time: objectui#7697 for ComboboxOption, and objectui#9406 (director decision batch 133, item 2, letter (a)) for InputShorthandSchema and UiCalendarSchema.

.changeset/9549-tree-filter-declared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

.changeset/9606-object-kanban-card-title.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

.changeset/9628-kanban-column-collapsed-honoured.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    The key was declared on both published faces of the object-kanban arm — the lane element of ObjectKanbanSchema (objectql.ts and its Zod mirror) and the runtime lane KanbanColumn (complex.ts and its mirror) — and read by KanbanEnhanced alone, a module no production source imports. An authored { "id": "todo", "title": "To Do", "collapsed": true } therefore parsed green on both faces and reached a board that did nothing with it: KanbanImpl's only collapse is the SWIMLANE row's, held in viewer state under objectui:kanban-collapsed:ANGLE-BRACKETS(swimlaneField) and never keyed to a lane's declared value. That is the ADR-0049 declared-but-unhonoured shape.

.changeset/9659-node-recursion-point-inert-clause.md

  • names content-channel-family-d-9256.test.ts → packages/types/src/__tests__/content-channel-family-d-9256.test.ts — edited by this change

    One reading in a neighbouring table got a line, not a rewrite. In content-channel-family-d-9256.test.ts the plain chatbot row lists children only, which on the two TWIN faces still means "body held out and live" — and objectui#9639 had to re-point that table's LIVE CONTROL at a twin precisely because the plain face refuses body now, for objectui#8572's reason rather than this table's. Measured at the time: body ACCEPTED on chatbot-enhanced and chatbot-floating, REFUSED on chatbot. ⚠️ That hold-out has since ended and not by a decision on either card: objectui#6771 retired body on BaseSchema itself, so the twins had no content channel left to inherit and each now declares the same NEITHER-channel tombstone its children sentence already asserted, pointing at requestBody. The row is annotated as one-sided so the next reader does not take it for a two-sided reading. ⛔ No assertion in that table moved.

.changeset/calendar-readme-schema-keys-5045.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    README.md's "Schema API / CalendarView" block described a CalendarViewSchema that does not exist. Measured against the interface itself (packages/types/src/complex.ts) and its zod mirror: events — the schema's only required key besides type — was published as events?, so a reader following the README omits it and TypeScript rejects the node; defaultDate was string where the schema says string | Date; and onDateClick was listed as a schema key when it is a CalendarViewProps component prop, sending readers to a different package's surface for a key calendar-view does not have (the schema's key is onDateChange). The block also listed 6 of the schema's 13 keys with nothing saying it was a summary (objectui#5045).

.changeset/object-view-unmirrored-keys-7779.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Who is NOT affected: every correctly typed document, and every document that never wrote these keys — absent stays valid on all nine. No renderer changed. The parity ledger (zod-mirror-parity.test.ts) records the move: UnmirroredDeclared 14 entries / 96 keys to 14 / 87, the ObjectViewSchema entry re-derived into the SPEC-DERIVED half because the mirror now references the spec in code.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 5d689c3f6 (merge-base with origin/main): 9 file(s) changed outside .changeset/, read against 1708 pending declaration(s) that publish a body (2311 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3104.5 KB 3104.5 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-DV1RNSsX.js —
Status FAIL —

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:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.60KB 6.18KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.39KB 14.98KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.56KB 36.67KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 162.67KB 41.95KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.54KB 22.44KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 42d3ea57eec9ad0c54ce69e51a31ef4e71729bce

Isolated adversarial at-tier review of PR objectui#11020 (card objectui#9256, family D, the public-block slice after the re-measure; Clause-② yes because twenty published accept sets narrow). Read against GitHub REST, the branch fetched as refs/review/pr-11020 (one commit over 5d689c3f6), and a detached worktree at that head with an offline install; ⛔ not against the dev report 5876936205. Every renderer named below was read by me; every probe below was run by me in that worktree, and the tree was restored to git status clean after each mutation.

① Derived judgments

1. The twenty narrowed types — renderer readership, re-derived. SchemaRenderer (packages/react) destructures type, children, body, schema, dataSource and the visibility keys out of the props bag it spreads and never renders the stripped children, so a channel reaches a component only through schema.children / schema.body, a destructuring, or a whole-node spread into something that reads them. The registry snapshot (packages/cli known-schema-types.ts) lists every twin: the ten record: blocks are registered with namespace: 'record' and skipFallback, the three page: and four element: blocks with their namespace and skipFallback, so no bare twin exists; the protocol-placeholder: twins of eight of them are PlaceholderRenderer, which reads schema.type and schema.props only, and the console's lazy metric-card / object-metric registrations load the same plugin. No registration of the twenty declares a children slot input.

type registration → reader reads the node's children / body verdict
record:details plugin-detail → RecordDetailsRenderer no / no — sections, fields, columns, hideFields, inlineEdit, showHeader, title, primaryField, aria sound
record:highlights → RecordHighlightsRenderer no / no — fields, aria sound
record:related_list → RecordRelatedListRenderer (elementDataSourceBlock + ElementDataSourceGate, neither reads a channel) no / no — the list keys; the one spread is ...schema.add sound
record:path → RecordPathRenderer no / no — statusField, stages, aria sound
record:activity → RecordActivityRenderer no / no — read(key) is called with literal feed keys only, plus items / loading / aria sound
record:discussion → RecordChatterRenderer (shared with record:chatter) no / no — the WHOLE node is spread into config; followed: the renderer reads config.feed, and RecordChatterPanel reads config.position, width, collapsible, defaultCollapsed, feed; config is never spread onward sound
record:history → RecordHistoryRenderer no / no — limit, emptyText, unknownUserText, entries, loading, properties sound
record:quick_actions → RecordQuickActionsRenderer no / no — the toolbar keys, actions, requiredPermissions, aria sound
record:reference_rail → RecordReferenceRailRenderer no / no — entries, hideEmpty, className sound
record:alert → RecordAlertRenderer no children; body IS read, as the message text — readProps is { ...schema, ...schema.properties } and the result is read for severity, title, body, icon, action, dismissible, dismissKey, visible; never spread onto a DOM element, so a merged children reaches nothing children refusal sound; body correctly left alone
page:header components, namespace: 'page' → PageHeaderRenderer (any-typed) no / no — properties, title, subtitle, breadcrumb, actions, showStar, showCopyId, recordChrome, maxVisible, mobileMaxVisible sound
page:tabs → PageTabsRenderer no / no at NODE level — reads items (and properties, tabStyle, position, id, alwaysShowStrip); renders item.children per ITEM; the badge walkers descend from item.children and render nothing sound; item level pinned live
page:accordion → PageAccordionRenderer no / no at NODE level — items, variant, allowMultiple; renders item.children sound; item level pinned live
element:text namespace: 'element' → ElementTextRenderer no / no — readProps merges schema.props and schema.properties ONLY (not the node), reads content, variant, align, aria; the body hit is the VARIANT_CLASS.body lookup sound
element:number → elementDataSourceBlock(ElementNumberRenderer) no / no — readProps, className, and useElementDataSource(schema) which reads schema.dataSource only sound
element:button → ElementButtonRenderer no / no — readProps keys and className; execute is built key by key sound
element:divider → ElementDividerRenderer no / no — className only sound
object-metric plugin-dashboard → ObjectMetricBlock → ObjectMetricWidget no / no — the block spreads props (already stripped) and named objectName / filter; the widget has no rest spread sound
object-master-detail-form plugin-form → MasterDetailFormRenderer → MasterDetailForm no / no — nineteen named schema.* reads, no children / body, no whole-node spread; MasterDetailFormSchema declares neither sound
metric-card plugin-dashboard → MetricCard; DashboardRenderer.renderWidget builds { ...widget, ...options } and hands it to SchemaRenderer (both the self-contained and the Card branch); DashboardGridLayout does the same no / no — MetricCard destructures the schema-host props and forwards ...domProps to Card; since every path goes through SchemaRenderer, children is stripped before it can land in that rest sound

⇒ no row is a FAIL. readProps in components merges the two config bags, not the node, so the brief's children-from-the-merge hazard does not exist there; on record:alert the merge does include the node, and nothing downstream reads children from it.

2. The exceptions. record:alert: only children is narrowed; the arm's body line is untouched, and my probe confirms a flat body still gets BaseSchema's objectui#6771 message while properties.body parses. Its new children message is true sentence by sentence (the reads above; "What it renders instead: one callout … title and its body message TEXT, both set in properties" — and the flat spelling is also tolerated, which the message does not deny). page:tabs / page:accordion: the renderer reads ITEM-level children (item.children inside renderChildren), never the node's; the pin's item-level case and my probe both parse green with children inside properties.items[] and refuse it at node level.

3. Producers. node scripts/body-dialect-census.mjs --keys over the twenty plus div, card, page, page:card as lit controls, whole worktree at the head (9146 files, .ts .tsx .js .mjs .cjs .json .jsonc .md .mdx): authored children on any of the twenty — only the PR's own two pin cases (record:alert, record:details in the new test file); authored body — only record:alert's text prop, four times, all in tests (two console component-input-union-specimens, one vscode-extension dialect pin, one in the new pin), and that key's parse verdict does not change here. Controls fire: div 179, card 183, page 55, page:card 1 children. ⇒ no shipped or stored document starts being refused; nothing needed migrating.

4. Shape. (a) No TypeScript declaration under packages/types/src outside zod/ carries any of the nineteen literals (grep over type: sites); the .d.ts is built from that source, and CI's Type Check and Test (dist pins) are green. (b) Every refusal is a MEMBER: Object.keys(mirror.shape) contains both keys on all eighteen arms and children on record:alert (the pin's member rows, run by me), and zod-mirror-parity is green (1000 tests across the three files I ran). (c) metric-card: children?: never and body?: never on DashboardWidgetSlotComponentSchema; both retirementTombstone members on the private slot arm; DASHBOARD_COMPONENT_WIDGET_TYPES is ['metric-card'] only, so the arm refuses no other literal. (d) Re-measured: DashboardComponentSchema.widgets stays z.array(z.union([slot arm, DashboardWidgetSchema])) — untouched. A metric-card widget carrying children yields ONE invalid_union at widgets.0 whose errors hold the slot arm's invalid_type at the arm-relative path children with the by-name message, and the strict arm's unrecognized_keys for value, children. Through the cli's own explainUnionIssue (the printer objectui validate uses) the two lines come out labelled arm 1/2 (the by-name message, absolute path widgets → 0 → children) and arm 2/2 (Unrecognized keys), so an author sees the message. The pin asserting inside errors is the right shape; a top-level assertion would pin a shape the union never produces.

5. Refusal messages, all read. Each of the eighteen neitherContentChannelGuidance strings names the registration I read and a renders clause of keys the renderer reads; none says "Did you mean". The two exceptions' reasons are both true: record:alert's renderer reads a key named body (pickLocalized(props.body)), so the builder's "no renderer read consumes body" sentence would be false there; and sdui-parser's validateTree recurses with node.children.forEach(visit) only — it never walks widgets — so the builder's "only the parser tier's not-a-container warning noticed it" would be false for a widget-slot metric-card, and its own string says so. The new TypeScript docblocks on the slot face say the same.

6. Pins and ablations. The 230-test pin covers all twenty: 18 rows × 2 channels × 6 assertions (216) + the population guard + record:alert's two carve-out cases + the item-level, four-container-control and nested-in-page cases + the three metric-card cases. Run by me: 230 / 230; tsc -p packages/types/tsconfig.test.json exit 0 on the unmutated head, so both @ts-expect-error directives fire. Ablations, each restored and the tree proven clean: A1 (delete RecordDetailsBlockSchema's children member) → 6 failed / 230, the five record:details.children rows plus the nested-in-page case, member row green; A3 (delete the slot arm's children member) → exactly 1 failed; A4 (delete RecordAlertBlockSchema's children member) → exactly 1 failed; A2 (delete the slot face's children?: never) → tsc exactly 1 × TS2578 at the pin's withChildren line, vitest unmoved. All four match the dev's readings. A2 being tsc-only is the family precedent (PRs objectui#9589, #10897, #10907, #11003 and the nl-query block): ?: never is erased before vitest runs, the pin header says so, and CI's Type Check runs that config.

7. Fixes #9256 — closing is right. Re-derived on the head: every registry key parsed behaviourally through AnyComponentSchema (648 keys; 136 parse { type } green; controls div accepts children, div refuses body, dialog refuses children, unknown type refused): 67 still accept children, 0 accept body, and the TypeScript scan adds form (its face declares a live children, and FormRenderer calls renderChildren(schema.children)) — the dev's 68. Dispositions checked by me on the renderers: the html / semantic factory (html-elements.tsx, semantic.tsx) renders renderChildren(schema?.children) for a, p, h1…h6, ul, li, code, section, nav and the rest of that arm (42 non-void tags), and passes undefined for the three void tags br / hr / img, which the E3 pin holds on purpose because they share one arm with 35 tags that do render the key; alert, badge, tooltip, toggle, box, container, flex, grid, stack, scroll-area, aspect-ratio, sidebar, app, page, div, card, button, span each read schema.children (family A / C, 18 on the zod face + form); page:card, page:section, page:footer, page:sidebar keep the live schema?.children || schema?.body fallback (ruling 5861449497 item 4 leaves them untouched; out of this card by the card body). No OPEN family-D registration remains. Families C and E stay out: the card body excludes them, ruling 5861449497 Q1 / Q2 disposed of them, and item 5 says this card closes by Fixes with the closing PR. The legacy widget envelope (item ③) is not a face that carries any of the twenty literals, so it does not make Fixes premature.

8. Beyond-brief text edits. The family-D pin's header and rows now say body is REFUSED on all three chatbot faces: true — ChatbotEnhancedSchema and ChatbotFloatingSchema each carry a body: retirementTombstone(…) naming requestBody, and the renamed LIVE CONTROL asserts exactly that on chatbot-enhanced. The CodeEditorSchema.children docblock now lists onChange: true (CodeEditorRenderer forwards onChange ?? schema.onChange). The zod README's two added sentences: true against the 21 arms in public-blocks.zod.ts (7 page:, 10 record:, 4 element:). The public-blocks.zod.ts module docblock: true, its carve-out paragraph qualifies the "neither channel" sentence for record:alert. The parity census's exclusion reasons: true, with ONE text defect — the objectql.zod.ts#ObjectMetricBlockSchema entry carries the clause "+ the objectui#9256 body / children refusals (its renderer reads neither channel)" TWICE in a row. Not false, text only, in a test's reason map; fix: delete the duplicate clause.

9. Downstream. Nothing under apps/site, the repo root or content/docs authors children or body on the twenty (census above covers them). CI's Type Check job is green on the head, which covers @object-ui/site and the root the dev did not run. Consumer-side probe, run by me under tsconfig.test.json: children and body on a DashboardWidgetSlotComponentSchema and children on a metric-card inside DashboardComponentSchema.widgets give exactly 3 × TS2322; the two channel-free controls compile. Probe deleted, tree clean.

10. CI and commits. Head 42d3ea57e: 43 check-runs, 39 success, 3 skipped (dependabot, Test (coverage), the coverage shard), 1 failure. Every named required context is green: Lint, Type Check, Build & E2E, Build Docs, Changeset Declaration, Test (all 8 shards and the aggregate), Spec Main Shape Gate, Governed Surface Queue Guard. The one failure is Bundle Analysis (not required), red on main at both 5d689c3f6 and the current e2dffc9d1 (check-run conclusions read via REST; carrier objectui#10996). The head's own budget comment reads the eager closure at 3104.5 KB against a 3104.5 KB ceiling with the same 330 chunks; PR objectui#11017, the commit now at main's head, read 3104.9 KB. The diff adds TypeScript members (no JavaScript) and zod strings that live in the lazy types-zod chunk (the previous slice's review verified that chunk is outside the eager closure), so this red is main's; I could not re-fetch the job logs (the proxy refuses the log-blob host), so the dev's +1 gzip byte is not re-measured by me. One commit, author Claude, trailer Co-Authored-By: Claude and the session line; no model name in the commit or the PR body.

② Semver level

minor for @object-ui/types with an explicit BREAKING note and a migration line, the spelling PRs objectui#9589 / #10897 / #10907 / #11003 used under the no-major rule; Changeset Bump Policy and Changeset Declaration green. Every sentence checked: before the PR all nineteen arms parsed { type, children } green (each is BaseSchema.extend with no override on 5d689c3f6) and metric-card parsed and type-checked with it; body was already refused everywhere by BaseSchema, and on all but record:alert the message now names what the node renders; "no render behaviour changes" is true because nothing read the keys; the migration line names real keys (element:text's properties.content, item-level children, record:alert's properties.body, page:section / page:card). The breaking effect is declared, and the census shows it reaches no shipped document.

③ Boundary flags

  • The cli ratchet. registered-types-validate-ratchet-10859.test.ts is unedited (the diff touches nothing under packages/cli) and green in CI; the ratchet counts refusals at type, and every one of the twenty still parses { type } green, so it does not move. objectui validate now refuses an authored children on these types at its own path (before: "Schema is valid"), and on metric-card inside the widget's invalid_union as arm 1/2 of two; the changeset records both. No cli pin or doc authored the old behaviour (census), so nothing else needs recording.
  • Out-of-scope findings, all three true. (1) record:alert's flat body is refused with BaseSchema's message naming children as the remedy while the renderer reads that key as text — carrier objectui#10872's flat-props batch (note 5873869262 there), unchanged by this PR. (2) The legacy envelope: { id, component: { type, children }, layout } parses green at the head for metric-card, and also for record:details and for markdown (narrowed by objectui#11003) — DashboardWidgetSchema.component is plain BaseSchema by the objectui#8344 note, so this is one pre-existing hatch around every family-D refusal, not a face carrying a literal; rightly outside this card. The dev's "carrier: none" is thin: it deserves a recorded note on the card that owns the widget slot's routing (the objectstack#8593 ruling declined the AnyComponentSchema redirect there), which is the seat's call. (3) StrictAnyComponentSchema refuses every metric-card widget that carries value — its required registration input — because both of its strict arms report value as unrecognized (probed at the head; { type: 'metric-card' } alone parses); pre-existing, no non-test consumer in the repo, but it is an exported face of @object-ui/types/zod, so it deserves a filed carrier rather than none. Neither (2) nor (3) is this PR's defect.
  • Serial. git merge-tree --write-tree of the head is clean against origin/main (now e2dffc9d1, one app-shell / i18n commit past the PR's base; the PR body's "has not moved since 5d689c3f6" was true when written and is stale now, with no effect), and against the heads of open drafts objectui#11006 (ebd156ef5), feat(core)!: retire the bare-string globalFilters[].options lift (objectui#4356) #10930 (1daf7edf7) and feat(app-shell,types): cloud:plan-status marks the organization's current plan card (objectui#10919) #10990 (1cb74cf77). objectui#10872's flat-props batch is still unclaimed (its pm:queue release 5868698636 is its latest state change), so by ACCEPT 5873860373 it runs serial behind this PR.
  • PR body. No angle brackets, no line addresses, no model names. No sentence read false against the tree except the stale main line above. The three page: / family labels ("live fallback, out of this card") agree with the card body and ruling item 4.

Defects, for the record. One, text only: the duplicated clause in zod-mirror-parity.test.ts's objectql.zod.ts#ObjectMetricBlockSchema exclusion reason (①8). It changes no verdict and no behaviour; fix in place or fold into objectui#10872's batch, which edits the same entries. Nothing else found.

Implemented-by: claude/issue-9256-family-d-public-blocks
Reviewed-by: session_01DuWo5bdP9SdVebamn99GGk

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 19:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit f6fb83f Sep 28, 2026
44 of 45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-9256-family-d-public-blocks branch September 28, 2026 20:05
huangyiirene pushed a commit that referenced this pull request Sep 28, 2026
…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>
huangyiirene pushed a commit that referenced this pull request Sep 28, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: types tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components+): family D — the ~60 registrations that read NEITHER body nor children, measured across all 25 registering packages

2 participants