Repository navigation
Mint the neutral box container and retire JSON-authored div from the schema catalog - #6878
Merged
Merged
Conversation
…ract faces Per the 2026-08-29 maintainer ruling (方案 A): `box` renders `children`, passes the authored `className` through verbatim, and injects zero classes. Interface + zod mirror land together with the zod-mirror-parity pairing; registry registration is `namespace: 'ui'`, `isContainer: true`. Deliberately reads `children` only — never `body` (the div fallback that made mechanical swaps unsafe). Ships with the layout docs page and the components-layout-box catalog exemplars. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
…odes) to box Mechanical retype "type": "div" -> "type": "box" across 21 fixtures; the 4 components-basic-sidebar fixtures whose div node authored `body` additionally move that content into `children` (box reads children only). Zero render difference verified per fixture through the real SchemaRenderer with the ruled dual assertion — element count AND text content — 25/25 pairs equal, and the 4 body-moved fixtures keep their 'Main content area' sentinel on the page. The components-basic-div doc category (2 files / 5 nodes) stays as authored under its standing 2026-08-09 exemption: it documents the deprecated type itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
With the stock drained (80 div nodes migrated to box), the BASELINE table and its freeze/no-growth/no-slack arms retire; the gate now refuses ANY JSON-authored div or span outside the components-basic-div documentation exemption, whose non-vacuity stays pinned at 2 files / 5 nodes. The premise arm (deprecation still declared, via ComponentRegistry.deprecationFor), the omission arm and the counter-probe are carried over unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
layout-dom-leak-5574 census: text 699 -> 702 (the two components-layout-box exemplars author 3 text nodes) and box measured at 84 rendered / 0 noElement — the migrated population plus exemplars, every node expected to render its element and leak nothing. widget-dom-leak-sweep: ui:box added to COMPONENTS_PLAIN_TYPES; it sweeps CLEAN with no ledger entry (born on toDomProps), unlike the BARE_SPREAD row its predecessor div carries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
node scripts/regenerate-known-schema-types.mjs — two lines: box and ui:box join the derived list. Repairs the shard-1 red on scripts/__tests__/known-schema-types-derivation-5115.test.ts (the derived artifact lives outside every path the diff touched: registering a component type has a gate consequence path-based derivation cannot see). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Aug 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3965
Executes the 2026-08-29T14:52 maintainer ruling (方案 A, verbatim 「同意」) — mint a neutral
boxtype, migrate the catalog off JSON-authoreddiv, close the #6732 ratchet — and no earlier superseded ruling. Base:origin/main@2c1c967.Re-derived counts, both units, with the control (measured on
2c1c967)"type": "div"underexamples/schema-catalog/src/schemas"type": "card"The control's 93 nodes / 53 files matches every prior reading (08-10, 08-18, 08-28, 08-29), so the population is a measurement, not a broken pattern. The card's original "48" was a FILE count; both units are stated everywhere below. Union of keys across all 85 div nodes:
type,className,children,body— re-verified structurally; exactly 4 nodes authorbody(all ofcomponents-basic-sidebar, at path.body[1].body[0]).1. Mint (ruling point 1) — landed as one stroke
BoxSchemainpackages/types/src/layout.ts,LayoutSchemaunion,SchemaRegistry['box'], index export.BoxSchemainpackages/types/src/zod/layout.zod.ts, discriminated-union member, zod index export, and the zod-mirror-parity pairing registered in both halves ofMIRRORS(the finding(components):data-tablereads two column keysTableColumndoes not declare —headerIconandfitContent#6424 family form) — clause-② review basis.packages/components/src/renderers/layout/box.tsx—namespace: 'ui',isContainer: true(it renders an authored child list; packages/layout:page-header注册缺isContainer: true,而PageHeader有意渲染schema.children—— sdui-parser 对文档认可的写法报not-a-container#3900/finding(components): container-declaration census — 8 more registrations renderschema.childrenwithout declaringisContainer, and this is the THIRD time the same gap has been found one registration at a time #6764 reasoning), DOM pass-through on thetoDomPropswhitelist from day one.content/docs/components/layout/box.mdx(+ meta.json), with a ts-fenced schema block thatcheck:doc-snippetscompiles.components-layout-box/basic-boxandnested-boxes(+ curated meta, regenerated index — 433 entries).The three ruled contract clauses are pinned in
packages/components/src/renderers/__tests__/box-neutral-container.test.tsx: renderschildren; authoredclassNameemitted verbatim (string equality on the DOM class attribute, not containment); zero injected classes (the no-className arm asserts no class attribute at all). A fourth pin makes the sharp edge deliberate:boxreadschildrenONLY — neverbody— with adivcontrol render proving the fixture shape is renderable and the difference is the read, not a broken fixture.Zod runtime reverse-verification against the rebuilt dist: admits a well-formed box node; refuses
type: 'div'againstBoxSchemaand a non-stringclassName(path["className"]);LayoutSchemaunion admitsbox.2. Migration (ruling point 2) — 25 files / 80 nodes; 2 files / 5 nodes stay under a standing exemption
Mechanical retype
"type": "div"→"type": "box"across 25 files / 80 nodes. The 4body-authoring sidebar nodes moved their content intochildrenin the same stroke (the #6771 B-ruling direction) — each a 2-line diff, formatting preserved.Zero render difference, verified by the ruled per-fixture method (element count + text content dual assertion, through the real SchemaRenderer, per fixture, before vs after): a temporary vitest harness rendered each of the 25 pre-migration snapshots and its migrated counterpart — 26/26 assertions green: 25/25 pairs equal on BOTH element count and text content, and the 4 body-moved fixtures additionally pinned their "Main content area" sentinel on the page after migration (the exact content the superseded rulings would have silently dropped at an unchanged element count). The harness was deleted after the measurement; log excerpt below.
Deviation from the ruling's literal figure, called out rather than silently chosen: the ruling says "27 文件 / 85 节点". This PR migrates 25 / 80 and leaves
components-basic-div/(custom-card.json,nested-divs.json— 2 files / 5 nodes) authoringdiv, per the standing 2026-08-09 exemption ("div.mdx 的 2 个刻意 legacy 示例在任何选项下永久豁免", recorded on PR #3998 and carried by #6732's gate): that category IS the documentation of the deprecated type — its fixtures render the Legacy Examples ondiv.mdx— and retyping them toboxwould make the deprecated component's own docs page demonstrate the wrong component. The governing ruling explicitly superseded two named rulings and did not name this exemption; I read the gross 27/85 as the population figure (with its own "recount on the day's ref" caveat) rather than as a revocation. Flagged in the report's open questions; converting those 2 files is a 5-minute follow-up if the maintainer rules the exemption dead — and their guidance-staleness is filed as #6877 either way.3. Ratchet close-out (ruling point 3)
examples/schema-catalog/test/deprecated-component-types.test.tsdrops the frozen-stockBASELINEmachinery and now refuses ANY JSON-authoreddivorspanoutside the documentation exemption — zero tolerance, both types. Kept: the premise arm (deprecation still declared, asked of the registry), the omission arm (a third deprecated type cannot go unguarded), the exemption non-vacuity pin (exactly 2 files / 5 nodes), the counter-probe.Red-arm proof on unfixed fixtures (each mutation confirmed on disk by anchored grep counts before reading any result; restores pinned to HEAD and proven — probe file absent /
git diff HEADempty + HEAD-blob hash equality, empty hash treated as failure):4.
boxjoins both DOM-leak instruments as a converged rendererlayout-dom-leak-5574census:box: 84 rendered / 0 noElement(the migrated 80 + 4 exemplar nodes — every node proves it renders its element and leaks nothing);text699 → 702 (the exemplars author 3 text nodes).widget-dom-leak-sweep(packages/app-shell):ui:boxadded toCOMPONENTS_PLAIN_TYPES; it sweeps clean with no ledger entry — unlike its predecessordiv, which sits in the BARE_SPREAD ledger row.ui:*registration). Declared here and in the report as the same-round file-face amendment.Verification at final HEAD
1bcb1f7(all exits captured before any pipe; gate-printed verdict lines quoted)pnpm exec vitest run examples/schema-catalog/ packages/types/ packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx— "Test Files 92 passed (92)", "Tests 2952 passed (2952)".pnpm exec vitest run packages/components/— "Test Files 217 passed (217)", "Tests 1999 passed (1999)". Lock verdicts "command-exit 0".@object-ui/types,@object-ui/components,@object-ui/app-shell,@object-ui/example-schema-catalog— exit 0, zero "error TS" lines;tsc -p tsconfig.test.json --listFilesconfirms the new/edited test files are inside their programs (1 hit each — not silently excluded).check:doc-types"Every documented component type is registered";check:doc-snippetsOK after building its declared closure (first run was exit 2 "PRECONDITION NOT MET" — treated as not-measured, built, re-run);check:doc-fencesOK (the new page is ts-fenced; the plaintext form was rejected by the ratchet and fixed);check:docs-route-closure,check:sdui-registration-pins"All 14 registration(s) … present" (console built first),check:eager-closure"chunk ui-components 386.1 KB / 389.6 KB ceiling",check:side-effects-array,check:control-bytes"OK (scanned 5732 tracked text files)",check:spec-symbols,check:phantom-deps,check:self-import,check:readme-exportsOK (after building plugin-ai — same not-measured class),regenerate:check"index.ts is up to date (433 entries)";check-changeset-presence"8 source file(s) of 2 released package(s) changed … declares 1 changeset(s)";check-changeset-no-major"No changeset declares a major bump".turbo run linton the four touched packages — "Tasks: 5 successful, 5 total", 0 errors. A separate--no-inline-config --format jsonreading of the 13 changed files: 0 new errors (the single error shown pre-exists byte-identically at BASE behind a deliberate inline exemption;box.tsx's two warnings equal thediv.tsxsibling control).pnpm lintnot run locally (population = per-package eslint via turbo; the four touched packages were run exactly as CI runs them;eslint.config.jsdeclares noproject/projectService, so this diff cannot move any untouched file's verdict). CI runs the full farm regardless.Changeset:
.changeset/3965-mint-box-neutral-container.md(minor,@object-ui/types+@object-ui/components).Out of scope, filed after a full-enumeration dedup (251 open issues via repo-scoped REST, positive controls hit): #6877 — the div/span deprecation guidance still teaches the measured-non-drop-in replacements and never names
box; every surface involved is pinned by the #4000 ruling and the #6823 agreement test, so it is a judged follow-up, not a rider here.The
kind:'html'tier and landmark conversions were not touched (ruling point 4).skills/**untouched.Generated by Claude Code