Skip to content

fix(layout,types): an app-schema-renderer node draws the app document it carries under schema (objectui#11494) - #11501

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-11494-app-schema-renderer-schema-input
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-11494-app-schema-renderer-schema-input

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11494

Clause-②: yes

An app-schema-renderer node now draws the app document it carries under schema, and AppSchemaRendererNodeSchema declares schema as that document, by reference. This executes triage ruling A (comment 5958227972) on the card. The standing ruling that this node is "the one JSON door for 'render a whole shell from a schema'" (objectui#4841, 5307574602) is unchanged. Builds on objectui#11440 (PR objectui#11493, landed as 00ccdf742, this branch's base).

What was wrong (measured at the base, 00ccdf742)

SchemaRenderer strips a node's schema key out of the props it spreads, and hands every registered component the node itself as its schema prop. registerLayout() registered AppSchemaRenderer directly, so the component took the node for its app document. Rendered through the real SchemaRenderer and registry at the base, with this PR's new layout pin file and the source untouched:

  • { type: 'app-schema-renderer', schema: { type: 'app', name: 'crm', navigation: NAV } } drew no navigation: expected 'App' to contain 'Accounts11494'.
  • The same navigation written flat on the node drew: expected 'crmAccounts11494' not to contain 'Accounts11494'.

What changed

  • @object-ui/layout (packages/layout/src/index.ts): app-schema-renderer is registered against AppSchemaRendererNode, a module-private adapter in the DetailSectionNode shape. It reads node.schema off the node SchemaRenderer already hands it, and gives that to AppSchemaRenderer. Every other prop (basePath, mobileNavMode, host props) passes through unchanged. There is no fallback to the node's own keys. A node without schema hands the component an empty { type: 'app' } document, which draws the shell with no branding and no navigation, as such a node drew before. AppSchemaRenderer, its props and the registration's inputs are unchanged.
  • @object-ui/types (packages/types/src/zod/app.zod.ts): AppSchemaRendererNodeSchema gains schema: AppComponentSchema.optional(). It is the same schema object, with no copy of its members.
  • content/docs/guide/layout.md: the passage now says what the node does, with a JSON example (checked once by hand on both faces, see Gates). The children sentence ("takes the page content as its children"), which the probe did not reproduce, is replaced by what holds: the node's children are not rendered, and objectui validate refuses them on this node.
  • Tests: two new pin files, the objectui#11440 rows moved on purpose, and two fixtures respelled to the nested form (below).
  • Changesets: @object-ui/types minor and @object-ui/layout minor, the latter marked breaking for a node that writes app keys flat on itself.

Contract notes (for the at-tier review)

  • Seam: the adapter reads props.schema.schema. SchemaRenderer's strip list is not touched. SchemaRenderer already delivers the whole node as schema, so no wider seam was needed. Nothing new is exported: the adapter is private to index.ts.
  • "By reference" means the nested document carries "type": "app". AppComponentSchema requires the app literal, so { schema: { name: 'crm' } } is refused at schema.type (invalid_value). A typeless variant would be a derived schema (.omit / .partial), not the document by reference, so it was not taken. The TypeScript prop AppSchemaRendererProps.schema already requires type: 'app' of JSX hosts.
  • Accept set, per face.
    • Strict face (StrictAnyComponentSchema): widens. A node with its document under schema validates; it was refused with unrecognized_keys naming schema. The nested document is closed like every object on this face (schema.brading is refused by name).
    • Tolerant face (safeValidateSchema, which objectui validate runs): schema used to pass unjudged and is now judged as the app document. A malformed nested value is refused: no type: 'app', a non-object, or mobileNavMode inside the document (the objectui#11363 tombstone). That is a narrowing for a malformed nested document on this face. The producer reading below found no such document.
  • One spelling. The document's keys written flat on the node stay undeclared. The strict face refuses them as unrecognized_keys (navigation, title), and the adapter does not read them. The tolerant face keeps its passthrough for them, so objectui validate alone does not refuse them. The guide says the strict face does, and nothing more.
  • Optional, not required. The registration input is not required, and the node the governed mobile guide teaches ({ type: 'app-schema-renderer', mobileNavMode: 'bottom_nav' }) carries no document. It still validates on both faces, and still draws: both are pinned.
  • Producers. objectstack at 4e6dc233: git grep app-schema-renderer finds one hit, a CHANGELOG line, and no node producer (control word page:header hits in the same tree). objectui: no doc, example or catalog document authors the node with a document, flat or nested. The only authored nodes are test fixtures, two of them respelled here.
  • A pending changeset this PR makes false. .changeset/11440-arm-passing-types.md (unreleased) says the registration's third input, schema, "is not declared". This PR's types changeset says that sentence no longer holds. The 11440 body is outside this claim's file surface and was not edited; the report names the correction.

Pins

  • packages/layout/src/__tests__/app-schema-renderer-schema-input-11494.test.tsx, through the real SchemaRenderer and registry:
    • navigation under schema draws, and the same navigation flat on the node draws nothing;
    • title under schema heads the sidebar, and flat it does not;
    • basePath and mobileNavMode are unchanged: the bottom bar's tab is /apps/crm/account, and the drawer control draws no bar;
    • a node without schema draws the empty shell with no error banner;
    • the registration publishes schema as an object input, and the arm declares it.
  • packages/types/src/__tests__/app-schema-renderer-schema-input-11494.test.ts, on both faces, each refusal with a lit control:
    • the member unwraps to AppComponentSchema itself, and is optional;
    • a nested document validates beside basePath and mobileNavMode, and a node without schema validates;
    • flat keys are refused strict as unrecognized_keys (the same keys nested pass);
    • mobileNavMode inside schema is the app document's tombstone (on the node it is clean);
    • an untyped document is refused at schema.type, and the strict face closes the nested document.
  • Moved on purpose, under the ruling: the objectui#11440 layout row "schema is an input the arm does not declare — and a node never delivers it to the component" (its file header now points at the 11494 file), and the types row "the registration's schema input is not declared" (now a one-line toContain('schema') with a pointer).
  • Fixtures respelled (each only used the flat or untyped spelling): the node in app-mobile-nav-mode-refusal-11363.test.ts nests its document, so its comparisons run against a clean baseline on both faces. The schema-catalog control node's document gained type: 'app'; without it, that node is refused on both faces.

Reverse verification

  • Predicted: red at the base, and ablating the adapter turns exactly the four drawing rows red.
  • Base (00ccdf742, source untouched, the new pin files only): Tests 13 failed | 4 passed (17).
  • Ablation on the committed fix, through objectstack's scripts/ablation-replace.mjs, which reverts the registration to AppSchemaRenderer:
    • anchor count 1 to 0, blob d66737e2b8e7 to 1391f799016e;
    • Tests 4 failed | 2 passed (6): the four drawing rows, as predicted;
    • restore: blob == HEAD (d66737e2b8e7) and git diff HEAD is empty.

Gates (final head 44a67d8fc)

  • Tests (pnpm exec vitest run --maxWorkers=2 packages/layout/ packages/types/, plus the 15 tests outside those two packages that name app-schema-renderer, AppSchemaRenderer, AppComponentSchema, mobileNavMode or layout.md, plus scripts/__tests__/check-doc-component-types.test.ts): Test Files 391 passed (391), Tests 9710 passed (9710). That set includes the ratchet registered-types-validate-ratchet-10859.test.ts, which this PR does not touch, and the zod-parity and strict-face suites in packages/types.
  • Type-check, @object-ui/types and @object-ui/layout: both scripts echoed type-check: Done, exit 0. They ran on ebb7d21ba; git diff --stat ebb7d21ba HEAD -- packages/ is empty. --listFiles shows both test programs include the touched test files. @object-ui/example-schema-catalog type-check: exit 0 on 44a67d8fc.
  • pnpm check:doc-snippets, after its --build-filter closure build: Semantic phase: 776 of 776 block(s) judged, 0 failed. It ran on 25c91e58b; only a test file changed after that.
  • pnpm check:doc-examples: Every covered @example compiles, or fails exactly as its ledger row declares.
  • pnpm check:doc-types: Every documented component type is registered.
  • node scripts/markdown-test-inputs.mjs --audit: 115 candidate test files, all adjudicated; 82 declared entries, all present.
  • pnpm check:control-bytes: OK (scanned 10092 tracked text file(s); skipped 85 binary).
  • pnpm check:new-line-citations: 0 new citation(s).
  • node scripts/check-changeset-presence.mjs: 9 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s).
  • pnpm check:changeset-claims: report-only, exit 0. It names 21 pending changesets that cite a touched file; each paragraph was read, and none is falsified. The one this PR does falsify (11440, above) names no file, so this gate cannot see it.
  • eslint over the 9 changed .ts / .tsx files (--no-inline-config --format json): 9 files, 0 errors, 4 warnings, all on lines this PR does not touch (blame). Three facts prove the narrowing:
    • population: eslint itself answers "File ignored because no matching configuration was supplied" for the 4 changed .md files;
    • count: the JSON output lists 9 files;
    • invariance: --print-config shows no parserOptions.project or projectService (not type-aware), and no rule in eslint-rules/ reads the filesystem, so this diff cannot move any untouched file's verdict.
  • The guide's new JSON example, checked once against the built @object-ui/types dist (not a permanent test): tolerant true, strict true. With the document flat on the node: strict false (unrecognized_keys on title, navigation), tolerant true, which is what the guide says.
  • node scripts/check-governed-queue-guard.mjs --test over the 13 paths: NOT GOVERNED.
  • Patch round 1 (head 6d47fdb9e): AppSchemaRendererNodeSchema is annotated with AppSchemaRendererNodeSchemaType, written by reference to AppComponentSchema (the PageKindNodeSchemaType precedent), so declaration emit names the nested document instead of re-serializing it inside AnyComponentSchema. Type only; the accept set is unchanged. Spec Main Shape Gate reproduced locally against objectstack 9b7a0ef3faf1: red without the annotation (index.zod.ts 590 TS7056 x2), green with it. The pending objectui#11440 changeset carries a dated note.

Acceptance notes

  • .changeset/11440-arm-passing-types.md should drop the sentence that says the registration's third input, schema, "is not declared" and the strict face refuses it. Out of this claim's surface; the report names the replacement.
  • The children / body refusal message on this node says SchemaRenderer strips both "out of the props it hands AppSchemaRenderer". The props now reach the adapter, which forwards no node children, so the sentence still holds in substance. It is published refusal text, and is left as it is.
  • packages/layout/README.md ("the key for that is app-schema-renderer") stays true and is not edited.
  • objectui validate runs the tolerant face, so flat app keys on this node pass it, as any undeclared key passes on any node. That is the two-face design objectui#5250 ruled, not this card's.

Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 (the domain:ui seat 1 dispatch; dev run under the PM's claim 5958336981).


Generated by Claude Code

claude added 4 commits October 2, 2026 18:20
… it carries under `schema`

The registration published a `schema` input no node could deliver:
SchemaRenderer strips a node's `schema` key and hands the registered
component the node itself, so AppSchemaRenderer took the node for its app
document. A document nested under `schema` drew an empty shell, and only
undeclared app keys written flat on the node drew.

Under triage ruling A, `registerLayout()` now registers the key against an
adapter (the DetailSectionNode shape) that hands `node.schema` to
AppSchemaRenderer, and AppSchemaRendererNodeSchema declares `schema` as
AppComponentSchema by reference, optional. The document keeps its own
refusals (mobileNavMode inside it), the flat spelling is refused by the
strict face and no longer read, and basePath / mobileNavMode are unchanged.
The layout guide says what the node does, its `children` sentence included.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…ral the actions census counts

The handler-keys census counts files that name `AppComponentSchema` and read
`.actions`; the README already carries `properties.actions`, so naming the
type there added a third prose file to its population.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
… page entry

The doc component-type gate reads every `type` literal in a fence, and
`object` is a navigation kind the gate exempts per page; layout.md carries
no such exemption. A `page` entry teaches the same nesting with a key the
gate resolves.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…ped app document

The fixture's document had no `type: 'app'`, which AppSchemaRendererNodeSchema
now judges as the app document and refuses at `schema.type`. The manifest
reading the test makes is unchanged; the node is now clean on every face.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 21 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/11355-small-p1-sites-r2.md

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

    ObjectChartSchema.isAnimationActive?: boolean is declared on the TypeScript face. Code sets it false for a render with no entrance animation: DashboardRenderer and DashboardGridLayout on the object-chart nodes they build, and DatasetWidget and DatasetReportRenderer on the nodes they hand the chart registration. ChartRenderer honours it. The zod mirror declares no member for it, so it is not an authoring key; zod-mirror-parity.test.ts files it as runtime-only.

.changeset/4895-retire-block-schema-family.md

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

    ⚠️ Not this family, and not touched. The live slotted record-page vocabulary — PageNodeSchema.kind === 'slotted' with slots?: PageSlotMap (packages/types/src/layout.ts), rendered by usePageAssignment / PageBlockCanvas / PageBlockInspector in @object-ui/app-shell — shares the words "block" and "slot" with the retired family and shares no declaration, type or file with it. Neither is the type: 'component' NAVIGATION item kind ({ type: 'component', componentRef }, NavigationItemSchema in zod/app.zod.ts), a different declaration in a different module.

.changeset/5903-objectgantt-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 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/6872-app-shell-branding-title-jsdoc.md

  • names content/docs/guide/layout.md → content/docs/guide/layout.md — edited by this change

    Correct the AppShellBranding.title doc comment. It read "Page title suffix (sets document.title)" while useAppShellBranding assigns document.title = title wholesale — nothing is appended; the caller composes the whole string (the console passes "App label — Product name"). That comment ships in dist/index.d.ts and is the only description a consumer sees on editor hover, so a reader who followed it passed a suffix-only fragment and got a truncated title with no error. The comment now carries the same wording as content/docs/layout/app-shell.mdx, and agrees with the AppShellProps tables in the package README and content/docs/guide/layout.md. No runtime behaviour changes; the wholesale assignment and the four-surface agreement are now pinned by tests.

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

  • names app.zod.ts → packages/types/src/zod/app.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/7344-handler-string-any-mirrors.md

.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/7735-zod-mirrors-stop-authoring-defaults.md

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

    What changed. All 41 .default() call sites under packages/types/src/zod/ are removed — layout.zod.ts 22, crud.zod.ts 11, form.zod.ts 5, views.zod.ts 2, app.zod.ts 1. @object-ui/components reconciles the third face a separate finding found: flex's registration defaultProps.align seeded 'center', the value its own renderer never applies, so a designer-made node laid out differently from a hand-authored one; it now seeds 'start'.

.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/7918-zod-lazy-getter-identity.md

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

    ⚠️ Also settled while locating the ten: AppMenuItemSchema has no declaration of its own — it is the barrel alias of app.zod.ts's MenuItemSchema, while the barrel's own MenuItemSchema is overlay.zod.ts's. Two different schemas, so the list really is ten entries and not nine.

.changeset/7926-page-node-refuses-actions.md

  • names content/docs/guide/layout.md → content/docs/guide/layout.md — edited by this change

    Scope. One key, by name; the node is NOT strict. A census over this tree read 91 authored page-tagged objects with a blind-spot reading of 8 unreadable sites, and found only actions (3 sites, all in content/docs/guide/layout.md) and breadcrumbs (1 site, its own question, untouched) surviving passthrough on a real page node — every other undeclared key belongs to a different declaration that merely spells type: 'page'. PageNodeSchema still passes unknown renderer props through.

.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/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/8871-page-node-refuses-breadcrumbs.md

  • names content/docs/guide/layout.md → content/docs/guide/layout.md — edited by this change

    Three author sites, all teaching passages in content/docs/guide/layout.md, and that count corrects the actions refusal's "1 site": its census reads every git-tracked JSON file, every json fence in .md/.mdx, and every TS/TSX object literal via the TypeScript AST (PR fix(types,docs): refuse actions by name on the page node, teach the shape that draws (objectui#7926) #8870), and it undercounted for two different reasons. The Schema API block declared breadcrumbs?: ArrayANGLE-BRACKETS({ label, href }) outright and its literal does carry type: 'page', but that literal sits inside a markdown typescript fence — a fence language the census's json-fence reader never visits, so it was never read at all. Best Practices §2 authored it on a fragment inside a json fence the census does read, but that fragment never writes type, so a page-tagged filter correctly excluded it. No example app, catalog fixture, template or customer document writes the key, so the refusal strands no authored document in this tree.

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

  • 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/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 ea3914139 (merge-base with origin/main): 11 file(s) changed outside .changeset/, read against 2044 pending declaration(s) that publish a body (2673 pending in total). · run

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3551.1 KB 3574.6 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-DCsElfBh.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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 571.20KB 136.84KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.51KB 66.22KB
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.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.53KB 22.84KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.20KB 38.29KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.43KB 64.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 233.13KB 63.89KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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) 120.63KB 39.56KB
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.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
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) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.32KB 1.64KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 5.07KB 2.39KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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: 44a67d8fce7974a65ae15a7db62669cc1d31e317
Local-runs: none

PR objectui#11501, Fixes #11494, at 44a67d8fc: four commits on 00ccdf742, the merge base with main. Judged at 2026-10-02T19:26Z on: the card body and every comment on objectui#11494 (triage's ruling A 5958227972, the claim 5958336981, the dev report 5959586517, and the seat's ACCEPT 5959621012, the last read as a claim to test, not as an input); objectui#4841's standing ruling 5307574602; the predecessor record 5958122327 on PR objectui#11493; the PR body and file list; the net diff of the head against its merge base (13 files, +405 / −57, byte-identical to the three-dot diff against origin/main); the registration, the arm, SchemaRenderer's strip and AppSchemaRenderer's props at the head, read with git show; and the check-runs on the head. Nothing was built, run or re-run locally.

Check-runs on the head: 43, every one completed as of 2026-10-02T19:24Z (polled seven times at ≥60 s intervals from 2026-10-02T19:13Z; the aggregate Test was the last to appear, green at 2026-10-02T19:22Z): 39 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard)), 1 failure: Spec Main Shape Gate, completed 2026-10-02T19:15Z, 0 cancelled. Among the successes: Test with its dist pins and shards 1 to 8, Type Check, Lint, Build & E2E, Doc Snippet Type Check, Doc Component Type Check, Doc Example Id Check, README Export Check, Governed Surface Queue Guard, Line Citation Gate, Inert vi.mock Specifier Check, Bundle Analysis, Live E2E (informational) and the five changeset gates. Those conclusions are the gate verdicts here; the one red is attributed to this diff in ① item 12, and it decides the verdict.

