Skip to content

fix(types): arm AnyComponentSchema for seven registered node-slot renderers (objectui#8499) - #8763

Merged
os-bill merged 5 commits into
mainfrom
claude/issue-8499-node-slot-unresolved-arms
Sep 9, 2026
Merged

fix(types): arm AnyComponentSchema for seven registered node-slot renderers (objectui#8499)#8763
os-bill merged 5 commits into
mainfrom
claude/issue-8499-node-slot-unresolved-arms

Conversation

@os-warren

@os-warren os-warren commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8499

Head 005409fc1, DRAFT. Every reading below was re-derived on this branch; nothing is inherited from the card's c90395b2.

Two patch rounds. Round 1 answered the ceiling FAIL at 5596236677; round 2 answers the ceiling re-review FAIL at 5597738741, whose three items are the last section of this body. The architectural measurement, the line-chart refusal and the ablation are unchanged and were independently reproduced by both reviews. The architectural measurement, the line-chart refusal and the ablation are unchanged and were independently reproduced by that review; they are not re-litigated here.

My own unresolvedNodeTypes reading

pnpm --filter @object-ui/types build, then scripts/measure-strict-authoring-face.mjs --json:

value
BEFORE (3fbdd4a2, corpusMatchesMain: true) footer h1 header line-chart main my-component nav password ui:calendar — 9, one occurrence each
AFTER (cc307588) line-chart my-component

The population is identical to the card's. Same nine spellings, same counts. my-component is exempt by the written carve-out in scripts/check-doc-component-types.mjs; line-chart is the one the card's premise fails for (below). Seven armed.

unresolvedRootTypes moved too, unasked: 28 to 20. The eight that left are ui:calendar article section aside footer header main navarticle / section / aside were root-only occurrences of the same sectioning family, so arming the family cleared them as a side effect rather than by a separate decision.

The (a)-vs-(b) shape measurement — I took (a), and (b) is not expressible by adding arms

Triage asked which of two shapes to take and named the fear precisely: "一个扁平 arm 会让 h1 在每一个节点槽位合法,包括它明显不该出现的地方".

The fear is correct, and the magnitude is: every declared node slot, all of them. Walked from AnyComponentSchema on the built face:

reading count
arms walked 107 (before) / 154 (after)
arm-level node slots 249 before / 257 after
node slots on nested, non-arm schemas 36 (AccordionItemSchema.content, CarouselItemSchema.content, TabItemSchema.content, ListItemSchema.content, TimelineEventSchema.content, ResizablePanelSchema.content, DetailViewFieldSchema.render, PageNodeRegionSchema.components, …)
distinct node-slot vocabularies 1

⚠️ Erratum, corrected from the ceiling review's own walk. The first version of this table printed 249 unlabelled — it is the before figure, and after is 257. It also filed several ARM-level slots under "nested": HeaderBarSchema.logo / .left / .center / .right / .rightContent / .actions belong to an arm of the union, as do DataTableSchema.emptyAction, CardSchema.header / .footer, DialogSchema.footer, SheetSchema.footer, SidebarSchema.footer / .content and TableSchema.footer. The genuinely nested population is the 36 item/region sub-schemas, and the conclusion is unchanged: one vocabulary, ~293 slots.

Every one of those slots is spelled with the SAME SchemaNodeSchema object — the walk tests identity, not shape. There is exactly one node-slot vocabulary in the whole face, and AnyComponentSchema is a discriminated union: it selects its arm from the authored type literal alone. A slot has no input into that selection. So:

  • (b) is not a variant of (a). It cannot be reached by choosing differently when adding an arm. It requires naming a NARROWER schema at a slot, which is a per-slot policy decision repeated across ~280 slots.
  • It is expressible in principle, and this repo has done it exactly onceDashboardWidgetSchema.component is spelled BaseSchema, explicitly "⚠️ BaseSchema, ⛔ NOT SchemaNodeSchema (objectui#8344)", because that slot's accept set had to differ. That one exception took a maintainer ruling (objectstack#8593) for a single slot.
  • The slot triage imagined does not exist. There is no node slot that "only takes data nodes". The data-shaped collections — DashboardComponentSchema.widgets, FormSchema.fields, TableSchema.columns — are not node slots at all; they are typed by their own schemas and this change does not reach them.

(b) is disproportionate for this card and I say so explicitly, with the measurement. It is a per-slot vocabulary programme, not a narrower way to add an arm.

What (a) newly admits that is absurd

All 47 new literals become legal at all ~280 slots. Concretely, each of these now parses and did not before:

  • { "type": "header-bar", "logo": { "type": "footer" } } — a footer as a header's logo;
  • { "type": "dialog", "trigger": { "type": "hr" } } — a horizontal rule as a dialog trigger;
  • { "type": "main", "children": [{ "type": "main" }] } — nested main, which HTML permits at most one of per document;
  • { "type": "tooltip", "trigger": { "type": "ui:calendar" } } — a date-picker grid as a tooltip trigger.

⚠️ Erratum, and it removes two examples rather than weakening the point. An earlier version of this list opened with { "type": "data-table", "emptyAction": { "type": "main" } } and argued the "already lax" half with { "type": "data-table", "emptyAction": { "type": "kanban" } }. Both are false as written: data-table requires data and columns, so neither document parses — the first does not parse now, and the second did not parse before. The four above are the ones that genuinely refuse-before and accept-after, and they carry the point on their own.

⚠️ This is not a new KIND of laxity, and the honest framing matters for pricing it. Every one of the 107 pre-existing arms was already legal at every one of those slots, for the structural reason above rather than by anecdote: one SchemaNodeSchema, one discriminated union, no slot input into arm selection. (a) enlarges an existing hole by 44%; it does not open one. The hole itself is the single node-slot vocabulary, and closing it is the programme above.

Accept set, before and after, each acceptance paired with a refusal control

Root and nested ({ type: 'div', children: [X] }) give the same verdict throughout; the nested column is the one objectui#8344 made meaningful.

document before after
{ type: 'footer' } / header / main / nav REFUSE ACCEPT
{ type: 'h1' } REFUSE ACCEPT
{ type: 'password' } REFUSE ACCEPT
{ type: 'ui:calendar' } REFUSE ACCEPT
{ type: 'line-chart' } REFUSE REFUSE (deliberate — see below)
control { type: 'h1ZZ' } REFUSE REFUSE
control { type: 'stat-card' } (registered nowhere) REFUSE REFUSE
control { type: 'metric-card' } (registered; ruled not an arm) REFUSE REFUSE
control { type: 'my-component' } REFUSE REFUSE
control { type: 'area-chart' } (console stub, unfulfilled) REFUSE REFUSE

Value-level controls, so no arm is a z.any() in disguise — each pair is one green and one red reading on the same key of the same new arm:

verdict
{ type: 'img', src: 'a.png', width: 100 } ACCEPT
{ type: 'img', src: 'a.png', width: true } REFUSE
{ type: 'password', required: true } ACCEPT
{ type: 'password', required: 'yes' } REFUSE
{ type: 'ui:calendar', mode: 'single' } ACCEPT
{ type: 'ui:calendar', mode: 'agenda' } REFUSE
{ type: 'main', hidden: true } ACCEPT
{ type: 'main', hidden: 42 } REFUSE

Nothing that was green went red. The whole-corpus reading says it without relying on my choice of examples: totals.nodes 2103 to 2160 (+57 — the previously unresolved values are now walked as nodes), nodesRedToday 52 to 52, documentsRedTodayWholeDocument 46 to 46. All 57 newly-walked nodes are green.

The two faces, re-measured with scripts/check-doc-component-types.mjs's own deriveRegistryKeys:

before after
registered renderer keys 656 656
AnyComponentSchema literals 107 154
registered keys with NO arm 552 505
arm literals with NO registration 3 (action, crud-dialog, report-builder) 3 — unchanged

That last row is the control that matters for a widening: the 47 literals added are all registered. This change names nothing that does not exist.

Cost, stated rather than buried: componentTypesNeverSeen went 13 to 50. 37 of the 47 new literals are not authored anywhere in the corpora — the price of one arm per family instead of one arm per defect. The card's own repair-scope paragraph asks for exactly that ("An arm per family, not per tag"), and the alternative leaves h2 diverging identically tomorrow.

The premise fails for one of the eight: line-chart is NOT a live renderer

The card says "Every one except my-component is a REGISTERED, LIVE renderer" and justifies h1, the sectioning family and ui:calendar by name. It does not justify line-chart, and measured here that claim is false for it.

apps/console/src/register-plugins.ts:77 registers ten chart variants as lazy stubs pointing at @object-ui/plugin-charts. That package registers eight keys — bar-chart chart chart:bar donut-chart object-chart pie-chart radar-chart scatter-chart — and line-chart is not among them. Registry.loadLazy's own docblock: "Resolves once the loader completes (whether or not the loaded module actually registered the expected type)". So the key is KNOWN to check:doc-types and resolves to nothing at render time. The repository already recorded the same reading in passing — scripts/check-doc-component-types.mjs calls a sibling defect "the same failure as the line-chart widget objectui#7896 recorded in packages/plugin-dashboard/README.md".

Triage's ruling admits arms only for things that 「运行时已经正确渲染」. line-chart does not render, so an arm would invent a capability rather than name one — outside what was ruled. It is filed instead: #8760. The absence is pinned WITH its reason in node-slot-registered-arms-8499.test.ts, reading both halves from source, so registering the key for real turns this red and the arm becomes owed.

The comparison instrument — what I built, and the trade-off

Triage's reminder: without a renderer registry ⊆ node-slot-resolvable comparison, the next registered renderer diverges the same way.

The full containment instrument is disproportionate here, and this is the number that decides it: 505 registered keys still have no arm (552 before). A containment gate needs a 505-entry ledger on day one — an artifact this card is not authorised to mint, and one that would bury the signal it exists to give. It would also need packages/types/dist, which breaks the no-build property that lets check:doc-types run as a cheap per-PR check.

What I built instead is the same comparison, scoped to the two families this card armed. node-slot-registered-arms-8499.test.ts reads packages/components/src/renderers/layout/semantic.tsx and .../basic/html-elements.tsx from source and asserts each arm's literal set equals the array the registration site loops over — plus a floor assertion on each read so a moved declaration reads as a failure rather than a vacuous pass, and a firing control that a registered-but-unarmed tag is detected. Adding a tag to either array without an arm now goes red. That is not the whole containment, and I am not claiming it is; it closes the two families that produced 5 of this card's 8 rows, at zero ledger cost.

Ablation — the pin fails when the arms are removed

One-off, restored, trap-guarded. The four arms were removed from their unions in zod/layout.zod.ts / zod/form.zod.ts (declarations left in place, so the exports still resolve and only the UNION loses them). No rebuild was performed, which is also the proof that the pin resolves package SOURCE rather than a stale dist/.

HEAD blob layout.zod.ts = 461c4283714754c254e2c4fb755c2aa167c23b11
HEAD blob form.zod.ts   = a5fc0eb5b050c9180ef435e8048cab7d318c7e0d
union members present:       2 -> 0     (grep -c on the anchor lines, both files)
layout blob after mutation = 87800b7a509ed2d358cc9b614411a5080db5b5a7   (differs from HEAD blob)
form   blob after mutation = 7a26943417e839f811d7572480408226f367100c   (differs from HEAD blob)
MUTATED_EXIT=1     Tests  17 failed | 9 passed (26)

The 9 that stayed green under ablation are: the UNREGISTERED refusals, the line-chart absence legs, and the three declaration-vs-registration legs.

⚠️ Corrected labelling, per the ceiling review. Calling all nine "control legs" overclaimed. The three declaration-vs-registration legs read SemanticElementSchema / HtmlElementSchema directly, so they survive removing those arms from AnyComponentSchema by design — the property they hold is arm-vs-registration parity, not union membership. They are not controls for the union; the UNREGISTERED refusals are. The test file now says so in its own docblock, and the leg that was labelled a firing control has been rebuilt (below), because it was comparing an array with itself plus an element and would have passed whatever the schema said.

Restore leg: git checkout HEAD -- ..., then git diff HEAD empty (verified, not inferred from an exit code), then re-run RESTORED_EXIT=0, Tests 26 passed (26).

Gates

⚠️ The table below is the head-cc307588 run. It is kept because the ceiling review reproduced it, but it is NOT the current head — the patch-round table at the bottom is. All from the repository root, exit codes captured before any pipe.

gate exit note
pnpm --filter @object-ui/types type-check 0 tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json — the third is where the compile-time mirror-parity ledger reconciles
pnpm --filter @object-ui/cli type-check 0
pnpm exec vitest run packages/types packages/cli packages/core packages/react examples/schema-catalog 0 410 files, 9168 tests
pnpm exec turbo run lint --concurrency=2 0 47/47 tasks. FULL run, not narrowed
node scripts/check-changeset-presence.mjs 0 the body originally said 7 published source files; the gate printed 8 at this head, and 9 across 3 released packages at 90fcf4f2 after the merge. 1 changeset throughout
node scripts/check-changeset-no-major.mjs 0
pnpm check:control-bytes 0 6960 tracked text files
pnpm check:doc-types 0 893 literals against 656 registered keys
pnpm check 0 the per-PR gate at lint.yml:481. Ran against a built packages/cli/dist/cli.js; 628 files analysed, 3 candidates remain, none of them this card's
pnpm check:readme-exports 0 first run was exit 1 with 514 not on disk -- run pnpm build first and three floor misses — PRECONDITION NOT MET, not a red. Zero complaints named packages/types/README. Green after a full build
pnpm check:spec-symbols 0
node scripts/check-type-check-coverage.mjs 0
pnpm check:sdui-registration-pins 0
pnpm check:published-dist 0 not a per-PR gate — measured here or nowhere
pnpm check:node-esm-load 1 not a per-PR gate — measured here or nowhere, and the exit is NOT this diff. Its provenance leg refuses 2 of 37 entries because turbo replayed @object-ui/auth and @object-ui/react-runtime from a SIBLING worktree's cache (/home/user/objectui-review-8723/...); the gate names its own remedy, --force-build. @object-ui/types was built by this tree and is in the 32 entries that imported and evaluated cleanly
pnpm exec turbo run build --filter=!@object-ui/site --concurrency=2 0 43/43

Self-scan beyond check:control-bytes, as the standing clause requires: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over all 9 changed files — no hits (grep exit 1).

scripts/check-governed-queue-guard.mjs --test over the 9 paths: NOT GOVERNED — none of the 5 governed surfaces matched. The dispatch's phrase "packages/types is a governed package (AGENTS.md #0.1)" is the CONTRACT-FIRST commandment, not the human-merge governed surface; what #0.1 forces on this change is that the repair lands on the declaration rather than as a tolerant read, which it does — no consumer gained a fallback, no renderer changed, and the four arms declare only keys their renderers demonstrably read. This PR stays draft regardless, per the dispatch and the standing 「无席内契约复审档 PASS 在案 ⛔ 禁止入队」.

Changeset

.changeset/8499-node-slot-registered-arms.md, '@object-ui/types': minor.

Graded against AGENTS.md: the rule is the paragraph at 9. Operational Rules — a changeset is required once any file under a released package's src/ moves, and "别再按「feature 要写、bug 修复不用」来判断". Seven such files moved across two released packages, so it is owed, and one declaration satisfies it. The level is minor by the objectui major/minor paragraph: "changeset 里不要声明 major" and "objectui 自身的破坏性变更也标 minor", mechanically enforced by check-changeset-no-major.mjs (run, exit 0). patch would be wrong here even though the card is typed Bug — this is a published accept-set widening, which triage flagged in advance ("承接者写 changeset 时仍应按公开面扩张处理级别").

File-surface collisions

Neither hard collision fired.

packages/types/src/registry.ts was deliberately NOT touched: SchemaRegistry is already non-exhaustive (71 keys against 107 arms before this change, 37 arms unlisted), so adding 47 tag rows would have been noise rather than a completeness repair.

Collateral: two tests whose EXAMPLES this card retired

Both had chosen h1-family types precisely because they were unarmed. Neither assertion was weakened; each was re-pointed at an input whose absence from the union is ruled rather than merely pending, so it cannot rot the same way.

  • packages/types/src/__tests__/node-recursion-point-8344.test.ts — its UNMIRRORED fixture was { type: 'h1' }, with a docblock reading "⛔ Do not 'fix' this by adding an h1 arm to make some other test green: that is the public-surface widening objectui#8344 routes into its own card." This IS that card, and the widening landed by ruling. Now { type: 'metric-card' }, whose exclusion zod/complex.zod.ts records as "DELIBERATELY not an arm of AnyComponentSchema" under objectstack#8593.
  • packages/cli/src/__tests__/check-validity-recogniser.test.ts — its "registered but not modelled" fixture was abbr, chosen on the reasoning that "the raw HTML primitives are the stable inhabitants of this bucket". They were not. Now metric-card, with the dated census re-measured in place: 658/102/558 becomes 656/154/505.

Out-of-scope findings, filed

Searched first. The repo-scoped REST search endpoint returns 403 in this container (sessions are bound to their configured re…), so the duplicate check switched to one targeted MCP search_issues — declared here as a channel switch. It returned 56 semantically related plugin-charts/types findings and none matching, so it is a live reader rather than a blind one.

NOT MEASURED

  • CI on this head. Nothing here waited for it. The gates above are local runs.
  • Build Docs. Not run. It is one of the three the dispatch names as not covering the PR head.
  • check:node-esm-load under clean provenance. Its exit 1 is a shared-turbo-cache condition, diagnosed but not cleared — clearing it needs --force-build, a second full build for a gate that does not gate this PR.
  • Browser rendering. No renderer changed, so nothing was re-verified in a browser. The claim that these seven draw rests on their fixtures and on catalog-gallery-render.test.tsx's OBJUI-001 assertion, not on my own screenshot.
  • Corpora outside this repo. The componentTypesNeverSeen and inputType-census numbers are this repository's corpora only. hotcrm and the objectstack corpus were not read.
  • corpusMatchesMain on the AFTER run reads false — expected and not a defect: packages/types/src is one of the measurement script's CORPUS_PATHS, and this branch edits it. The BEFORE run read true at 3fbdd4a2, so the before/after pair is anchored.
  • Packages beyond types / cli / core / react / schema-catalog. Their suites were not run. The full turbo run lint and turbo run build covered every package; the test sweep was scoped to the consumers of AnyComponentSchema / safeValidateSchema that carry assertions about it.
  • Whether CalendarSchema (bare calendar) mirrors the right renderer. Noticed while arming ui:calendar: CalendarSchema's key set is the ui date-picker primitive's, while bare calendar resolves to plugin-calendar's CRUD view. Not verified against ObjectCalendarRenderer's read sites, so it is recorded as an observation and NOT filed.

Patch round — what the ceiling FAIL (5596236677) asked for

Run by the os-dev seat in session session_012W3vMLTFY9SPr2LyxhSeYi, on the same branch and the same PR — a patch round, not a re-dispatch.

1. The green→red: LAYOUT_VOCABULARY could not read an enum arm

apps/console/src/__tests__/public-contract.test.ts derived the layout vocabulary as
LayoutSchema.options.map(arm => arm.shape?.type?.value). .value exists on z.literal and not on z.enum, so the two arms this PR added resolved nothing: 19 arms yielded 17 literals and the file's own anti-vacuity case fired — the gate working, not failing.

The derivation now switches on zod's own def.type discriminator.value for a literal arm, .options for an enum arm — rather than trying accessors in turn. ⛔ A .value-else-.options-else-… fallback chain was deliberately NOT written: it would absorb exactly the accessor drift the anti-vacuity case exists to report. An arm whose declaration is neither shape resolves nothing, and that is still a failure.

⛔ The vocabulary is not hand-restated — that is the defect the file exists to catch. What changed is the count invariant, because it had to: one enum arm contributes 37 spellings, so literals equals arms is no longer meaningful. No arm contributes zero is, and that is what is asserted:

expect(LAYOUT_ARM_LITERALS.filter((literals) => literals.length > 0))
  .toHaveLength(LAYOUT_UNION_ARMS.length);

The identity case also now pins one spelling out of each enum arm (main, h1) alongside box / flex / container, so a reader that silently stopped resolving enums cannot pass it either.

apps/console/src/__tests__/public-contract.test.ts result
head cc307588 (before) FAILTests 1 failed | 18 passed (19); expected [...] to have a length of 19 but got 17 at :501
head 90fcf4f2 (after) PASSTests 19 passed (19)

2. The second contract: seven declared layout containers, LEDGERED — none promoted

Fixing the derivation reveals what the review predicted. Measured on this head:

reading before after
LayoutSchema arms 19 19
literals resolved 17 61
arms resolving zero literals 2 0
LAYOUT_VOCABULARY ∩ isContainer 7 14
of those, uncurated 1 (aspect-ratio) 8

The seven new members are exactly the sectioning tags — article aside footer header main nav section. No HTML tag from HtmlElementSchema declares containment, so the population grows by seven and not by forty-four.

Disposition taken: ledgered in UNCURATED_LAYOUT_CONTAINERS, tracked by objectui#8775. ⛔ Nothing was promoted into PUBLIC_BLOCKS, ⛔ nothing was weakened, skipped or deleted.

The re-housing option was refused, deliberately. Moving SemanticElementSchema out of LayoutSchema would have gone green just as cheaply and cost no accept-set change — but the seven have declared isContainer: true since objectui#6764 and are registered category: 'layout'. Re-housing removes seven genuine layout containers from the forcing function's population without changing anything about what they are: a gate that stops looking, not a gate that passes. Ledgering is that file's own reviewable alternative, and it is what the mechanism was built for — they arrived by absence, exactly as designed.

Why promotion is not this card's to take — the merits, restated in patch round 2 because the reason first published here was FALSE. ⛔ This paragraph used to say that curating one of the seven would delete that tag from every react page. That is refuted at the mechanism, and the refutation is reproduced in patch round 2 below: react-page.tsx:75-77 skips every container config, these seven have carried isContainer: true since objectui#6764, and simulating the promotion of main leaves the injected-identifier count at 46 before, 46 after with Main absent from both. What promotion actually does is widen the AI-authoring vocabulary, sdui.manifest.json and the generated intrinsics, and turn the container-declaration-census.test.tsx pin red by design — a deliberate re-opening, which is what semantic.tsx means by "re-opens the question HERE". ⭐ The disposition does not move: nothing is promoted, and promotion is still not a roster edit — it is a public-contract decision, and objectui#8775 carries it.

The forcing function is satisfied, and proven load-bearing — one ledger entry ablated, restored:

HEAD blob public-contract.test.ts = b50b6d546a2f982d387eceb132c121df07e1b5f7
anchor `  main: SECTIONING_TAG_UNRULED,`   grep -c 1 -> 0   (mutation proven on disk)
blob after mutation = 8f3dc375e60054122fc35ed4882506cc09a8a2d1   (differs from HEAD blob)
MUTATED_EXIT=1   × curates every declared layout container, or records why not
                 Tests  1 failed | 18 passed (19)
restore: git checkout HEAD -- PATH;   git diff HEAD --quiet -> exit 0
         blob after restore = b50b6d54...  (matches HEAD blob exactly)
RESTORED_EXIT=0  Tests  19 passed (19)

The entry deleted was one of the seven, and the case that fired is the forcing function itself, by ABSENCE.

3. The tautological control, rebuilt

node-slot-registered-arms-8499.test.ts's "detects a tag that is registered and unarmed" compared [...armed] against [...armed, 'hgroup'] — set algebra that passes whatever the schema says, never touching a registration. It now runs the real instrument on both halves: a genuine mismatch read from a real registration site (html-elements.tsx's 37 tags against SemanticElementSchema's 7), and a proof that the source reader fails closed — a declaration name that is not in the file throws rather than quietly yielding [], which is what makes the toBe(7) / toBe(37) non-vacuity guards real guards.

4. Gates at head 90fcf4f2

origin/main merged once before pushing (efbd566d2, clean, no conflicts; #8756/#8766 and others landed). ⛔ scripts/pm/dispatch-gates.mjs cannot serve objectui paths — it refuses cross-repo by design (exit 2, naming both repos), so this list is hand-derived from objectui's own package.json scripts and .github/workflows/.

gate exit note
pnpm exec vitest run packages/types packages/cli apps/console 0 266 files, 4463 tests. ⚠️ apps/console is in scope this time — not running it is how the green→red reached the ceiling review
pnpm exec vitest run apps/console/src/__tests__/public-contract.test.ts 0 19/19, the file that was red
pnpm exec turbo run type-check lint --filter=@object-ui/types --filter=@object-ui/cli --filter=@object-ui/console 0 42/42 tasks, 0 errors; the 266 warnings are pre-existing no-explicit-any in packages/types, none from this diff
pnpm exec turbo run build --filter=@object-ui/console --filter=@object-ui/cli 0 36/36
node scripts/check-changeset-presence.mjs 0 10 files changed, 9 published source of 3 released packages, 1 changeset
pnpm check 0 the per-PR gate at lint.yml:481, against a built cli
pnpm check:control-bytes 0
pnpm check:doc-types 0
pnpm check:spec-symbols 0
pnpm check:sdui-registration-pins 0 first run exit 2 = PRECONDITION NOT MET, the gate's own words ("This is exit 2, not a pass") and its own remedy — no console build to weigh. Green after building: 16/16 registrations, 518 chunks
pnpm check:readme-exports 0 first run exit 1, all 6 complaints @object-ui/plugin-ai … not on disk — run pnpm build first, zero naming this diff = PRECONDITION NOT MET. Green after building that package: 0 unbuilt
pnpm check:vi-mock-specifiers 0
pnpm check:shell-escape-residue 0

Declared narrowing. type-check / lint / build were filtered to types + cli + console rather than run farm-wide, and packages outside those three had their suites left to CI. This is a declared deviation, not an omission: the patch round's own diff is four files in two of those packages plus a changeset, and CI runs the full farm.

Self-scan beyond check:control-bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the four changed files — no hits (grep exit 1).

Files changed this round (4): apps/console/src/__tests__/public-contract.test.ts · packages/types/src/__tests__/node-slot-registered-arms-8499.test.ts · packages/types/src/zod/layout.zod.ts (docblock only — one false absurd-slot example swapped) · .changeset/8499-node-slot-registered-arms.md (the 249/36 erratum). ⛔ packages/components/src/renderers/** untouched — verified, 0 files.

⛔ Still draft, ⛔ not enqueued, ⛔ needs:contract-review untouched — it stays on until a fresh ceiling PASS on this moved head.

⚠️ The gate table above was taken at head 90fcf4f2 and is superseded by the one in patch round 2.


Patch round 2 — the ceiling re-review FAIL (5597738741), three items

Round 2 was implemented by the domain:spec objectui seat in session session_012W3vMLTFY9SPr2LyxhSeYi, as commit 005409fc1; that commit's own Claude-Session trailer carries the same reference. (The footers at the foot of this body name the round-1 seat and are left alone — a PR body normalises footers on every edit, so attribution that has to survive is written here, in prose.)

That review passed everything it re-derived — the enum-aware derivation, the generalized invariant (proved equivalent, not weakened), the ledger disposition, and the re-housing refusal (which it found more correct than the dispatch had allowed). ⛔ None of that is re-litigated here. Three items failed; all three are answered in one commit, 005409fc1.

1 · A false mechanism was baked into SHIPPED text — replaced in four places

The claim "curating a sectioning tag into PUBLIC_BLOCKS would DELETE that tag from every react page" is refuted. Re-measured here rather than taken on the review's word:

leg reading
react-page.tsx:75-77 if (!tag || cfg.isContainer) continue; — skips every container config, whatever list it came from
getMeta('main').isContainer / getConfig('main').isContainer true / true — the flag is already there
injected identifiers today 46
injected identifiers with main promoted (simulated: push main onto PUBLIC_BLOCKS, re-run the scope loop) 46
Main in either set absent from both; set difference []
promoted config's own flag {"type":"main","isContainer":true} — so it is skipped on the same line an unlisted tag never reaches
ComponentRegistry references in packages/react-runtime/src 0 (control: 21 React references in the same file; its only imports are react and sucrase)

⇒ a lowercase main in a react page is a DOM intrinsic before and after. The docblock the first round read describes objectui#6764's direction — declaring containment on an already public block removes an injected identifier — and reading it forwards is how the inversion happened.

The conclusion does not change: no tag is promoted, and "promotion is not a roster edit" survives. Only the reason was wrong. Replaced in all four places it lived:

  1. the shipped ledger string SECTIONING_TAG_UNRULED (apps/console/src/__tests__/public-contract.test.ts) — now states what promotion does move (vocabulary / sdui.manifest.json / intrinsics, and the census pin red by design) and names the refuted claim as an earlier revision's error;
  2. the UNCURATED_LAYOUT_CONTAINERS docblock in the same file — carries the cost of a ledger entry once, so no entry re-argues it, including the measured 46/46;
  3. this PR body — the paragraph above, marked as corrected rather than quietly rewritten;
  4. objectui#8775's body — corrected in place, with a correction comment posted alongside so the change is visible in the card's timeline. That card is otherwise sound and stays open.

2 · unexportedNodeSchemas — option (b), declared in the changeset

The four new zod schemas are unreachable by name from @object-ui/types/zod (the barrel uses explicit export lists; control: CalendarSchema is present, the four are not). Re-measured with scripts/measure-strict-authoring-face.mjs --json after building @object-ui/types: 49 node types, up from [breadcrumb, object-tree] — the same 2, plus this change's 47 new literals.

Taken: (b) — leave them unexported and declare the movement knowingly. Three reasons, in the order they became decisive:

  • The commit option (a) was offered to lean on does not exist. b7af6b52 returns HTTP 422, "No commit found for SHA" from /repos/objectstack-ai/objectui/commits/b7af6b52. Positive controls on the same endpoint and the same 8-char short form: 90fcf4f2200, fb010227200. So the 422 is a real absence, not a channel artifact.
  • Option (a)'s file is held by a live PR. packages/types/src/zod/index.zod.ts is one of the three files in PR feat(types): re-export BreadcrumbSchema and ObjectTreeSchema from the zod barrel (objectui#7917) #8777 (objectui#7917, open and non-draft, head a5eaf49c) — the PR whose entire purpose is that barrel's export list. Editing it here is outside this PR's 9-file surface and lands on top of another seat's in-flight work.
  • It would widen the public surface beyond this card's ruling. The ruling is arm the registered renderers. Exporting SemanticElementSchema / HtmlElementSchema publishes a named authoring surfacez.enum families, not per-tag schemas — that no consumer asked for and that no ruling covers.

The changeset now says all of this, including the honest asymmetry: the 2 were data blocks a consumer had a standing reason to validate alone (which is why objectui#7917 exists), while the 47 are HTML primitives and two input aliases with no per-tag consumer. ⚠️ And the interaction is stated rather than hidden: if PR #8777 lands first, the same movement reads 0 to 47.

3 · The false changeset sentence, corrected

"Every arm declares only keys its renderer demonstrably reads" is untrue for UiCalendarSchema. Re-measured in packages/components/src/renderers/form/calendar.tsx: minDate 0, maxDate 0, onChange 0 occurrences; controls mode 3, className 4. The renderer reads mode, value, defaultValue, className and nothing else.

The sentence now scopes itself to the arms this change authors, and states the inherited exception: UiCalendarSchema extends CalendarSchema, so it carries minDate / maxDate. ⛔ No schema was changed to make the sentence true — that is pre-existing debt on CalendarSchema and a different card's accept-set movement. (onChange is a handlerKeyRefusal, i.e. declared-and-unwritable, so it is a named refusal rather than an unread authorable key — a different category, noted so the correction is not over-read.)

4 · The optional strengthening — taken

The review's blind spot: dropping a single non-container spelling (q) from the 37-enum left this file green. LAYOUT_VOCABULARY is now cross-checked against LayoutSchema._zod.propValues.type — zod's own discriminator index, a second accessor path over the same union. Both read 61 literals over 19 arms and agree exactly (derived-only: [], propValues-only: []).

⚠️ Scoped in-comment rather than over-claimed: this pins the reader, not the schema. Both paths read the same union, so a spelling genuinely deleted from an enum arm still leaves both sides equal — that direction is pinned against the registration source elsewhere, which is where it belongs.

Ablation, isolating the new assertion (the mutation is in the test file itself, so no build step is involved):

HEAD blob apps/console/src/__tests__/public-contract.test.ts = 36a3c83bbd646e8277e465107e1dad8cad3d34a8

leg A — enum branch DELETED
  enum-branch grep -c  1 -> 0        ablation marker grep -c  1   (mutation proven on disk)
  blob after mutation = 86efab8d4e95508255d58797e69611f74ff95839   (differs from HEAD blob)
  EXIT=1   Tests  3 failed | 16 passed (19)
  restore: git checkout HEAD -- ABSOLUTE_PATH   ->   git diff HEAD --name-only  EMPTY
           blob after restore = 36a3c83b...  (matches HEAD blob exactly)

leg B — enum branch resolves all but ONE spelling (`q`), so every arm still yields >= 1
        and only the NEW cross-check can fail
  blob after mutation = 84f76099807f4af2a7e1089409909507477801d1   (differs from HEAD blob)
  EXIT=1   Tests  1 failed | 18 passed (19)
           x reads the vocabulary off the union rather than restating it
           AssertionError: expected [ 'a', 'abbr', 'address', ...(58) ] to deeply equal [ ...(57) ]
  restore: git diff HEAD --name-only  EMPTY;  blob = 36a3c83b...  (matches)

Leg B is the one that matters: exactly one case fails, and it is the new one. Both legs ran under trap restore EXIT INT TERM with absolute paths, and both restores were verified by an empty git diff HEAD, ⛔ never by an exit code.

5 · Gates at head 005409fc1

origin/main merged once before pushing (5591f03bd; #8776, #8780, #8781, #8746, #8779 and others landed — clean, no conflicts). ⛔ scripts/pm/dispatch-gates.mjs cannot serve objectui paths (exit 2, cross-repo by design), so this list stays hand-derived from objectui's package.json and .github/workflows/. Every exit code below was captured as cmd > file 2>&1; EXIT=$? — ⛔ never through a pipe.

gate exit note
pnpm exec vitest run apps/console/.../public-contract.test.ts packages/types/ packages/cli/.../check-validity-recogniser.test.ts packages/components/.../container-declaration-census.test.tsx 0 159 files, 3150 tests, from the repo root
pnpm build --concurrency=2 0 43/43 tasks
pnpm --filter @object-ui/console run type-check 0 and it genuinely covers the edit: tsc --noEmit --listFiles lists public-contract.test.ts (1 hit of 3715 files), so this is not a suite that excludes *.test.ts
pnpm --filter @object-ui/console run lint 0 0 errors, 213 pre-existing warnings; the edited file reports 0 errors / 0 warnings
node scripts/check-changeset-presence.mjs 0 10 files changed, 9 published source of 3 released packages, 1 changeset
node scripts/check-changeset-no-major.mjs 0
node scripts/check-changeset-overwrite.mjs 0 1 added, 0 modified, 0 deleted
pnpm check:sdui-registration-pins 0 ⚠️ PRECONDITION NOT MET (exit 2) until built, exactly as the dispatch predicted; green after the build above — 16/16 registrations, 518 chunks
pnpm check:readme-exports 0 ⚠️ same shape (exit 1, @object-ui/plugin-ai not on disk); green after the build — 0 unbuilt
pnpm check:control-bytes 0 6985 tracked text files

Declared narrowing. Farm-wide turbo run lint / type-check / test were not run; the affected package's own lint and type-check were run in full, and the rest is CI's. Evidence this narrowing measures rather than skips: the lint population is 198 files read from eslint's own --format json output (not estimated), the edited file is in it at 0/0, and type-aware linting is not enabled in eslint.config.js (zero projectService / project: true / tsconfigRootDir occurrences) — so this diff cannot move the verdict on any file it did not touch.

⚠️ One reading worth recording so nobody re-derives it as a finding: adding --no-inline-config to the console's eslint run surfaces 3 react-hooks/static-components errors in src/pages/settings/. They are not in this diff and not a regression — --no-inline-config is the lint:root spelling, and the package's real lint script (eslint .) is exit 0.

Self-scan beyond check:control-bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over both changed files — no hits (grep exit 1).

Files changed in round 2 (2): apps/console/src/__tests__/public-contract.test.ts · .changeset/8499-node-slot-registered-arms.md. ⛔ packages/components/src/renderers/** untouched (0 files) — the react-page and census readings above are reads, not edits. ⛔ packages/types/src/zod/index.zod.ts untouched, and deliberately so (item 2).

⛔ Still draft, ⛔ not enqueued, ⛔ needs:contract-review untouched — a delta re-review runs first.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w


Generated by Claude Code


Generated by Claude Code

…derers (objectui#8499)

Nine `type` spellings sat at declared node slots in this repo's own corpora and
resolved in no arm of `AnyComponentSchema`. Eight were registered, live renderers
with fixtures; the ninth (`my-component`) carries a written exemption. A reader
following `content/docs/utilities/runner.mdx` got a document that renders in the
browser and is refused by `objectui check` — the direction that trains authors to
distrust the validator.

`check:doc-types` judges type literals against the renderer registry (656 keys),
not against `AnyComponentSchema` (107 arm literals), so the two faces disagreed by
construction and nothing compared them at a node slot.

Four arms, 47 literals, 107 -> 154:
  - SemanticElementSchema  — the 7 sectioning tags `layout/semantic.tsx` registers
  - HtmlElementSchema      — the 37 safe tags `basic/html-elements.tsx` registers
  - InputShorthandSchema   — `email` / `password`, `form/input.tsx`'s two aliases
  - UiCalendarSchema       — `ui:calendar`, `form/calendar.tsx`'s date primitive

Every arm declares only keys its renderer demonstrably reads. `line-chart` is
deliberately NOT armed: measured here it is a lazy stub in `apps/console` that
`@object-ui/plugin-charts` never fulfils, so an arm would invent a capability
rather than name one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3483.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-G1k4qkDf.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 244.96KB 61.76KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.39KB 65.32KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.20KB 33.48KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 55.50KB 15.75KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

…r the seven

`LAYOUT_VOCABULARY` in `apps/console/src/__tests__/public-contract.test.ts` read
each arm's `type` through `.value` alone. That accessor exists on `z.literal` and
not on `z.enum`, so the two enum arms armed for objectui#8499 resolved nothing:
19 arms yielded 17 literals and the file's anti-vacuity case fired, exactly as it
was built to.

The derivation now switches on zod's own `def.type` discriminator — `.value` for
a literal arm, `.options` for an enum arm — rather than trying accessors in turn,
so accessor drift still surfaces as an arm resolving nothing instead of being
absorbed by a fallback chain. The count assertion moves with it: one enum arm
contributes 37 spellings, so literals-equals-arms is no longer the invariant;
no-arm-contributes-zero is, and that is what is asserted.

Fixing the derivation reveals a second declared contract. The seven sectioning
tags have registered `isContainer: true` since objectui#6764, so becoming arms of
`LayoutSchema` pulls them into this file's derived `LAYOUT_VOCABULARY` ∩
`isContainer` population, where an uncurated container lands by ABSENCE. They are
ledgered in `UNCURATED_LAYOUT_CONTAINERS` against objectui#8775, which holds the
decision. None is promoted into `PUBLIC_BLOCKS`: curating an `isContainer` tag
makes `react-page.tsx` drop it from the `kind:'react'` JSX scope, so promotion is
not a roster edit and is not this card's to take. Housing the arm outside
`LayoutSchema` was refused for the opposite reason — it would remove seven real
layout containers from the population without changing what they are.

Also: the registered-but-unarmed control in
`node-slot-registered-arms-8499.test.ts` compared an array with itself plus an
element, which passes whatever the schema says. It now runs the real instrument
and asserts the source reader fails closed. The `~30 nested slots` figure in the
changeset is corrected to the measured 36, and 249 is labelled as the before
figure against 257 after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi
os-bill pushed a commit that referenced this pull request Sep 9, 2026
…erately untouched

⛔ NOT a finished change. The public export `EventHandlersSchema`
(`z.record(z.string(), z.function())`) and its barrel line are removed and the
knowledge is preserved as a NOTE at the declaration site, per the maintainer
ruling on objectui#6124 (decision batch #8, reconfirmed batch #25) recorded on
objectui#6910.

The third half of that ruling — deleting the parity-ledger row
`'base.zod.ts#EventHandlersSchema'` in
`packages/types/src/__tests__/zod-mirror-parity.test.ts` — is NOT in this commit.
That file is held by the open PR #8763, and this seat's dispatch forbids editing
it. So this tree is RED BY CONSTRUCTION: `zod mirror parity — the population is
closed > no map entry names a const that no longer exists` fails with
`['base.zod.ts#EventHandlersSchema']`. Measured, not predicted.

No PR is opened from this commit. It exists so the NOTE and the measurements
survive the hand-off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi
@github-actions github-actions Bot added the apps label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.2 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-BDdX9raw.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 245.88KB 62.05KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.39KB 65.32KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 55.50KB 15.75KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

os-bill commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Director seat adoption record — summon #20, session_01Tep4AYXZvyBA7jsvne5KZV (os-bill), 2026-09-09T06:59Z. The verdict below is adopted verbatim from an isolated contract-review subagent (explicit model = CONTRACT_REVIEW_TIER). Transcript tier check before adoption: every harness-stamped model field in the subagent transcript reads claude-fable-5-1 (71 stamps, no other value). Head re-read at posting time = 90fcf4f2cc, unchanged since the review. ⛔ This seat takes no release action on this carrier (no ready flip, no auto-merge, no enqueue, no label write): the owning seat (domain:ui (objectui seat, takeover claim 5596240727)) adopts this verdict verbatim or discards it, and acts per the state machine.


Contract review (CONTRACT_REVIEW_TIER, isolated seat) — objectui PR #8763 @ 90fcf4f2cc59277ff555b66ea39377e0d73122c4

Verdict: CHANGES REQUIRED

Everything below was re-derived from git diff origin/main...refs/pr-review/8763 (merge-base efbd566d, 10 files, +797/−61), the head's source blobs, the card's 10 comments, the PR body, and REST check-runs. No test suite was run; nothing was posted; the shared checkout was not modified.

Ruling conformance

Ruling / brief item Source Verdict
Arms only for things that already render at runtime (「运行时已经正确渲染」) triage 5580341429 Conforms. All 47 literals resolve to a ComponentRegistry.register site (below). line-chart correctly refused (see (a)).
my-component is exempt, "8 not 9" triage, dispatch 5595407505 Conforms. Written exemption scripts/check-doc-component-types.mjs:572; not armed.
Measure family-arm vs slot-constrained shape, declare "everywhere" with Clause-②: yes triage §「子问题」 Conforms. PR body measures one SchemaNodeSchema at every slot (base.zod.ts:263,480-489: nodeUnion is one z.union whose slot 0 is the wrapped component union); a discriminated union dispatches on the literal alone, so (b) is not reachable by adding arms. Declared in both Claims.
Do not reopen "arm or no arm" as a decision box dispatch Conforms.
Consider a registry ⊆ arms comparison instrument ("reminder, not prescription") triage Conforms, scoped: node-slot-registered-arms-8499.test.ts compares SemanticElementSchema/HtmlElementSchema literal sets for equality against the tags/TAGS arrays read from semantic.tsx/html-elements.tsx source, with non-vacuity floors (7/37) and a fail-closed reader. Full containment (505 unarmed keys) declined with the number stated.
File surface: zod/objectql.zod.ts (#8221) and the nine @default JSDoc tags (#8318) untouched; packages/components/src/renderers/** read-only dispatch, takeover claim 5596240727 Conforms. objectql 0 files; no @default line in the layout.ts/form.ts diff; 0 renderer files changed.
Ceiling FAIL 5596236677 items 1–4: enum-aware LAYOUT_VOCABULARY, no PUBLIC_BLOCKS promotion, ledger the seven with a tracking issue, stop on fork ceiling review Conforms. public-contract.test.ts:468 switches on def.type (literal.value, enum.options, else []); anti-vacuity re-stated as "no arm resolves zero"; seven entries :549-555 under SECTIONING_TAG_UNRULED naming #8775 (open). Nothing promoted.
Ceiling errata (249→257 labelling; two false data-table examples) ceiling review Fixed in body and changeset.
Ceiling "Also found": MEDIUM (four zod mirrors unreachable by name), LOW-1 (changeset's "only keys its renderer demonstrably reads" false for UiCalendarSchema), LOW-2 (tautological control) ceiling review LOW-2 fixed. MEDIUM and LOW-1 not addressed on 90fcf4f2, and the patch-round dev report (5596533764) neither takes nor declines them. → F1, F2.

Derived judgments

(a) Arms added to the published AnyComponentSchema union (four arms, 47 literals, 107→154):

Arm (source schema) type literals Union entry
SemanticElementSchema = BaseSchema.extend(...)zod/layout.zod.ts:499 aside main header nav footer section article (7) LayoutSchema :588
HtmlElementSchema = BaseSchema.extend(...)zod/layout.zod.ts:544 h1…h6 p a blockquote pre strong em b i u small mark sub sup del ins abbr ul ol li dl dt dd figure figcaption img hr br time address cite q (37) LayoutSchema :589
InputShorthandSchema = InputSchema.omit({type,inputType}).extend(...)zod/form.zod.ts:716 email password (2) FormComponentSchema :805
UiCalendarSchema = CalendarSchema.extend(...)zod/form.zod.ts:753 ui:calendar (1) FormComponentSchema :806

TS twins: layout.ts:942,968 (union :1023-1024); form.ts:1659,1680 (union :1706-1707). LayoutSchema/FormComponentSchema feed AnyComponentSchema (index.zod.ts:436-439).

Seven of the card's nine spellings resolve: footer header main nav (semantic arm), h1 (html arm), password (shorthand arm), ui:calendar. The other two: my-component — the reader's own plugin component, written exemption scripts/check-doc-component-types.mjs:572; no arm can exist. line-chartapps/console/src/register-plugins.ts:77 registers it only as a registerLazy stub pointing at @object-ui/plugin-charts; that package's src/index.tsx registers bar-chart chart chart:bar pie-chart donut-chart radar-chart scatter-chart (+object-chart) and no line-chart; a git grep over packages/*/src and apps/*/src finds no real registration anywhere (only the stub loop and cli/src/utils/known-schema-types.ts:234). It does not render, so arming it would fail the ruling's criterion; filed #8760 (open) and pinned-with-reason in the test. Correct. Note the family arms also admit 40 spellings the card did not list (email, article section aside, 36 HTML tags) — inside the card's own "arm per family, not per tag" scope paragraph and triage's 「按族」.

(b) Registration parity (each literal is the registered spelling):

  • semantic.tsx:13 const tags = ['aside','main','header','nav','footer','section','article'], registered :46-47 with namespace: 'ui' (bare key + ui: twin) — arm enum identical, same order.
  • html-elements.tsx:45-50 TAGS (37), registered :180-181 namespace: 'ui' — arm enum identical.
  • input.tsx:94 register('email', …), :111 register('password', …), both namespace: 'ui'; wrappers pin inputType last, so the arm's omission of inputType is right.
  • calendar.tsx:13 register('calendar', …) with :23 namespace: 'ui' and :27 skipFallback: trueRegistry.ts:458 composes ui:calendar, :492 skips the bare key. Literal 'ui:calendar' is the only spelling that reaches this renderer. Correct.
  • No schema was invented from nothing: all four derive from BaseSchema/InputSchema/CalendarSchema. They are export const in their modules but not re-exported by either barrel (F1).

(c) Union order / discriminator effect on existing arms: the four are appended to two z.discriminatedUnion('type', …) lists; dispatch is by discriminator map, so order cannot change an existing arm's verdict as long as literals are unique — pinned by node-slot-registered-arms-8499.test.ts (new Set(all).size === all.length) and any-component-union-fanout.test.ts:93-94. All four arms are BaseSchema.extend (passthrough, base.zod.ts:449); no .strict() arm added; .omit() in zod 4 keeps the def's catchall. Existing arms' child slots gain acceptances only (the intended widening), never refusals. The runner.mdx:329-330 document { "type": "h1", "children": "Sales Dashboard" } parses: nodeUnionOptions includes z.string() (base.zod.ts:482). Probe not run — no packages/types/dist and no zod install anywhere in this container (searched); the judgment is structural, corroborated by the dev's whole-corpus reading (nodesRedToday 52→52, documentsRedTodayWholeDocument 46→46) and by the ceiling review's independent reproduction.

(d) Spec mirror: @objectstack/spec declares none of the 47 spellings (grep over packages/spec/src/**/*.ts excluding tests: zero hits for ui:calendar, h1, aside, …; email/password occur only as inputType values, component.zod.ts:2325). Spec's PageComponentSchema.type is z.union([PageComponentType, z.string()]) — an open string arm; RESERVED_COMPONENT_TYPE_NAMESPACES (derived from the enum) are page record app nav global ai element, so ui: is not spec-reserved and component-type-unknown will not refuse ui:calendar; ComponentPropsMap has no row for any of them (specCarried still [] in public-contract.test.ts:496). There is no spec node/component union for AnyComponentSchema to be narrower than; the objectui face was already 107 spellings wide against the spec's enum by design. Not a finding.

(e) New exports / keys: four TS interfaces reachable via the ./layout and ./form subpaths of @object-ui/types (exports map) but not from the root . barrel (src/index.ts layout block ends :147, form block :196 — the four are absent; control CalendarSchema :172). Four zod schemas reachable from no published entry: ./zod is the sole zod entry and zod/index.zod.ts lists neither (layout block ends :119, form block :152; control CalendarSchema :144, PageNodeSchema :118). New keys: HtmlElementSchema children href target rel title src alt width height dateTime cite; SemanticElementSchema children; InputShorthandSchema = InputSchema keys − type/inputType + wrapperClass (form.zod.ts:726, read at input.tsx:42; TS InputSchema already declares it, ledger row zod-mirror-parity.test.ts:1991 untouched); UiCalendarSchema = CalendarSchema keys. EXPECTED_MIRROR_PAIRS 158→162 with four MIRRORS/Declared rows.

Semver / changeset

  • Clause-②: adding union arms widens the accept set and the public type surface ⇒ yes mandatory. Both Claims carry it verbatim (5595407505, takeover 5596240727 — the newest Claim names branch claude/issue-8499-node-slot-unresolved-arms). Carriers: needs:contract-review present on card and PR (REST labels read). PR is draft.
  • .changeset/8499-node-slot-registered-arms.md:2'@object-ui/types': minor. AGENTS.md (9. Operational Rules, line 162): a changeset is owed once any src/ file of a released package moves — 10 files across types/cli/console, one declaration satisfies; lines 237–242: never major, breaking is minor. Additive API (4 subpath-exported interfaces, 47 accepted literals, two widened union types) grades minor; patch would understate a published accept-set widening, as triage pre-warned. Changeset Declaration / Changeset Bump Policy / Changeset Fixed Group Check green. semver_ok: true, with the changeset's one false sentence at F2.
  • Governed paths: none of the 10 paths matches GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:227-233: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md). Not governed. The dispatch's "governed package (#0.1)" phrase is the contract-first commandment, not the human-merge surface — the dev's reading (option A) is correct.

Boundary flags

Findings

  • F1 (blocking) — The four new schemas are published by the union but unreachable by name from any published entry. packages/types/src/zod/index.zod.ts layout block (ends :119) and form block (ends :152) omit SemanticElementSchema, HtmlElementSchema, InputShorthandSchema, UiCalendarSchema; packages/types/src/index.ts (:147, :196) omits the four interfaces. Every sibling arm is barrel-exported on both faces (controls CalendarSchema index.zod.ts:144 / index.ts:172; PageNodeSchema :118 / :148), and the changeset names the four schemas as the deliverable. The ceiling review raised this as MEDIUM at cc307588; the patch round neither took nor declined it. Remedy: one additive commit on 90fcf4f2 adding the eight barrel lines plus a reachability pin through a published entry (the parity test imports ../zod/form.zod.js directly and cannot see this). No force-push; no re-housing.
  • F2 (non-blocking, fold into the F1 commit).changeset/…:40 "Every arm declares only keys its renderer demonstrably reads" is false for UiCalendarSchema: it inherits CalendarSchema (form.zod.ts:401-409) including minDate/maxDate, which calendar.tsx:14-19 never reads (it reads mode, value, defaultValue, className). Pre-existing debt on calendar, not new widening — but changesets are the input to centrally written release notes (CLAUDE.md), so the sentence ships unless corrected.
  • F3 (non-blocking, no action)HtmlElementSchema (layout.zod.ts:544-567) declares the per-tag DOM keys on all 37 tags and types width/height as string|number against the registration's per-tag number inputs (html-elements.tsx:53-71). Reasoned in-file (renderer forwards verbatim; BaseSchema passthrough already accepted them); narrows nothing, invents no key the renderer drops.
  • F4 (non-blocking, no action) — The comparison instrument covers the two family arms (5 of the card's 8 rows), not the registry ⊆ arms containment triage floated; 505 registered keys remain unarmed. Triage called it a reminder, not a prescription, and the trade-off is stated with its number. The single node-slot vocabulary (one SchemaNodeSchema, ~293 slots) is architecture this PR priced but did not create.

CI at read time

Head 90fcf4f2, 33 check runs, latest-per-name: 30 success, 3 skipped (Test (coverage shard ${{ matrix.shard }}/4), Test (coverage), dependabot — template/skip names, not failures), 0 failed, 0 pending. Test (shard 1/4) — red at cc307588 — is success (completed 06:06:01Z); Type Check, Lint, Build & E2E, Build Docs, Doc Component Type Check, README Export Check, Governed Surface Queue Guard all success. PR at read (~06:46Z): mergeable: true, mergeable_state: clean, draft true; the brief's behind at 05:50Z did not reproduce (origin/main has since moved to 6cc48c4e; merge-base with the PR is efbd566d).

Dev report answers. (i) 5595906713 Q1 (per-slot vocabulary card): B stands for this PR; A is a legitimate follow-up programme card seeded by the PR body's measurement — not blocking, not this PR's. Q2 (governed premise): A — confirmed above; governed=false. (ii) 5596533764 deviations: declared type-check/lint/build narrowing is covered by the full-farm CI now green; channel switch and PR-body footer are cosmetic; removing the two false examples rather than replacing them is the honest move. Its silence on MEDIUM/LOW-1 is the gap F1/F2 name. (iii) 5596906444 Q1 (how MEDIUM/LOW-1 land): A — one fresh additive commit on 90fcf4f2 carrying only the two barrel files, the reachability pin and the changeset correction, by the seat holding the newest Claim; never a merge of the competing patch-round implementations, never a force-push. Q2 (why two seats): process observation, outside this review's remit.

Implemented-by: branch claude/issue-8499-node-slot-unresolved-arms
Reviewed-by: director seat summon #20 (isolated fable subagent, transcript-verified before adoption)

{"pr":8763,"repo":"objectui","head":"90fcf4f2cc59277ff555b66ea39377e0d73122c4","verdict":"CHANGES REQUIRED","blocking":["F1"],"clause2":"yes","semver_ok":true,"governed":false,"ci":"33 latest-per-name: 30 success, 3 skipped (coverage template x2, dependabot), 0 failed; Test (shard 1/4) green; mergeable_state clean at read"}


Generated by Claude Code

…-tag ledger

The ledger string and its docblock said that curating one of the seven
sectioning tags into PUBLIC_BLOCKS would DELETE that tag from every react
page. Measured, it does not:

  - react-page.tsx:75-77 skips EVERY isContainer config
    (`if (!tag || cfg.isContainer) continue;`), and these seven have carried
    isContainer: true since objectui#6764.
  - Simulated promotion of `main`: 46 injected identifiers before, 46 after;
    `Main` absent from both; set difference empty.
  - react-runtime imports only `react` and `sucrase` (0 registry references,
    control: 21 `React` references in the same file), so a lowercase `main`
    in a react page is a DOM intrinsic either way.

The docblock the implementer read describes objectui#6764's direction --
declaring containment on an ALREADY-PUBLIC block removes an injected
identifier -- not promotion. The conclusion is unchanged (no tag is promoted,
and promotion is still not a roster edit); only the reason was wrong. What
promotion actually does is widen the AI-authoring vocabulary,
sdui.manifest.json and the intrinsics, and turn the census pin red by design.

Also in this commit:

  - The changeset's "every arm declares only keys its renderer demonstrably
    reads" is corrected: UiCalendarSchema INHERITS minDate/maxDate from
    CalendarSchema and calendar.tsx reads neither (0 occurrences; controls
    `mode` 3, `className` 4). Pre-existing debt, stated rather than fixed.
  - The movement of the repo's own unexportedNodeSchemas measurement, from
    [breadcrumb, object-tree] to 49 node types, is declared knowingly in the
    changeset rather than left silent.
  - A second accessor path pins the vocabulary derivation against zod's own
    propValues.type index, scoped in-comment to what it does and does not
    catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.1 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-dkC7OloV.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 245.88KB 62.05KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.39KB 65.32KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 55.85KB 15.86KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants