fix(types): a page node refuses maxWidth and padding by name; the layout guide teaches pageType and the container (objectui#11318) - #11421
Conversation
…he layout guide teaches `pageType` and the `container` (objectui#11318) Both keys are `container` members. The layout guide taught them on a `page` node in four snippets, `PageNodeSchema` declared neither, and `PageRenderer` reads neither: its max-width class comes from `pageType` through `getPageMaxWidth`, and its wrapper inset is fixed. The tolerant face kept the keys through `.passthrough()`, the strict face refused them as a bare `unrecognized_keys`, and the page rendered unchanged. - `PageNodeSchema` declares both as `retirementTombstone` arms whose messages name `pageType` and a wrapping `container`; the TypeScript twin declares them `?: never` (the objectui#7926 / objectui#8871 shape on the same node). - `content/docs/guide/layout.md`: the four snippets use `pageType` or a `container` with its own `maxWidth` / `padding`; the Schema API block drops the two keys and the two inert `headerClassName` / `bodyClassName` lines. - `page-width-padding-refusal-11318.test.ts` pins each refusal on both faces and runs every page fence of the guide through both faces. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…f the layout guide (objectui#11318) The pin runs the guide's page fences through both faces, so an edit to `content/docs/guide/layout.md` is an edit to its input; the ledger row keeps a markdown-only pull request from skipping it. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
… page Schema API declares `headerClassName` / `bodyClassName` (objectui#11318) The page section's Schema API block dropped both lines as unread, so the note's reason for keeping the scan narrow is restated without that fact. Comment only. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
…objectui#11318) objectui#11420 (objectui#11321) landed as e0a9c67 and added its own reader row to `scripts/markdown-test-inputs.mjs` at the same place as this branch's row. Resolved by keeping both rows, in the ledger's path order: `page-width-padding-refusal-11318.test.ts`, then `public-block-doc-examples-bag-11321.test.ts`. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Fixes #11318
Clause-②: no
The layout guide taught
maxWidthandpadding: falseon apagenode. Neither is a page key: both arecontainermembers,PageNodeSchemadeclared neither, andPageRendererreads neither. This PR makes the page node refuse both by name, with the door that works in the message, and corrects the guide.Dispatched by the PM loop (claim comment
5943320845, triage direction5924850908); implemented in sessionhttps://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37. Not a governed surface:node scripts/check-governed-queue-guard.mjs --testover the eight paths answers NOT GOVERNED.On the Clause-② line: the claim's reason says the change moves the message, not the accept set. That holds for the strict face, which already refused both keys. On the tolerant face (
safeValidateSchema, whatobjectui validateruns today) this PR narrows the accept set: a page carrying either key used to pass and is now refused. A narrowing is not a widening, so the declaration staysno.What changed
packages/types/src/zod/layout.zod.ts:PageNodeSchemadeclaresmaxWidthandpaddingasretirementTombstonearms (PAGE_MAX_WIDTH_REFUSAL,PAGE_PADDING_REFUSAL). ThemaxWidthmessage namespageType, and a wrappingcontainerwith its ownmaxWidth. Thepaddingmessage names a wrappingcontainerwith its ownpadding. This is the shapeactions(objectui#7926) andbreadcrumbs(objectui#8871) set on the same node. The node stays open, not.strict().packages/types/src/layout.ts: the TypeScript twin declares both keys?: never, with docblocks.content/docs/guide/layout.md: the four snippets and the Schema API block are corrected, and each place that taught a key gets a refusal callout. The per-site table below has the details.page-app-dashboard-spec-parity.test.ts: the Pagelocalledger row lists the two new refusals. Its note asked that a third refusal come with a census, so the note now records that census and says four.page-width-padding-refusal-11318.test.ts.scripts/markdown-test-inputs.mjsrecords it as a reader of the guide. Its--auditrefused the new pin until that row existed.11318-page-width-padding-refusal.md(@object-ui/types: minor, breaking for a page node carrying either key).packages/layout/src/__tests__/guide-layout-app-shell-doc.test.ts: a comment only. Its scope note said the page Schema API declaresheaderClassName/bodyClassName, which this PR made false (Acceptance notes).Per-site decisions
maxWidth?,padding?,headerClassName?,bodyClassName?on the page shapepageType?, plus two NO lines naming the refusalspageTypeis the declared width control. The two className lines are an in-place fix (Acceptance notes)."maxWidth": "lg"and a list of sizes"pageType": "utility", plus a second snippet: acontainerwith"maxWidth": "2xl"pageType. A narrower column is a container."maxWidth": "2xl""pageType": "utility""padding": falseon the page, container"className": "p-8""padding": 8and"maxWidth": falsemaxWidth: falsekeeps the container page-wide instead of its defaultxlcap."maxWidth": "lg"on the page"pageType": "utility", and acontainerwith"maxWidth": "2xl"around the formpageType: "utility"matters for the snippets with atitletoo. A page with nopageTypeis arecordpage, and a record page leaves its heading to apage:headerblock, so itstitledraws nothing (measured below). The Content Width section says so in one sentence.Measurements (BASE
0858267e4)The dispatch's four mechanism assumptions, each measured:
PageNodeSchemadeclared neither key;ContainerSchemadeclares both. Measured with one-time render probes through the realSchemaRendererand registry (not committed):maxWidth: 'lg'drew the inner classmx-auto space-y-6 max-w-7xl, the same as the page without the key. The DOM had nomaxwidthattribute.padding: falsekept the wrapper classmin-h-full w-full bg-background p-3 md:p-4 lg:p-6, the same as the control.headerClassName/bodyClassNameon the page, neither class value appeared anywhere in the DOM.pageType: 'utility'drew an innermax-w-4xland the heading.appdrewmax-w-screen-xl,homedrewmax-w-screen-2xl, and the defaultrecorddrewmax-w-7xl. Compiled with the repo's Tailwind 4.3.3,max-w-7xlandmax-w-screen-xlare both 80rem. So the guide saysrecord,listandappshare a cap andhomeis the widest, and it citesgetPageMaxWidthfor the mapping rather than copying sizes.maxWidth: '2xl'dreww-full max-w-2xl mx-auto p-2 sm:p-3 md:p-4. The container withpadding: 8drewp-4 sm:p-6 md:p-8.unrecognized_keys(Unrecognized key: "maxWidth"/"padding") and no prescription. After this PR, both faces answerinvalid_typeat the key's own path, with the prescription.labelis the button's text key". This PR touches neither that paragraph nor any line next to it.Census: every git-tracked JSON file and
jsonfence in.md/.mdx(24type: "page"objects read), plus every TS/TSX/JS object literal through the TypeScript AST (193type: 'page'literals). Key-matcher control: 28 hits fortitle/pageType. The only page objects carrying either key were the four guide fences, so the refusals strand no authored document in the tree. This is a reading on the base, not a live count.Tests and gates (head
0c61074)packages/types/suite plus the 18 reader and script files below →Test Files 341 passed (341),Tests 9065 passed (9065). On its own,packages/types/is 323 files and 8632 tests. That includes the new pin: 28 tests, both faces per key, and every page fence of the guide through both faces.pnpm --filter @object-ui/types type-check(script name echoed:type-check) → exit 0.tsc -p tsconfig.test.json --listFilesOnlylists the new pin, so its@ts-expect-errorlegs are compiled.pnpm --filter @object-ui/types build→✓ dist completeness: 1 package(s) complete (142 emitted files verified).dist/layout.d.tscarriesmaxWidth?: neverandpadding?: never.scripts/__tests__/markdown-test-inputs.test.tsplus the nine other test files that name the script,guide-layout-page-buttons-7926, the fivepackages/layoutguide pins,component-node-vocabulary-7434anddoc-version-claims.check:doc-types✅ ·check:doc-fences✅ ·check:doc-example-ids✅ (415 references resolve) ·check-doc-links.mjs"Links are valid across 17 scan roots." ·check:control-bytes✅ ·check:new-line-citations"VERDICT new-cross-file-line-citations: 0 new citation(s)" ·check:spec-symbols✅ ·check:doc-example-readersOK ·check-changeset-presence✅ (5 source files of 2 released packages, 1 changeset; the layout test sits undersrc/) ·check-changeset-fixed✅ ·check-changeset-no-major✅ ·check-changeset-overwrite✅ ·check:pending-changeset-literals✅ ·check-test-path-roots✅ ·check-type-check-coverage✅ ·check-lint-coverage✅ ·check:unreferenced-sourcesOK ·markdown-test-inputs.mjs --audit"111 candidate test files, all adjudicated".eslint --print-configapplies 116 to 118 rules to each of the five package files and 0 toscripts/markdown-test-inputs.mjs, which sits outside the linted population. ②--format jsonreports 6 files, 0 errors and 0 warnings. ③ Invariance: the config extendstseslint.configs.recommended, sets noparserOptions.project/projectService, and no rule undereslint-rules/reads the file system, so this diff cannot move the verdict on any untouched file.check:doc-snippetsandcheck:doc-examples. Reason: both exit 2 on an unbuilt tree, and their build closure is 34 packages, which does not fit the foreground cap. Basis for the narrowing:check:doc-snippetsjudges only ts/tsx fences. This diff adds or removes none, and the one it edits (Schema API) carries adoc-snippet: fragmentdeclaration the gate skips.check:doc-examplesjudges JSDoc@exampleblocks, and this diff adds none. CI'sdoc-snippet-types.ymlruns both....@object-ui/types). The TS face gains two?: nevermembers. The AST census above found notype: 'page'literal writing either key anywhere in the tree, and reading anever-typed optional still compiles. CI runs every package'stype-check.@object-ui/layout's owntype-checkwas not run either: its one change here is a comment.Ablations (one-time, each through
ablation-replace.mjsfrom committed head92cbe41, and no file they mutated or ran has changed since; restore proven by blob equal to HEAD and an emptygit diff HEAD):"maxWidth": "lg"in the Content Width fence → 3 failed / 25 passed: both faces over the guide, plus the page-key scan. The failing line readsContent Width: invalid_type at maxWidth.maxWidth?: neverfrom the TS twin →tsc -p tsconfig.test.jsonexit 2. The output showsTS2578 Unused '@ts-expect-error'in the new pin and twoTS2322errors inzod-mirror-parity.test.ts, so the twin pair is enforced from both sides.Acceptance notes
headerClassName?andbodyClassName?on the page shape. Neither is declared onPageNodeSchema, and both are refused as bareunrecognized_keysby the strict face. The render probe drew neither class anywhere. The two lines were the only place in the tree teaching them, and they sat in the block this PR rewrites. So they are removed from the guide only, with no schema change: the triage direction names two keys, and with zero teaching sites and zero authored sites left, a named refusal for them has no pull.git grepover the tree finds no other page-node use of either spelling; the remaining hits areAppShellprose,VirtualGrid's own prop, and a local variable inRelatedList.guide-layout-app-shell-doc.test.tsexplained its narrow scan by saying the page Schema API declaresheaderClassName/bodyClassName. That stopped being true here, so the sentence is restated without it. Comment only; the file is in the 18-file set above.pagewith atitleand nopageTypedraws no heading and no description. Other guide passages author that shape: Page Component › Basic Usage, With Action Buttons, Detail Page with Actions, and Best Practices § 3. The render probe on the Basic Usage fence verbatim left only the child's text in the DOM. The same fence withpageType: "utility"drew both.ContainerSchema.paddingis a barez.number(), while thecontainerrenderer maps only a closed set of values.padding: 9andpadding: 20pass both faces and render no padding class at all, not even the default. The corrected Page Padding passage points readers at the Container reference for the scale.Generated by Claude Code