① Derived judgments

  1. The seam. app-schema-renderer is registered against AppSchemaRendererNode, a module-private FC in packages/layout/src/index.ts that reads node.schema off its schema prop and calls AppSchemaRenderer with { ...shellProps, schema: node?.schema ?? { type: 'app' }, children }. Right, and the narrowest seam. packages/react/src/SchemaRenderer.tsx at the head hands every registered component schema: schemaForComponent, the evaluated node itself, and its metadata destructure strips schema, children and body out of the spread, so the nested document already arrives at props.schema.schema and nothing in SchemaRenderer or AppSchemaRenderer had to move. No export is added: the export * lines are unchanged and the adapter is a const.
  2. Node children. The guide now says the node's children are not rendered and objectui validate refuses them. Right. SchemaRenderer strips children from the spread and passes no React children to createElement(Component, …), so the adapter forwards children: undefined and AppSchemaRenderer's {children} slot draws nothing; the arm's children / body tombstones (objectui#11440) are untouched. The old "takes the page content as its children" sentence was false at the base, and its replacement is what the code does.
  3. The arm. AppSchemaRendererNodeSchema.schema is AppComponentSchema.optional(), the same schema object (pinned by unwrap() identity), so the document keeps its own refusals (mobileNavMode, logo, favicon, actions) and its type: 'app' literal. Right, and the ruling's "by reference" admits nothing else: a typeless variant would be a derived schema. The TS twin AppComponentSchema (packages/types/src/app.ts) already requires type: 'app', and AppSchemaRendererProps.schema is that type, so the adapter's { type: 'app' } default type-checks (Type Check green).
  4. Accept set, strict face (StrictAnyComponentSchema): widens. A node carrying its document under schema validates where it was refused with unrecognized_keys naming schema; the nested document is closed by the walker (pinned: brading refused by name). Flat document keys on the node stay undeclared and are refused by name (navigation, title, pinned): one spelling on this face. Right.
  5. Accept set, tolerant face (safeValidateSchema, the objectui validate door): schema passed unjudged and is now judged, so a nested value that is not an app document is newly refused at its own path: a non-object (invalid_type at schema), a document without type: 'app' (invalid_value at schema.type), mobileNavMode inside it (invalid_type at schema.mobileNavMode). Right reading, and rightly declared in the types changeset with those paths and codes. The producer reading holds at the head: git grep finds no app-schema-renderer node outside tests and the two guides; skills/objectui/guides/mobile.md teaches the no-document node, which still validates on both faces (pinned); apps/site's registerLayoutBlocks.ts registers the key and authors no node. The one previously passing document in the tree was the schema-catalog test control { schema: { name: 'crm', navigation: [] } }, respelled in this diff with type: 'app': a fixture, not a producer. Flat keys still pass this face, as every undeclared key does, so a flat author now gets a shell with no navigation and no validator word; the guide claims only that the strict face refuses them, which is true (objectui#5250's two faces, not this card's).
  6. Optional, not required. The registration's schema input carries no required, the arm is .optional(), and a node without it hands the component { type: 'app' }: the empty shell such a node drew before, with no error banner (pinned). Right. basePath and mobileNavMode reach AppSchemaRenderer unchanged (pinned: the bottom bar's one tab is /apps/crm/account; drawer draws no bar).
  7. Registration inputs unchanged (schema object, basePath string, mobileNavMode enum) and isContainer: true kept. Right: the card did not open that, and the parser tier's not-a-container reading (objectui#9910) is as before.
  8. Public surface. @object-ui/layout: nothing exported or removed; the component behind the key is now a wrapper, which no host imports by the key. @object-ui/types: AppSchemaRendererNodeSchema.shape gains schema; no TypeScript node type is added (none restates this node, per the parity ledger's amended EXCLUSIONS row). known-schema-types.ts untouched: right, no key moves. The zod README and the docblocks are made true. Right.
  9. Moved rows and fixtures. The objectui#11440 layout row and types row are inverted into the two 11494 pin files, each with a pointer; the objectui#11363 node nests its document so its comparisons run against a clean baseline on both faces. Right, under the ruling; the diff changes nothing else in those files.
  10. Reverse verification as reported (base 13 red / 4 green; ablation 4 red, exactly the drawing rows; restore proven): consistent with the diff, not re-run here. "Navigation under schema draws" and "flat draws nothing" are the two pins the ruling asked for, both through the real SchemaRenderer and registry.
  11. Merge with main. main is three commits past the merge base; one (0d723a33f, objectui#6152 round 6) touches zod-mirror-parity.test.ts at hunks between lines 372 and 3246, while this diff edits line 4713: a clean queue-time merge, and that merge ref is what the gates compiled.
  12. Wrong: the declaration emit of @object-ui/types/zod against @objectstack/spec built from objectstack main. Spec Main Shape Gate on the head is failure, and its annotations attribute it as the gate's contract requires: packages/types/src/zod/index.zod.ts:590, export const AnyComponentSchema = defineNodeComponentUnion(z.discriminatedUnion('type', [...])), TS7056 "The inferred type of this node exceeds the maximum length the compiler will serialize", twice, compiled against objectstack b94a2a727785; the eight TS7016 "Could not find a declaration file for module @object-ui/types/zod" in packages/cli and packages/core tests are its downstream (no d.ts was emitted), and the one TS7006 follows from those. The mechanism is the one objectui#11440 met and wrote down at packages/types/src/zod/layout.zod.ts (the PageKindNodeSchemaType docblock): a shape written into an arm of AnyComponentSchema is re-serialized inside the union by declaration emit, and this diff writes the whole AppComponentSchema (navigation, areas, menu, context selectors) into the app-schema-renderer arm, a second copy beside the app arm. It is this diff's, not drift's: main at b654d4ed5 ran the same gate green, completing at 2026-10-02T19:02Z, and the only objectstack commit between the two runs (9b7a0ef3) changes a README under packages/spec/liveness and a changeset, so the spec shape was the same. Type Check on the head is green against the published spec; against the spec at objectstack main the same union no longer serializes, which is what this gate exists to see first, and the next spec release would make the published Type Check red too. The known remedy is the precedent's: a named type for the arm or for its schema member, emitted by name rather than inlined, then this gate green on the new head. ⛔ Not re-judged here: the conclusion is the verdict, and it is red on a line this diff feeds.

② Semver level

  • .changeset/11494-layout-app-schema-renderer-adapter.md: @object-ui/layout minor, the break spelled out in the body ("Breaking for a node that writes the app document's keys flat on itself"). Right. The repository's version policy pins objectui's major to @objectstack's and ships objectui's own breaks as minor with the break in the body (scripts/check-changeset-no-major.mjs; Changeset Bump Policy green). The break is real for an undeclared spelling that drew (flat navigation / title on the node no longer draw), the migration is one move, and no producer authors it; the entry says all three.
  • .changeset/11494-types-app-schema-renderer-schema.md: @object-ui/types minor. Right. The strict face widens by one declared member; the tolerant face narrows only for a nested value that is not an app document, named with its paths and codes; no previously valid document outside one test fixture is refused, and no TypeScript type narrows. The entry's last sentence says objectui#11440's pending sentence no longer holds. The prose matches the refusal sites read in the diff.
  • examples/schema-catalog is an example, not a released package; packages/layout and packages/types tests ride with their packages. check-changeset-presence: 9 source files of 2 released packages, 2 changesets. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read and Changeset Overwrite Report are green on the head. skip-changeset: not used, rightly.
  • Clause-②: yes. Right: the accept set moves on both faces of a published validator door, and body line 3 carries it; the claim, both changesets and the report say the same.
  • The gate red in ① item 12 is not a semver question: the fix it needs (a named type) changes no accept set and no export, so the two entries stand as written once it lands.

③ Boundary flags

Dev open_questions:

  1. Typed nested document (A, as landed) or a typeless derived variant (B). Answered A by the seat in 5959621012, and A is right: the ruling says by reference, the TS prop already requires type: 'app', and B would mint a second shape of the app document. Nothing more is owed.

Dev deviations:

  • engine-strict=false install; the adapter inside index.ts through createElement (the file is .ts), not exported; the background doc-snippet build; the serial gate honoured (the branch was cut on 00ccdf742 after PR objectui#11493 landed, confirmed by the merge base): mechanics, no contract effect.
  • File-surface readings: the four test files and the zod README sit inside the claim's "The tests" and packages/types/src/zod/ lines; AppSchemaRenderer.tsx was not needed and is untouched. Accepted.
  • Commit trailers: the four commits carry the model-free pair, and no model identifier is in the diff. Accepted.

Dev out_of_scope_findings and the PR's acceptance notes:

  • .changeset/11440-arm-passing-types.md (pending at the head) still says the registration's third input, schema, "is not declared … the strict face refuses it as an unrecognized key", which this diff makes false; Changeset Claim Re-read cannot see it, because that entry names no file. The seat ruled that no dated note is owed since the same release prints the correction in the types entries. Flagged to the seat, the carrier the dev named: the practice on PR objectui#11493 was a dated note on each pending entry it falsified, and without one the release prints the two sentences side by side. The rework round this record opens can carry the one-sentence edit; whether it must is the seat's ruling, and it is not a FAIL condition here.
  • APP_SCHEMA_RENDERER_NEITHER_CHANNEL says SchemaRenderer strips children / body "out of the props it hands AppSchemaRenderer": the strip is now of the props handed to the adapter, which forwards neither, so the sentence holds in substance. Noted, no carrier.
  • packages/layout/README.md stays true; objectui validate passing flat keys is the two-face design (objectui#5250). Noted, no carrier.
  • The parity ledger's EXCLUSIONS row for the node is amended in place (no TS declaration restates the node): right, and the Test shards that run that ledger are green.

Why FAIL, in one line: every contract judgment above is right, and one gate on the head is red on a line this diff feeds (① item 12). The rework is the precedent's named type, on this branch; a new head gets a new record, and this one stands until then.

Serial: main has moved three commits past the merge base (① item 11); objectui#8347 (BaseSchema's index signature) is not among them, and per the claim whichever lands second merges main.

Implemented-by: claude/issue-11494-app-schema-renderer-schema-input
Reviewed-by: session_01YLg8XqWGJ785fwQ5v4pH37

VERDICT: FAIL

claude added 2 commits October 2, 2026 19:36
…it refers to the nested app document by name

Nesting AppComponentSchema into the app-schema-renderer arm made declaration
emit re-serialize the whole app document a second time inside
AnyComponentSchema, and tsc refused the union with TS7056 against
@objectstack/spec built from objectstack main (Spec Main Shape Gate). The arm
is now annotated with AppSchemaRendererNodeSchemaType, written by reference to
AppComponentSchema, on the PageKindNodeSchemaType precedent. Type only: the
schema and its accept set are unchanged.

Appends a dated note to the pending objectui#11440 changeset: its sentence that
the registration's `schema` input is not declared no longer holds.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3551.1 KB 3574.6 KB
Main entry chunk (gzip) 150.3 KB 350 KB
Entry file index-CwUvl4ZR.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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 571.20KB 136.84KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.51KB 66.22KB
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.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.53KB 22.84KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.20KB 38.29KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.43KB 64.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 233.13KB 63.89KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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) 120.63KB 39.56KB
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.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
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) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 4.14KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 5.07KB 2.39KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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: 6d47fdb9e11dd745def76576a632c6eb8e3f81c7
Local-runs: none

PR objectui#11501, Fixes #11494, at 6d47fdb9e: a delta record on the full record 5959852006 (head 44a67d8fc, VERDICT FAIL on Spec Main Shape Gate), which it supersedes; every judgment not named below stands as written there. Judged at 2026-10-02T20:11Z on: the REWORK 5959890499 and the patch-round dev report 5960220501 on objectui#11494 (the latter read as a claim to test); the PR body as edited by the seat (one patch-round line under Gates, no code); the two commits since 44a67d8fc, e5dc2df30 (the PR's one own commit this round) and 6d47fdb9e (the merge of main ea3914139); the net diff of the head against its new merge base, which is ea3914139, main's tip (14 files, +439 / −58, byte-identical to the three-dot diff); base.zod.ts, tombstone.zod.ts, strict-authoring-face.ts and the zod barrel at the head, read with git show; and the check-runs on the head. Nothing was built, run or re-run locally.

Check-runs on the head: 43, every one completed, read twice at 2026-10-02T20:06Z and 2026-10-02T20:09Z with identical sets (the aggregate Test was the last to complete, green at 2026-10-02T20:03Z): 40 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard)), 0 failure, 0 cancelled. Spec Main Shape Gate is success (completed 2026-10-02T20:00Z); so are Type Check, Test with dist pins and shards 1 to 8, Lint, Build & E2E, Doc Snippet Type Check, Doc Component Type Check, README Export Check, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan, and the five changeset gates. Those conclusions are the gate verdicts here, and none is red.

