Skip to content

feat(types): the four page: containers take their child list in properties.children, and objectql.zod.ts shares propsBag (objectui#10872, batch 6) - #11222

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10872-container-children-channel
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10872-container-children-channel

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #10872
Clause-②: yes

objectui#10872 batch 6: the four page: containers take their child list in properties.children. page:card, page:section, page:footer and page:sidebar now refuse a node-level children by name, and restate body with the same message. The message's first sentence names properties.children, the member each block's @objectstack/spec row declares. Also in this PR, with no behaviour change: objectql.zod.ts's two public-block arms import the one propsBag helper instead of keeping a byte copy of it.

The card stays open. record:line_items (no spec row yet) and the rest of the flat-props / envelope-key batch remain, as the claim and release 5909279008 list them.

The ruling this executes, and the premises it rests on

The seat applied triage's action:* answer (5906817482: "The properties bag is the contract", "B is not taken … a second dialect") to this branch. The ruling holds only on premises 1–4 of the order, so each was measured before the first edit to the four arms. All four hold for all four containers.

# premise reading (objectui origin/main cfc9b6db98, installed @objectstack/spec 17.5.0) holds
1 the installed spec refuses a node-level children on all four PageComponentSchema.safeParse({ type, children: [node] }): unrecognized_keys with keys ["children"] on each of the four. The message reads "Unrecognized key(s) on this page component: children. Before ADR-0089 D3a these were dropped silently…". { type, properties: { children } } and a bare { type } both pass. Control: page:tabs, page:header and record:details give the same refusal, so the instrument is live. yes, 4 of 4
2 each row declares children inside properties ComponentPropsMap['page:card'] is PageCardProps, with keys title, bordered, actions, children, body, footer, aria. actions and body are [REMOVED] tombstones, and body's text says "Rename the key to children". The other three rows are all PageContainerProps (identity checked), whose one key is children. Every children is z.array(z.unknown()) and optional, and each row parses { children: [node] }. yes, 4 of 4
3 a spec-shaped node renders its children through objectui today This was a one-off runtime probe, never committed, through the real SchemaRenderer, ActionProvider, I18nProvider and the @object-ui/components registry (happy-dom). Each container got two element:text markers in properties.children. All four rendered both markers, in order, and the DOM was byte-identical to the node-level spelling. The roots were div.rounded-lg.border.bg-card (card), section.space-y-4, the separator-led footer and aside.flex.flex-col.gap-4. Control: with no children, the render had 0 markers. The mechanism is SchemaRenderer's properties hoist, which copies every bag key except type / id onto the node the renderer reads. yes, 4 of 4
4 no objectui or objectstack writer emits a node-level children on the four Census: node scripts/body-dialect-census.mjs --keys page:card,page:section,page:footer,page:sidebar,page:tabs,page:accordion,grid,flex,card --json. objectui (9440 files): 7 nodes of the four types carry a node-level children, all in the test bucket (six page:section and one page:card, in app-shell render tests). There are 0 in apps / examples / content/docs / skills / packages/*/src. Lit control: card has 88 node-level children in example-app and flex has 251. objectstack (origin/main 72f8c38201, apps / examples / packages / content archived into the scratchpad, 8379 files): 109 nodes of the four types (83 page:card, 20 page:section, 1 page:footer, 5 page:sidebar), and 0 carry a node-level children. Lit control: flex has 2. The showcase's own my-work page writes page:card with properties.children, and the spec lint's page-walk says "PageComponentSchema is .strict(), so a component carries no children key of its own". Writers read directly: the page designer's canvas addresses properties.children for page:card and page:section (childGroups in PageBlockCanvas), and for any block that already carries that array. The inspector writes every prop into properties (patchProp). The default page synthesizer (buildDefaultPageSchema's componentNode) emits { type, properties } and none of the four types. yes, 4 of 4
5 objectBlockPropsBag is a byte copy of propsBag With only the function name normalised, the two 8-line bodies have the same md5 (d7a54fe8…) and diff exits 0. Folded: propsBag is exported from public-blocks.zod.ts and objectql.zod.ts imports it. There is no third copy, and the barrel still does not re-export it. yes
6 NAMESPACED_REFUSED_AT_TYPE does not move The diff of the ratchet file against the base is empty, and registered-types-validate-ratchet-10859.test.ts is green in the sweep below. yes

What changed

  • packages/types/src/zod/public-blocks.zod.ts
    • Each of the four arms declares children and body as retirementTombstone members. Both are fed ONE string from a new local composer, pageContainerChildListGuidance(type, row). This is the objectui#9256 construct with a different message: these blocks do render their child list, so the neither-channel text would be false for them. The first sentence names properties.children, the row (PageCardProps / PageContainerProps) and the node to write. The message also says the spec refuses the node-level spelling, and that the hoist makes the move a no-op at render time.
    • propsBag is exported, and its docblock says it is the one copy, internal, and not re-exported from index.zod.ts.
    • The module docblock's "content channels" section gains a paragraph for the four containers. The flat-key paragraph no longer says a flat body on the containers is refused by the base's objectui#6771 retirement, "whose children remedy they do render": this diff makes that false.
  • packages/types/src/zod/objectql.zod.ts: the objectBlockPropsBag copy is deleted, and object-metric and object-master-detail-form build properties with the imported propsBag.
  • packages/types/src/__tests__/container-children-channel-10872.test.ts (new, 102 tests), in four groups:
    • (a) Refusal, on both faces. invalid_type at the key's own path, and a first sentence naming properties.children, the block, the row and the node to write. Neither the neither-channel text nor the "Did you mean body → children?" remedy appears. One string feeds .describe() and the issue message, every value is refused, and both keys are members.
    • (b) Positive controls, on both faces. properties.children parses, the bare node parses, the bag spelling parses nested in a page while the node-level spelling is refused there at children.0.children, and page:card's full row (title, bordered, children, footer) parses.
    • (c) The installed spec. It refuses the node-level spelling and passes the bag spelling, and each row still declares children, so the remedy names a real member.
    • (d) The fold. Both objectql.zod.ts arms' properties description is word-for-word the helper's text, from the same composer as the public-blocks.zod.ts arms.
  • .changeset/10872-container-children-channel.md: '@object-ui/types': minor, Clause-②: yes (narrowing), see below.

Surface beyond the order's file list, each forced by this diff and declared

  • content-channel-public-blocks-9256.test.ts: its "CONTROL — %s renders the node's child list, so it still takes children" rows asserted that the four ACCEPT a node-level children, which is the exact behaviour this batch narrows. The control keeps its purpose, which is to show that a reader keeps its channel. It now asserts properties.children parses on the arm and on AnyComponentSchema, and that the node-level refusal is not the neither-channel text. Text of the other rows is untouched.
  • record-alert-flat-body-remedy-10872.test.ts: its "CONTROL arm — page:card … keeps BaseSchema's objectui#6771 refusal" asserted the old body message on page:card, which this batch restates. The control moves to card, which reads the node-level children and keeps the objectui#6771 remedy. It asserts the same three facts, and a comment records the move.
  • packages/types/src/zod/README.md: one sentence. "Every arm except the four page: containers … refuses children and body" would now misdescribe the four, so it gains a sentence saying they refuse both with a message naming properties.children. This follows AGENTS.md Add automated testing infrastructure and CI/CD workflows #2, and batch 5's README hunk was ratified on the same ground.
  • .changeset/10872-public-block-zod-arms.md (pending, batch 1): a dated note only, with the frontmatter untouched. Its flat-props bullet says children on the four is "judged against the base's own types", and that body on page:card is refused "by the base's objectui#6771 retirement". Neither describes the release as a whole any more. This is the dated-note precedent of batches 2–4 on that same file.
  • zod-mirror-parity.test.ts: the four EXCLUSIONS reason strings name the new members (text only, the order's allowance).

Changeset: minor, and why

  • The accept set narrows against main on four exported arms: a node-level children is refused where it parsed. Hence Clause-②: yes (narrowing).
  • The arm is minor, on the objectui#9256 / batch-5 precedent. This repo forbids major in any changeset (one fixed group) and spells a narrowing as minor with an explicit note.
  • It is not breaking against the last release. The published @object-ui/types 17.6.0 (npm latest, 2026-08-24) carries no public-block arm: its dist has 0 hits for PageCardBlockSchema / PageSectionBlockSchema / PublicBlockComponentSchema, against 1 each for the controls CardSchema / FlexSchema. Its safeValidateSchema answers invalid_union for { type: 'page:section' }, with or without children and with a bag, and for page:footer / page:sidebar, while the control { type: 'card', children: [...] } passes. So across the release the accept set only widens.

Tests (final head 573373f931, main e6bc087a30 merged in; all heavy runs through os-verify-lock)

command verdict
pnpm --filter '@object-ui/types^...' build exit 1 ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT. The closure is empty (@object-ui/types has no workspace dependency), so nothing ran. Not a red build.
pnpm --filter @object-ui/types build exit 0, "dist completeness: 1 package(s) complete (136 emitted files verified)"
pnpm --filter @object-ui/types type-check exit 0: three tsc legs, and tsconfig.test.json --listFilesOnly lists the three edited or new test files
pnpm exec vitest run packages/types/ exit 0, Test Files 290 passed (290), Tests 7012 passed (7012)
arm-shape consumer sweep, 13 files: every non-packages/types hit of git grep -lw on AnyComponentSchema / StrictAnyComponentSchema / PublicBlockComponentSchema in tests (the packages/types hits run in the row above), plus the app-shell block-config-schema-parity-8216 oracle, check-handler-key-read-sites.test.ts and registry-inputs-spec-parity.test.ts exit 0, Test Files 13 passed (13), Tests 396 passed (396). Includes registered-types-validate-ratchet-10859.test.ts.

The same matrix ran green on 8e2dd4e1ed before the merge of main: 290 files / 6984 tests, and the 13-file sweep with 396 tests.

Ablation (proves the pin can fail; nothing permanent left behind). node /home/user/objectstack/scripts/ablation-replace.mjs ran nested four deep in WRAP mode, one leg per arm, each with --delete of that arm's two member lines. Each leg reported "ok mutation landed", with the anchor count going from 1 to 0. During the mutation, grep -c for the four constants' tombstone calls read 0. The pin files live in packages/types and import the arms by relative source path, so there is no dist/ leg.

  • Predicted: 60 red in the new pin, from the 16 children-refusal, 16 first-sentence and 16 remedy-text rows (8 + 16 + 16), the 8 one-string rows, the 4 every-value children rows and the 8 nested-page rows. Also predicted: 4 red in the objectui#9256 control, and 0 red among the member, positive-control, spec and fold rows.
  • Observed: container-children-channel-10872.test.ts (102 tests | 60 failed) and content-channel-public-blocks-9256.test.ts (230 tests | 4 failed), which is Tests 64 failed | 268 passed (332).
  • Restored: each leg printed "ok restored: blob == HEAD (b6d53f2bbae4) and git diff HEAD is empty", and the outer script's own check read "git diff HEAD empty: yes".

Gates (head 573373f931, each exit captured before any pipe)

  • node scripts/check-changeset-presence.mjs: exit 0. "7 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/10872-container-children-channel.md."
  • pnpm changeset:check: exit 0. "No changeset declares a major bump."
  • check-changeset-overwrite: exit 0, report-only. "1 changeset(s) added, 1 modified, 0 deleted"; the modified one is the dated note, and its frontmatter is unchanged.
  • check:changeset-claims: exit 0, report-only. None of the 32 flagged pending changesets names the four containers or the helper.
  • check:pending-changeset-literals: exit 0.
  • check:control-bytes: exit 0, "OK (scanned 9615 tracked text file(s))". A grep -naP self-scan over the nine changed files found 0 lines.
  • check:new-line-citations: exit 0, "0 new citation(s)".
  • check:handler-key-reads: exit 0, "OK 139 arm(s) … 57 judged, 0 left unjudged on an arm with an unresolved spread".
  • All exit 0: check:spec-symbols, check:test-path-roots, check:installed-pin-claims, check:doc-types, check:doc-fences, check-doc-links, check-type-check-coverage ("43/43 packages compile their tests"), check:unreferenced-sources, and markdown-test-inputs.mjs --audit ("107 candidate test files, all adjudicated"). This batch adds no markdown reader.
  • eslint, narrowed, with --no-inline-config --format json over the six changed TS files: 6 files, 0 errors. There are 3 pre-existing no-explicit-any warnings outside the hunks. Invariance: --print-config shows no parserOptions.project / projectService, so linting is not type-aware and this diff cannot move a verdict on an untouched file. The repo-wide pnpm lint is left to CI.
  • check-governed-queue-guard --test over the nine paths: "NOT GOVERNED".
  • NOT MEASURED: check:doc-snippets, reason "PRECONDITION NOT MET (exit 2)". It needs 34 packages built, and the README change is prose outside any fence. Left to CI.
  • NOT MEASURED: check:readme-exports, reason exit 1, "531 self-import(s) could not be judged … the population COLLAPSED". Other packages' dist is missing. Left to CI.

Serial / overlap

main moved while this ran: PR objectui#11207 (app.zod.ts and its pins) and five others landed. None touches a file of this diff. git merge-tree was clean, main was merged in (merge, no rebase), and the matrix above was rerun on the merge. The seat named an in-flight branch for objectui#9547 that edits objectql.zod.ts (its onNavigate region) and zod-mirror-parity.test.ts (its own rows). This diff's hunks there are the helper region and the four container rows only.

Acceptance notes (not filed; for the seat)

  1. The bag's nested nodes are not judged by objectui's validator. This predates the batch, and the batch makes the bag the only channel on these four. The spec rows type children (and page:tabs / page:accordion items[].children) as z.array(z.unknown()). So { type: 'page:section', properties: { children: [{ type: 'no-such-type-xyz' }] } } parses green on both faces, and so does a malformed element:text there. The same child written at node level was refused (invalid_union at children). The spec's lint walks those bag arrays by hand (page-walk), so os validate still judges them. What this batch moves is only which spelling is accepted, not whether nested content is judged by the publish gate. The pre-existing gap belongs in its own card. Dedupe words: properties.children nested node not validated · bag slot children z.array(z.unknown()) objectui validate · items[].children unjudged safeValidateSchema.
  2. The parser tier still accepts the node-level spelling. @object-ui/sdui-parser's BASE_PROPS lists children, and isContainer authorises it on the four, so the manifest tier and the VS Code validator pass a node-level children that both zod faces now refuse. This is batch 3's record:alert pattern. The carrier is this card's flat-props / envelope-key batch, whose order names both tiers. ⛔ Not touched here: the order excludes packages/sdui-parser and packages/vscode-extension.
  3. A committed runtime pin for premise 3 is not in this PR. Zone 3 suggested one. It would need @object-ui/react and @object-ui/components in a DOM project, but @object-ui/types imports no renderer and its tests run in the unit (node) project. Its home would be packages/components/src/__tests__/, which is outside the claimed surface. The probe's readings are in premise 3 above. If the seat wants it pinned, a follow-up with that surface is a small test.
  4. skills/objectui/guides/page-builder.md says the three thin containers "render a section, footer and aside around their children". That is prose, true at runtime, and names no node spelling. It is governed, so it is only noted here.

Generated by Claude Code

…rties.children (objectui#10872, batch 6)

page:card, page:section, page:footer and page:sidebar refuse a node-level
children by name and restate body with the same message, which names
properties.children, the member their @objectstack/spec row declares. The
spec's own PageComponentSchema refuses the node-level spelling as an
unrecognized key, so objectui validate now agrees with os validate. The
renderers are untouched.

objectql.zod.ts's two public-block arms import the one propsBag helper
instead of a byte copy of it; the member's description is unchanged.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types tests labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 32 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/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/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.

.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/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/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/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/8478-describe-line-addresses.md

.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.

.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/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/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/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.

Compared the checked-out tree with c3df43a42 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1813 pending declaration(s) that publish a body (2423 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3575.9 KB 3607.4 KB
Main entry chunk (gzip) 149.7 KB 350 KB
Entry file index-D6caIEdH.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.54KB 3.46KB
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) 568.99KB 136.03KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.19KB 66.27KB
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) 40.88KB 11.46KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 240.43KB 63.18KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.44KB 15.11KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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) 19.82KB 7.15KB
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: 573373f9313cd9d9292265422a6f3be0cb6d3d3a
Local-runs: none

Inputs, and nothing else: card objectui#10872 (body and all 40 comments, 5861624206 through 5913225209); PR objectui#11222 (body, the 9-file list, and the net diff of the head against merge-base e6bc087a30: 9 files, +376 / −37, matching the file list); the head's check-runs; and, for every spec claim, git show 0f6dcac5e9:PATH (the @objectstack/spec@17.5.0 tag commit). Read-only: no checkout, build, test, gate or ablation ran here.

① Derived judgments

The ruling and the fork prohibition (claim 5912278360, applying triage 5906817482 A to the four containers). The diff stays inside it: the four arms refuse the node-level spelling and point at the spec row's properties.children; no flat channel is declared (B is not taken); no renderer, sdui-parser or vscode-extension file is touched; index.zod.ts and the ratchet file are absent from the diff. The four premises, by what I re-read versus what I accept on the report's word:

  • (1) re-read at the tag. packages/spec/src/ui/page.zod.ts:266 — PageComponentSchema is a lazySchema over a strictObject (surface this page component) whose members are type, id, label, properties, events, style, className, responsiveStyles, visibleWhen, visibility, dataSource, responsive (a retiredKey) and aria. No node-level children member exists, so the strict shape refuses children as unrecognized on any page component, the four included, independent of type. Holds by shape; I did not run a parse.
  • (2) re-read at the tag. component.zod.ts:5259 / 5262 / 5263 / 5265 map page:card to PageCardProps and page:section / page:footer / page:sidebar to PageContainerProps. PageCardProps.children (:836) and PageContainerProps.children (:439) are z.array(z.unknown()).optional(); PageCardProps.body (:842) is a retiredKey whose text says "Rename the key to children". Holds.
  • (3) accepted on the report's word. The runtime probe (both markers rendered in order, DOM byte-identical to the node-level spelling, 0 markers without children) was one-off and never committed, and no pin in this PR re-derives it. What I could read agrees with the mechanism claimed: packages/react/src/SchemaRenderer.tsx at the merge-base carries the properties hoist ("COMPAT: Hoist 'properties' up to schema level", :1552; type / id excepted, :147). See ③ 3.
  • (4) accepted on the report's word. The census numbers (0 non-test node-level writers of the four in objectui and in objectstack 72f8c38201; 7 objectui test fixtures; lit controls card 88 / flex 251 / flex 2) are the instrument's. I confirmed the instrument the changeset names exists: census:body-dialect at root package.json:54. The nearest independent check is CI: an app-shell render fixture that also validated would redden a test shard.
  • (5) the fold, read in the diff. objectBlockPropsBag is deleted from objectql.zod.ts, both call sites use the imported propsBag, and the description string is pinned word-for-word (pin group d). public-blocks.zod.ts imports only ./base.zod.js, ./imported-defaults.js and ./tombstone.zod.js, so the new objectql.zod.ts → public-blocks.zod.ts edge closes no import cycle. Right.
  • (6) the ratchet test is not in the diff. Right.

Accept-set changes, each named. On PageCardBlockSchema, PageSectionBlockSchema, PageFooterBlockSchema and PageSidebarBlockSchema:

  • children: from BaseSchema's z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional() (base.zod.ts:391, recursive through nodeUnion) to retirementTombstone(PAGE_*_CHILD_LIST), which is z.never({ error }).optional().describe(…) (tombstone.zod.ts:64). A narrowing on both faces, and nested in a page (pinned at children.0.children). Right under the ruling.
  • body: from BaseSchema's aliasKeyRefusal (base.zod.ts:380, remedy "→ children") to the same tombstone. Refuse-to-refuse; message only. Right.
  • properties.children: unchanged, the row by reference. Right.
  • Every other arm and AnyComponentSchema's member list: untouched. objectql.zod.ts's two arms: properties description byte-equal by pin. Right.

Nested nodes under the four (the seat's question; dev out_of_scope_findings[0], PR acceptance note 1). Measured from the arms, not the report. At the merge-base a node-level children was judged recursively — SchemaNodeSchema is a z.lazy over nodeUnion (base.zod.ts:288) — so { type: 'page:section', children: [X] } refused a malformed X; the bag's children was z.array(z.unknown()) from the row, so the same X under properties.children passed. At the head the node-level spelling is refused by name whatever X is, and the bag is unchanged. So, precisely:

  • (a) on the contract spelling the gap is pre-existing: objectui's faces have never judged a nested node inside the bag, since batch 1 armed the rows by reference;
  • (b) the diff does remove the one spelling on which main judged nested content — and that spelling is the one PageComponentSchema refuses, i.e. the second dialect ruling A forbids;
  • (c) after this head no accepted spelling on the four is judged nested by safeValidateSchema or StrictAnyComponentSchema. That is the state page:tabs and page:accordion have been in on main since objectui#9256: their node-level children is refused and items[].children is the same z.array(z.unknown()).
    Keeping the node-level channel to keep the judgment would be option B. Closing the gap on the bag means judging more than the spec row does — a row override, or a bag walk like the spec lint's (packages/lint/src/page-walk.ts at the tag walks properties.children and properties.items[i].children by hand; objectui has no equivalent, grep at the merge-base: 0 non-test reads of properties.children under packages/types/src or packages/cli/src). No ruling on this card has made that decision. Judgment: not a regression this PR must close; a coverage loss it must state, and it states it. It is a class-(a) public-door finding the dev did not file, so ③ 1 escalates it to the seat with the reach written as (b) and (c), not only as "pre-existing".

The four declared surface widenings, and the parity strings.

  • content-channel-public-blocks-9256.test.ts (+9 / −4): the four CONTROL rows asserted the node-level accept this batch narrows; they now assert the bag spelling parses on the mirror and on AnyComponentSchema, and that the refusal is not the neither-channel text. Forced by the diff; the control keeps its purpose. Right.
  • record-alert-flat-body-remedy-10872.test.ts (+5 / −2): the control moves from page:card to card. Checked at the head: CardSchema (layout.zod.ts:410) declares its own body: aliasKeyRefusal('body', 'children', 'this card node', …) and a recursive node-level children, so the three assertions hold on card's own member rather than BaseSchema's — the new title says "the objectui#6771 remedy" and drops "BaseSchema's", which is accurate. Forced, non-vacuous. Right.
  • packages/types/src/zod/README.md (1 sentence): accurate after the diff; the old sentence would have misdescribed the four. AGENTS.md Add automated testing infrastructure and CI/CD workflows #2, the batch-5 precedent (5908946678). Right.
  • .changeset/10872-public-block-zod-arms.md (+2): a dated note appended at the file's end; frontmatter untouched. The bullet it corrects is quoted accurately. The dated-note precedent of batches 2–4 on the same file. Right.
  • zod-mirror-parity.test.ts (+4 / −4): four reason strings, text only, on exactly the four rows this batch changes — the claim's allowance. Right.

The propsBag export. export function propsBag on public-blocks.zod.ts; index.zod.ts does not re-export it (0 hits at the head); the package exports map has ./zod → ./dist/zod/index.zod.js and no wildcard, so no deep path publishes the module. Not a public-surface change. Right. The parity census counts export const, so the helper neither needs nor dodges a row.

Construct, a nit only. The helper is retirementTombstone, though these two keys are not retired: the renderers keep reading node-level children for stored documents, as the composed message itself says. aliasKeyRefusal (batch 3's choice for a key that moved paths) would name the semantics better. The primitive, code, path and accept set are identical either way, and nothing harvests retirementTombstone call sites into a ledger (internal/retired-field-keys.ts is hand-maintained; the only scripts naming the helper are the changeset-polarity census and check-handler-key-read-sites.mjs, both success on the head). Not a finding.

Check-runs on the head. Read twice while rendering this record: at the first read 42 runs stood at 33 success, 3 expected skips (dependabot, the two coverage matrix rows) and 6 test shards still in progress; at the final read, immediately before this write, all 43 runs are concluded — 40 success, 3 expected skips, 0 failure. Success includes every Test (shard N/8) and the Test aggregate, Type Check, Spec Main Shape Gate, Lint, Test (dist pins), Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, README Export Check, Doc Snippet Type Check, Governed Surface Queue Guard, Control Byte Scan and Line Citation Gate. Those conclusions are the gate verdicts this record relies on; nothing was run locally.

② Semver level

.changeset/10872-container-children-channel.md declares '@object-ui/types': minor, and the PR body's second line is Clause-②: yes, matching claim 5912278360. The diff narrows the published @object-ui/types/zod accept set on four exported arms against main, so a changeset is owed and Clause-②: yes (narrowing) is the right declaration. minor for a narrowing follows the objectui#9256 and batch-5 precedent (ACCEPT 5908946678) under the repo's no-major policy; Changeset Bump Policy and Changeset Fixed Group Check are success on the head.

The prose, line by line:

  • Lead sentence: accurate.
  • "What it was": BaseSchema.children is recursive at base.zod.ts:391, so both faces passed the node — true. The spec refuses it as unrecognized under ADR-0089 D3a — true at the tag (① premise 1). A flat body was refused by BaseSchema with a remedy naming the node-level children — true (base.zod.ts:380).
  • The four "observable" bullets: each is pinned in container-children-channel-10872.test.ts groups (a)–(c), and the quoted first sentence matches pageContainerChildListGuidance in the diff.
  • "Nothing renders differently": no renderer is in the diff — true. The hoist — read at the merge-base. The designer-canvas and synthesizer claims — the report's word (premise 4). pnpm census:body-dialect — exists.
  • "Why minor, and why this breaks no published document": that the batch-1 arms are unreleased is evidenced by this very diff — .changeset/10872-public-block-zod-arms.md is still a pending changeset. The npm 17.6.0 dist readings are the report's word. Given unreleased arms the conclusion follows: 17.6.0 refuses all four types at type; the next release accepts the bare node and the bag spelling and refuses the node-level one, which 17.6.0 also refused. "Across the release, the accept set only widens" is true for the four types.
  • The helper-fold paragraph: "no behaviour change … description text unchanged" — pinned (group d).
  • Migration: accurate.

③ Boundary flags

open_questions: []. Every dev deviation, finding and PR acceptance note, answered:

  1. Nested nodes under properties.children are unjudged by objectui's faces (class a, public door, measured; ① above). Not blocking under ruling A. ESCALATED to the seat: file it as its own card before this card is next released to pm:queue, stating (i) main judged the node-level spelling recursively and this head judges neither spelling on the four, (ii) page:tabs / page:accordion items[].children have been in the same state since objectui#9256, (iii) the spec closes it at the lint layer (page-walk) and objectui's safeValidateSchema has no bag walk. Dedupe words as the dev gave them: properties.children nested node not validated · bag slot children z.array(z.unknown()) objectui validate · items[].children unjudged safeValidateSchema.
  2. The sdui-parser manifest tier and the VS Code validator accept a node-level children on the four that both zod faces now refuse: a pre-existing tier disagreement, the batch-3 record:alert pattern (5882754723, 5882953316); the order excludes both packages. Carrier stays this card's flat-props / envelope-key batch, whose order names both tiers. Non-blocking.
  3. Premise 3 has no committed pin. Accepted on the report's word (① 3): @object-ui/types imports no renderer and runs in the node project; the pin's home is packages/components/src/__tests__, outside the claimed surface. The seat may order it as a small follow-up with that surface. Non-blocking.
  4. The two test-control rewrites, the README sentence, the dated note and the four parity strings: ratified (① above).
  5. Construct (retirementTombstone with a new composer, not aliasKeyRefusal): stated by the dev; identical accept set; nit only (① above).
  6. propsBag exported at module level, not on the barrel: judged not public (① above).
  7. pnpm --filter '@object-ui/types^...' build exits 1 on an empty closure: not a red; the @object-ui/types build is exit 0 in the report and Test (dist pins) is success on the head.
  8. The objectstack fetch side effect (a scratch ref, deleted; the shared checkout's origin/main advanced, no file edited) and the merge of main (a merge, not a rebase; PR objectui#11207 and five others, none touching a file of this diff): fine.
  9. PR acceptance note 4 (skills/objectui/guides/page-builder.md says the thin containers render "around their children"): governed prose naming no node spelling; noted, no action.
  10. Serial: the objectui#9547 branch edits other regions of objectql.zod.ts and zod-mirror-parity.test.ts; this diff's hunks there are the helper region and the four container rows. Whichever lands second merges main.

Implemented-by: claude/issue-10872-container-children-channel
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: PASS

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.

2 participants