fix(types,components)!: a container's padding is one of the renderer's twelve steps on both faces (objectui#11424) - #11470
Conversation
…s twelve steps on both faces `ContainerSchema.padding` was `z.number()` on the mirror and `number` on the declaration, while the `container` renderer maps only 0-8, 10, 12 and 16 to a padding class. `padding: 9` or `20` parsed green on the tolerant and the strict face and rendered with no padding class at all, not even the default. Both faces now state the literal set; the mirror's refusal names it. The registration's `padding` input becomes a closed `enum` of the same numbers, in `maxWidth`'s object form, so the manifest and `validateTree` carry the set too. The renderer's branches are unchanged: nothing rounds or clamps. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…adding steps; changeset Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC 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
|
Contract reviewServed-tier: Inputs read: card objectui#11424 (body and all 3 comments: triage ① Derived judgmentsEach accept-set or public-surface change the diff implies, named right or wrong against the card's direction (triage
No accept-set change outside these: no new key, no new value, no new arm, no other node or package touched. The dev's H4 census (23 authored container paddings at base, all mapped) means no corpus document moves; the head's doc gates (Doc Snippet Type Check, Doc Component Type Check, Doc Example Id Check, Skill Example Check, Build Docs) are green, which is the only evidence of that reading available to this review. ② Semver level
③ Boundary flagsDev flags, from the PR body and the os-dev report's
Reviewer's own observations, none moving the verdict:
Check-runs on
Implemented-by: VERDICT: PASS |
Fixes #11424
Clause-②: no
ContainerSchema.paddingnow accepts only the steps thecontainerrenderer maps (0 to 8, 10, 12 and 16), on both published faces. The refusal names the set. The registration'spaddinginput carries the same closed list into the SDUI manifest. The renderer's branches are unchanged: nothing rounds or clamps an unmapped value.Why
no(a narrowing): the accept set shrinks from every number to the twelve mapped values, and nothing widens. The changeset isminorand states the breaking authoring meaning, because objectui never declaresmajor(AGENTS.md section 9, version alignment).The accept-set change
5988b6b53)ContainerSchema.paddingz.number()z.literal([0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16]); its error message lists the setContainerSchemamemberpaddingnumbercontainerregistration,paddinginputtype: 'number', descriptionPadding value (0, 1-8, 10, 12, 16)type: 'enum', twelve{ label, value }entries with numeric values, in the object formmaxWidthusescontainer.tsx)padding === Nbranches,schema.padding ?? 4Readings, before and after
{ type: 'container', padding: 9 }, tolerant face (safeValidateSchema, whichobjectui validateruns)invalid_valueissue atpadding,values= the twelve steps, and the message lists themStrictAnyComponentSchema)padding: 20, both facespaddingkey, both faces (control)container,padding: 9or20container, nopaddingkey (control)p-2 sm:p-3 md:p-4p-2 sm:p-3 md:p-4validateTreeagainst the built console'sdist/sdui.manifest.json,containerwithpadding={9}orpadding={20}type: 'number')errorinvalid-enum; the message ends"padding"=9 is not one of [0,1,2,3,4,5,6,7,8,10,12,16]padding={8}and nopaddinggenerateDtsover that manifest, thecontainerpropspadding?: numberHow each reading was taken: the parse readings come from the new pins below. The base half comes from ablation leg A1, which restores
z.number(): the six refusal pins fail with zero issues. The render readings come from the components pin, which renders the realcontainerthroughSchemaRenderer. The manifest readings come frompnpm --filter @object-ui/console build, which writesdist/sdui.manifest.json, thenvalidateTree/generateDtsfrom the built@object-ui/sdui-parser. The base row is the head manifest with thepaddinginput swapped back to the base declaration.Pins
packages/types/src/__tests__/container-padding-set-11424.test.ts: on each of three doors (the node schema,safeValidateSchema,StrictAnyComponentSchema), 9 and 20 are each refused atpadding, with codeinvalid_value, issuevaluesequal to the twelve steps, and a message containing that list. Each mapped step parses, and an absent key parses (the control). Also on the node: a lit control (an undeclared key beside a mapped step stays green, becauseBaseSchemais.passthrough()). Two compile-time checks follow.@ts-expect-erroron 9 and on 20 against the TS face. A mutual-assignability check that the zod-inferred and the declaredpaddingare one type.packages/components/src/__tests__/container-padding-set-11424.test.tsx: derives the mapped set by renderingcontainerfor 0 through 32 plus -1, 0.5, 1.5 and 9.5, keeping the values that put a padding utility on the element. The pin asserts the derivation is not vacuous, that 9 and 20 draw no padding class, thatContainerSchema.paddingdeclares exactly the rendered set, and that the registration publishes exactly that set as atype: 'enum'input. It also checks the absent-key default ladder as a control.Assertions read the issue's kind, path and value list, and the message is checked only for the set itself: "the refusal names the set" is the contract here.
Ablation (head
5674cee1, under the shared verify lock)Each leg was applied with
ablation-replace.mjs(the anchor must hit exactly once, the blob hash is checked before and after) and wrapped in an EXIT/INT/TERM restore trap. Vitest aliases@object-ui/types,@object-ui/types/zodand@object-ui/coreto source, so the mutated file is what the pins load. Nodistwas involved.ContainerSchema.paddingback to.number()(anchor x1 to x0, blob62e8a896toc5cd8664): 7 failed, 13 passed of 20. The failures were the six refusal pins (three faces, 9 and 20) and the components pin "declares exactly the rendered set".01c7bf53to37e7ecfe): 1 failed, 4 passed of 5, the pin "the registration publishes exactly the rendered set".number(blob2d7a387cto6ca5a812):tsc -p tsconfig.test.jsonexits 2. It reports twoTS2578unused@ts-expect-error, aTS2322in the two-faces check, and aTS2322in the existingzod-mirror-parity.test.tsledger, which also guards this pair.git diff HEADwas 0 lines. Every leg went red as predicted. None reversed.Mechanism hypotheses (dispatch zone 2), measured
padding ===occurrence incontainer.tsxis in the comment aboveconst padding = schema.padding ?? 4;, which quotes thepadding === 0 && 'p-0'branch. The set in this PR comes from the rendered branches (the components pin), not from the card.maxWidth's registration form istype: 'enum'withenum: [{ label, value }]. It is notoptions, and that form came from objectui#4889, as the registration comment records. objectui#10286 only narrowed the zod arm. The grammar admits numeric values:ComponentInput.enumis typedstring[]or{ label: string; value: any }[]. The manifest serializer forwardsenumverbatim,validateTree's enum arm is a strictincludes, and codegenJSON.stringifys each value. So the registration took theenumbranch of zone 3's suggested route, not the description-only one.@objectstack/specis 17.5.0, and itsComponentPropsMap(55 keys) has nocontainerrow. In its.d.tsfilespaddingappears only in two prose comments indist/data. The control (maxWidthornameField) hits three.d.tsfiles. So the read site decides, per the objectui#7759 ruling.container. It excludedCHANGELOG.md. It found 53containernodes, 23 of them carryingpadding:0thirteen times,4four,6four,8twice. All of them are mapped, so nothing was re-spelled and no fixture became a refusal pin. Other readings: the registration'sdefaultProps.paddingis4, thePageDesignerpalette'scontainerseed carries nopadding, and no YAMLtype: containerand nocontainerJSX tag inside a string were found. This is a one-time reading at base, not a live count.For the contract reviewer: the
ComponentInput.typeruling of 2026-08-17That ruling ("the coarse arm plus
descriptionIS the publication face's expression ceiling today, and SPEC IS THE SOLE JUDGE OF VALUES") deferred givingComponentInputconstraint slots (integer,min,max). I read it as not barring this change. Theenumkind already exists,maxWidthon this same registration already uses it, and no slot is added. For an objectui-own key, the value judge is objectui's own declaration, which this PR closes first. Flagged so the record can say so explicitly.Docs
content/docs/components/layout/container.mdx: the interface fence states the literal union, and a short paragraph names the set and the refusal.content/docs/guide/layout.md(Page Padding): thepaddingsentence names the steps and says any other number is refused.Neither file is on the claim's listed file surface (no corpus document authors an unmapped value, so H4 added none). Both are here under AGENTS.md commandment 2 (docs-driven): the reference fence restates the TS face this PR changes. The os-dev report declares this as a surface extension.
Local verification (all at head
5674cee1)pnpm --filter '@object-ui/components^...' run build(8 projects, exit 0). The rebuiltpackages/types/dist/layout.d.tscarries the literal union.pnpm --filter @object-ui/types run type-checkandpnpm --filter @object-ui/components run type-check: exit 0. Both scripts runtsconfig.test.json, and--listFilesOnlyshows that program includes each new test file.pnpm exec vitest run packages/types/: 331 files, 8777 tests passed.pnpm exec vitest run packages/components/: 352 passed and 1 skipped file, 3566 passed and 24 skipped tests.pnpm exec vitest run examples/schema-catalog/ packages/sdui-parser/: 59 files, 2526 tests passed.apps/console/src/__tests__(registry inputs, public contract, manifest build, intrinsics compile, union specimens and others),packages/coreregistry tier tests and threeapp-shellfiles that rendercontainer. All 17 files passed, 642 tests.pnpm check:doc-snippetsexit 0 (776 of 776 blocks judged, 0 failed), after the scoped build its precondition names.pnpm check:doc-examplesexit 0.pnpm check:doc-typesexit 0.pnpm check:component-surface-parityexit 0 (report-only; the key set is unchanged and there is nocontainerrow).pnpm check:sdui-registration-pinsexit 0 against a fresh console build: 16 of 16 pinned registrations present.node scripts/check-changeset-no-major.mjs,node scripts/check-changeset-presence.mjs,pnpm check:new-line-citations,pnpm check:control-bytes,pnpm check:changeset-claims,pnpm check:pending-changeset-literals,pnpm check:doc-fences: all exit 0.--no-inline-config, JSON format): 5 files, 0 errors, 2 warnings, both on the unchangedforwardRefdeclaration incontainer.tsx. This is a targeted run on the touched files, not a proven narrowing ofpnpm lint. The repo-wide lint belongs to CI.Acceptance notes
StackSchema.gapand the flexgaparez.number()with the description "Tailwind scale 0-8". A probe at head ran each document through both faces and the real renderer.{ type: 'stack', gap: 7 }and{ type: 'flex', properties: { gap: 9 } }parse green on both and render no gap class. The controls withgap: 4render the ladder.stackmaps 0 through 6, 8 and 10, so7sits inside the scale its own description advertises and draws nothing. Reported to the seat in the os-dev report.gap,grid.tsxbuilds an arbitrarygap-[…rem]class at runtime, which a compiled Tailwind stylesheet may not contain. Not filed.pagenode'spaddingrefusal text (objectui#11318) still reads true: "a declared, rendered NUMBER on the container's spacing scale". It does not list the set. Left as is.jsdom@30's engines floor (^22.22.2), andengine-strict=truerefuses the install. Every local command here ran on Node v22.23.3 (verified tarball from nodejs.org, kept in the session scratchpad). CI usesnode-version: '22.x'.Generated by Claude Code