① Derived judgments

Items 1 to 11 of 5959852006 stand: the net diff's added and removed lines are byte-identical against the new merge base for twelve of the thirteen files judged there, and the thirteenth, app.zod.ts, differs only as item 12 below reads. What moved:

  1. Resolved: the declaration emit against @objectstack/spec built from objectstack main. e5dc2df30 annotates the arm, export const AppSchemaRendererNodeSchema: AppSchemaRendererNodeSchemaType = BaseSchema.extend({ … }), and adds the exported alias with its docblock; the file's diff is 25 insertions and the one replaced line. Type-only, read, not only type-checked:

    • The schema expression is byte-identical to 44a67d8fc's once the annotation is stripped from its first line (the extracted blocks hash the same). No runtime object changed.
    • The annotation states BaseSchema.extend({ … })'s type member for member. BaseSchema is BaseSchemaCore, a z.object({ … }).passthrough(), so its config is z.core.$loose, which .extend keeps and the annotation names. BaseSchemaCore's shape has type, body and children and none of schema, basePath or mobileNavMode, so Omit of exactly those three keys plus the six members is zod's own Extend of the two shapes: the three overridden members leave, the three new ones enter, every other base member (id, name, label, className, style, data, bind, the visibility and disabled keys, testId, ariaLabel) is carried by reference.
    • Each member type is the expression's: z.literal('app-schema-renderer') is ZodLiteral; AppComponentSchema.optional().describe(…) is ZodOptional of typeof AppComponentSchema (describe returns the same type); z.string().optional() is ZodOptional of ZodString; z.enum(['drawer', 'bottom_nav']) is ZodEnum over exactly those two literals, no third; retirementTombstone(guidance: string) is not generic and returns z.never({ error }).optional().describe(guidance), so ReturnType names exactly ZodOptional of ZodNever, the tombstone that refuses body and children on both faces.
    • Therefore z.infer and z.input of the arm are unchanged member for member. StrictAnyComponentSchema is derived at runtime by cloning each schema's _zod.def (strict-authoring-face.ts), on a byte-identical object, after the annotation has been erased, so the strict face cannot have moved. Type Check proves the expression is assignable to the annotation; the other direction is the reading above: no member is written wider than the expression produces, so the declared type loses no member and widens none. The runtime pins (unwrap() is AppComponentSchema, the member is optional, both faces' verdicts) are in the green Test shards.
    • Right. The remedy is the precedent's (PageKindNodeSchemaType), and it closes the red: Spec Main Shape Gate is success on this head. Which objectstack sha it compiled against: the job's log is not readable through this container's proxy (the results store answers 403, twice), and a green run leaves no attributing annotation (one runner-image notice only), so it is bounded by reading rather than quoted. The gate's resolve step reads objectstack commits/main at the job's start (2026-10-02T19:48Z); objectstack main was then 1fd56645af24 (committed 2026-10-02T19:27Z), and nothing landed on it in the minutes after, so the run compiled against 1fd56645af24. That tree includes 57cc6950628f, the one packages/spec commit since the red run's b94a2a727785 (crypto-provider.ts and the two contracts JSON), so the green is against a spec shape that has moved past the red one, not back toward the published one. The dev's local legs, against 9b7a0ef3faf1 (the red run's shape), were red without the annotation and green with it; CI's green on the later shape agrees; the differentiator is the annotation, and the diagnosis in 5959852006 stands as the record of what was wrong.
    • New public surface: AppSchemaRendererNodeSchemaType, an exported type of @object-ui/types/zod through the app.zod barrel. A type alias adds nothing to any accept set; the precedent's alias is exported the same way, and the objectui#11440 entry does not name it either, so the types changeset not naming this one follows the precedent. Right.
  2. The merge. main brought two commits since b654d4ed5: f68e0a080 (objectui#11483, the metric-card widget arm, in packages/types' dashboard schemas and zod-mirror-parity.test.ts) and ea3914139 (objectui#11496, packages/cli). Neither touches app.zod.ts, index.zod.ts, base.zod.ts, packages/layout, the guide or the 11494 pins. zod-mirror-parity.test.ts carries both sides at the head (the PR's amended EXCLUSIONS row and main's metric-card rows), merged without conflict. f68e0a080 adds to the same root union, and the shape gate compiled the union with both additions green, which is the stronger reading. Nothing from ea3914139 moves items 1 to 11. Right.

② Semver level

Unchanged from 5959852006: @object-ui/layout minor with the break spelled out, and @object-ui/types minor, both right. This round adds a type export inside the types minor and no accept-set change; check-changeset-presence still reads 9 source files of 2 released packages and 2 changesets at the new merge base. The dated note on the objectui#11440 entry is prose in a pending entry of the same package and release, not a bump. Clause-②: yes on body line 3 stands. The five changeset gates are green on the head.

③ Boundary flags

  • The flag of 5959852006 on .changeset/11440-arm-passing-types.md: closed. e5dc2df30 appends nine lines to that file and removes none (a pure append, read in the diff), under the heading the REWORK prescribed and ending in its prescribed sentence. Accurate against the head: schema is declared on AppSchemaRendererNodeSchema as AppComponentSchema itself, optional; @object-ui/layout's adapter delivers it; the strict face accepts the nested document (pinned); the entry's earlier sentence no longer describes the node, and the rest of that entry is still the reading of objectui#11440's change. Changeset Claim Re-read, Changeset Overwrite Report and Control Byte Scan are green on the head.
  • Dev open_questions this round: none. Dev deviations: the local reproduction was narrowed to @object-ui/types, @object-ui/core and @object-ui/cli (declared; CI ran the full gate, green); objectstack main had moved between the red CI run and the local legs (read above, consistent); the merge commit's message was reworded before the push, and nothing pushed was rewritten (the branch's history from 44a67d8fc is the two listed commits); the dev did not edit the PR body, and the seat's one added line matches the text the dev supplied and names the new head. Both new commits carry the model-free trailer pair. Accepted.
  • The seat's claim surface was amended for the 11440 entry by the REWORK; the file is in the diff and nowhere else. Accepted.
  • Everything else in ③ of 5959852006 stands: the open question answered A, the NEITHER_CHANNEL text holding in substance, the two-face note, the parity ledger row.

Serial: the merge base is main's tip, so there is nothing to merge at queue time as of this reading; objectui#8347 has not landed.

Implemented-by: claude/issue-11494-app-schema-renderer-schema-input
Reviewed-by: session_01YLg8XqWGJ785fwQ5v4pH37

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 20:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 20:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit fcdc8ec Oct 2, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11494-app-schema-renderer-schema-input branch October 2, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants