Skip to content

fix(types,components)!: a container's padding is one of the renderer's twelve steps on both faces (objectui#11424) - #11470

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11424-container-padding-set
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11424-container-padding-set

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11424

Clause-②: no

ContainerSchema.padding now accepts only the steps the container renderer maps (0 to 8, 10, 12 and 16), on both published faces. The refusal names the set. The registration's padding input 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 is minor and states the breaking authoring meaning, because objectui never declares major (AGENTS.md section 9, version alignment).

The accept-set change

surface before (base 5988b6b53) after (this branch)
zod mirror ContainerSchema.padding z.number() z.literal([0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16]); its error message lists the set
TS face ContainerSchema member padding number the literal union of the same twelve numbers
container registration, padding input type: 'number', description Padding value (0, 1-8, 10, 12, 16) type: 'enum', twelve { label, value } entries with numeric values, in the object form maxWidth uses
renderer (container.tsx) twelve padding === N branches, schema.padding ?? 4 unchanged (comments only)

Readings, before and after

reading base head
{ type: 'container', padding: 9 }, tolerant face (safeValidateSchema, which objectui validate runs) parses green refused: one invalid_value issue at padding, values = the twelve steps, and the message lists them
same document, strict authoring face (StrictAnyComponentSchema) parses green refused, same issue
padding: 20, both faces parses green refused, same issue
each of the twelve steps, both faces green green
no padding key, both faces (control) green green
rendered container, padding: 9 or 20 no padding class no padding class (the renderer is unchanged; the value is refused before it)
rendered container, no padding key (control) p-2 sm:p-3 md:p-4 p-2 sm:p-3 md:p-4
validateTree against the built console's dist/sdui.manifest.json, container with padding={9} or padding={20} no diagnostic (base row type: 'number') error invalid-enum; the message ends "padding"=9 is not one of [0,1,2,3,4,5,6,7,8,10,12,16]
same, padding={8} and no padding no diagnostic no diagnostic
generateDts over that manifest, the container props padding?: number the twelve numeric literals

How 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 real container through SchemaRenderer. The manifest readings come from pnpm --filter @object-ui/console build, which writes dist/sdui.manifest.json, then validateTree / generateDts from the built @object-ui/sdui-parser. The base row is the head manifest with the padding input 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 at padding, with code invalid_value, issue values equal 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, because BaseSchema is .passthrough()). Two compile-time checks follow. @ts-expect-error on 9 and on 20 against the TS face. A mutual-assignability check that the zod-inferred and the declared padding are one type.
  • packages/components/src/__tests__/container-padding-set-11424.test.tsx: derives the mapped set by rendering container for 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, that ContainerSchema.padding declares exactly the rendered set, and that the registration publishes exactly that set as a type: '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/zod and @object-ui/core to source, so the mutated file is what the pins load. No dist was involved.

  • A1, ContainerSchema.padding back to .number() (anchor x1 to x0, blob 62e8a896 to c5cd8664): 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".
  • A2, the registration enum drops step 16 (blob 01c7bf53 to 37e7ecfe): 1 failed, 4 passed of 5, the pin "the registration publishes exactly the rendered set".
  • A3, the TS face back to number (blob 2d7a387c to 6ca5a812): tsc -p tsconfig.test.json exits 2. It reports two TS2578 unused @ts-expect-error, a TS2322 in the two-faces check, and a TS2322 in the existing zod-mirror-parity.test.ts ledger, which also guards this pair.
  • Restore: each leg's restore read blob == HEAD blob, and the final git diff HEAD was 0 lines. Every leg went red as predicted. None reversed.

Mechanism hypotheses (dispatch zone 2), measured

  • H1 holds. The renderer has exactly twelve branches: 0 through 8, 10, 12 and 16. The thirteenth padding === occurrence in container.tsx is in the comment above const padding = schema.padding ?? 4;, which quotes the padding === 0 && 'p-0' branch. The set in this PR comes from the rendered branches (the components pin), not from the card.
  • H2 holds in substance, but the spelling differs. maxWidth's registration form is type: 'enum' with enum: [{ label, value }]. It is not options, 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.enum is typed string[] or { label: string; value: any }[]. The manifest serializer forwards enum verbatim, validateTree's enum arm is a strict includes, and codegen JSON.stringifys each value. So the registration took the enum branch of zone 3's suggested route, not the description-only one.
  • H3 holds. The installed @objectstack/spec is 17.5.0, and its ComponentPropsMap (55 keys) has no container row. In its .d.ts files padding appears only in two prose comments in dist/data. The control (maxWidth or nameField) hits three .d.ts files. So the read site decides, per the objectui#7759 ruling.
  • H4: no corpus document authors an unmapped value. A census at base covered git-tracked JSON, md/mdx fences (JSON parsed, with ts/tsx/js through the TypeScript AST) and TS/TSX/JS object literals, plus JSX tags named container. It excluded CHANGELOG.md. It found 53 container nodes, 23 of them carrying padding: 0 thirteen times, 4 four, 6 four, 8 twice. All of them are mapped, so nothing was re-spelled and no fixture became a refusal pin. Other readings: the registration's defaultProps.padding is 4, the PageDesigner palette's container seed carries no padding, and no YAML type: container and no container JSX tag inside a string were found. This is a one-time reading at base, not a live count.

For the contract reviewer: the ComponentInput.type ruling of 2026-08-17

That ruling ("the coarse arm plus description IS the publication face's expression ceiling today, and SPEC IS THE SOLE JUDGE OF VALUES") deferred giving ComponentInput constraint slots (integer, min, max). I read it as not barring this change. The enum kind already exists, maxWidth on 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): the padding sentence 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)

  • Build: pnpm --filter '@object-ui/components^...' run build (8 projects, exit 0). The rebuilt packages/types/dist/layout.d.ts carries the literal union.
  • pnpm --filter @object-ui/types run type-check and pnpm --filter @object-ui/components run type-check: exit 0. Both scripts run tsconfig.test.json, and --listFilesOnly shows 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.
  • The registry and manifest consumers: 17 files in apps/console/src/__tests__ (registry inputs, public contract, manifest build, intrinsics compile, union specimens and others), packages/core registry tier tests and three app-shell files that render container. All 17 files passed, 642 tests.
  • pnpm check:doc-snippets exit 0 (776 of 776 blocks judged, 0 failed), after the scoped build its precondition names. pnpm check:doc-examples exit 0. pnpm check:doc-types exit 0.
  • pnpm check:component-surface-parity exit 0 (report-only; the key set is unchanged and there is no container row). pnpm check:sdui-registration-pins exit 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.
  • ESLint on the five changed source and test files (--no-inline-config, JSON format): 5 files, 0 errors, 2 warnings, both on the unchanged forwardRef declaration in container.tsx. This is a targeted run on the touched files, not a proven narrowing of pnpm lint. The repo-wide lint belongs to CI.

Acceptance notes

  • Same defect, other keys, not changed here. StackSchema.gap and the flex gap are z.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 with gap: 4 render the ladder. stack maps 0 through 6, 8 and 10, so 7 sits inside the scale its own description advertises and draws nothing. Reported to the seat in the os-dev report.
  • Unmeasured observation: for an unmapped gap, grid.tsx builds an arbitrary gap-[…rem] class at runtime, which a compiled Tailwind stylesheet may not contain. Not filed.
  • The page node's padding refusal 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.
  • Toolchain: the container's Node is v22.22.0, below jsdom@30's engines floor (^22.22.2), and engine-strict=true refuses the install. Every local command here ran on Node v22.23.3 (verified tarball from nodejs.org, kept in the session scratchpad). CI uses node-version: '22.x'.

Generated by Claude Code

claude added 2 commits October 2, 2026 13:04
…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>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types package: components tests labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 14 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

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

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

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

.changeset/5632-svg-host-dom-passthrough.md

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

    Both renderers forwarded their whole prop bag to the SVG they render, so every authored SDUI key on the node became an attribute — 14 per target, and icon="check" on all 71 icon nodes in the schema catalog. The two nodes declare different keys, and each renderer consumes its own node's keys by name: IconSchema (packages/types/src/layout.ts) against the pass-through docblock in renderers/basic/icon.tsx, and SpinnerSchema (packages/types/src/feedback.ts) against the one in renderers/feedback/spinner.tsx. icon and color are IconSchema's alone — SpinnerSchema declares neither. Read those two declarations for what they carry; this paragraph deliberately copies no member list. So the SDUI pass-through list withholds nothing they need.

.changeset/6872-app-shell-branding-title-jsdoc.md

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

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

.changeset/6951-text-value-retired.md

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

    Two published faces, one retirement. The TypeScript interface TextSchema (@object-ui/types, layout.ts) declares value?: never; the Zod mirror TextSchema (@object-ui/types/zod, layout.zod.ts) declares value as a retirementTombstone(), so the key stays DECLARED and is refused BY NAME — a plain deletion would have let an authored value ride BaseSchema's .passthrough() into a silent blank, which is worse than the tolerated fallback it replaces. The value?: string members of TextSpanSchema and TabsSchema in the same file are other schemas' contracts and are unchanged.

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

    Two published faces, one retirement. The TypeScript interface TextSchema (@object-ui/types, layout.ts) declares value?: never; the Zod mirror TextSchema (@object-ui/types/zod, layout.zod.ts) declares value as a retirementTombstone(), so the key stays DECLARED and is refused BY NAME — a plain deletion would have let an authored value ride BaseSchema's .passthrough() into a silent blank, which is worse than the tolerated fallback it replaces. The value?: string members of TextSpanSchema and TabsSchema in the same file are other schemas' contracts and are unchanged.

.changeset/7735-zod-mirrors-stop-authoring-defaults.md

  • names container.tsx → packages/components/src/renderers/layout/container.tsx — edited by this change

    | key | mirror wrote | the renderer applies | |---|---|---| | ContainerSchema.maxWidth | 'lg' | container.tsx: ?? 'xl' | | FlexSchema.align | 'center' | flex.tsx: \|\| 'start' |

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

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

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

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

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

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-form-layout.md

.changeset/8499-node-slot-registered-arms.md

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

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). ⚠️ Dated note, 2026-09-27 — that set has since gained code — objectui#10756. At this change TAGS and this arm both named 37 tags; both now name 38, and the parity pin counts 38. The rest of this entry is kept as the reading of this change. - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. ⚠️ Dated note, 2026-09-28 — inputType is now declared on this arm, as a refusal — objectui#8762. Later in this same release the arm declares inputType on both faces and refuses it by name (?: never on the TypeScript face, a retirementTombstone on the zod mirror, at path inputType), with guidance pointing at { "type": "input", "inputType": "email" }. So "inputType is deliberately NOT declared on this arm" no longer holds; the reason does, since the wrapper still spreads its own value last. The rest of this entry is kept as the reading of this change. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).

.changeset/8505-grid-columns-breakpoint-narrowing.md

.changeset/8516-8556-mirror-partial-record-narrowing.md

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

    | key | mirror was | mirror is | | :-- | :--------- | :-------- | | GridSchema.columns (zod/layout.zod.ts) | z.record(z.string(), z.number()) | a PARTIAL record over the six breakpoints | | ReportComponentSchema.exportConfigs (zod/reports.zod.ts) | z.record(z.string(), ReportExportConfigSchema) | a PARTIAL record over ReportExportFormat |

.changeset/8871-page-node-refuses-breadcrumbs.md

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

    What was measured, on this branch's base 93127bd6f. Zero readers, with a point-access probe rather than a bare word: on that base \.breadcrumbs scores 0 tree-wide (exit 1) against \.breadcrumb\b's 12 files tree-wide (10 under packages/) as the lit control. At head the same two probes read 16 and 13 and \.breadcrumbs is exit 0 over 4 files — every hit one of this branch's own four files (this changeset, the refusal pin, layout.ts, zod/layout.zod.ts) quoting the probe string, and the pin's own exclusions put head back at exit 1. The base reading is the measurement; the head reading is this branch's echo of it. The bare word would have lied — it also names Sentry's own unrelated concept (app-shell/src/observability/sentry.ts) and appears in two comments listing UI surfaces (core/src/utils/record-title.ts, layout/src/NavigationRenderer.tsx), so a bare probe reports five readers that do not exist.

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

    What was measured, on this branch's base 93127bd6f. Zero readers, with a point-access probe rather than a bare word: on that base \.breadcrumbs scores 0 tree-wide (exit 1) against \.breadcrumb\b's 12 files tree-wide (10 under packages/) as the lit control. At head the same two probes read 16 and 13 and \.breadcrumbs is exit 0 over 4 files — every hit one of this branch's own four files (this changeset, the refusal pin, layout.ts, zod/layout.zod.ts) quoting the probe string, and the pin's own exclusions put head back at exit 1. The base reading is the measurement; the head reading is this branch's echo of it. The bare word would have lied — it also names Sentry's own unrelated concept (app-shell/src/observability/sentry.ts) and appears in two comments listing UI surfaces (core/src/utils/record-title.ts, layout/src/NavigationRenderer.tsx), so a bare probe reports five readers that do not exist.

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

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

.changeset/9092-inline-locale-declared-face.md

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

    • AppComponentSchema.label (app.ts) - ObjectGridSchema.label and .description (objectql.ts) - PageNodeSchema.aria.ariaLabel (layout.ts)

.changeset/layout-default-jsdoc-7361.md

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

    The published @default documentation on two layout.ts members now matches the value the renderer actually applies. ContainerSchema.maxWidth documented 'lg' while container.tsx applies schema.maxWidth ?? 'xl', and the shared FlexLayoutProps.align documented 'center' while flex.tsx applies schema.align || 'start' and stack.tsx applies schema.align || 'stretch'. The renderers are unchanged — they are the authority for what runs — so only the docblocks moved; align now states both consumers in prose instead of carrying a single @default, because one member shared by two deliberately divergent component types cannot have one correct default.

  • names container.tsx → packages/components/src/renderers/layout/container.tsx — edited by this change

    The published @default documentation on two layout.ts members now matches the value the renderer actually applies. ContainerSchema.maxWidth documented 'lg' while container.tsx applies schema.maxWidth ?? 'xl', and the shared FlexLayoutProps.align documented 'center' while flex.tsx applies schema.align || 'start' and stack.tsx applies schema.align || 'stretch'. The renderers are unchanged — they are the authority for what runs — so only the docblocks moved; align now states both consumers in prose instead of carrying a single @default, because one member shared by two deliberately divergent component types cannot have one correct default.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 5988b6b53 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 2014 pending declaration(s) that publish a body (2642 pending in total). · run

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3551.3 KB 3574.6 KB
Main entry chunk (gzip) 150.3 KB 350 KB
Entry file index-P_cdRJrF.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 571.32KB 136.84KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.57KB 66.16KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.82KB 11.27KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.53KB 22.84KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 141.26KB 38.01KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.27KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.14KB 45.06KB
plugin-grid (index.js) 232.66KB 63.77KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.60KB 28.96KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5674cee1bd014fab5b8d3af709aff1b9560abff7
Local-runs: none

Inputs read: card objectui#11424 (body and all 3 comments: triage 5944196293, claim 5952634211, os-dev report 5953957690), PR #11470 (body, 8-file list, net diff against main at 5988b6b53), the check-runs on the head (two reads, timed below), and four files at the head through the contents API to verify the dev's readings (container.tsx, packages/types/src/base.ts, types/index.ts, core/src/registry/Registry.ts). Nothing built, run or re-run.

① Derived judgments

Each accept-set or public-surface change the diff implies, named right or wrong against the card's direction (triage 5944196293: a literal union of the twelve mapped values on both faces, TS twin follows, the refusal names the set, the renderer does not round or clamp; pins: 9 and 20 refused with the set named, each mapped value parses, an absent key keeps the default).

  1. zod mirror ContainerSchema.padding, z.number() to z.literal([0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16], { error }) (packages/types/src/zod/layout.zod.ts). Public accept set on @object-ui/types/zod narrows from every number to twelve. The refusal is one invalid_value issue at ['padding'] carrying the twelve in values, with a message that lists them. Pinned on three doors (the node, safeValidateSchema, StrictAnyComponentSchema) for 9 and 20, each mapped step, and the absent-key control, plus a lit control that .passthrough() still admits an undeclared key. Right. The spelling is zod's multi-literal form rather than the z.union of literals maxWidth uses two lines above; the substance (a closed literal set whose issue names the set) is the direction's, and the multi-literal form is what makes values carry the set the pins assert.

  2. TS face ContainerSchema.padding, number to 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 16 (packages/types/src/layout.ts). Public type narrows; tsc now refuses 9 and 20 and any number-typed variable. Held to the mirror by the new mutual-assignability pin and, per the dev's A3 ablation, by the existing zod-mirror-parity ledger. Right. The TS twin follows as the direction requires.

  3. container registration padding input, type: 'number' to type: 'enum' with twelve { label, value } entries whose values are numbers; description re-worded (packages/components/src/renderers/layout/container.tsx). Public surface on @object-ui/components registry config, which the console serialises into dist/sdui.manifest.json; downstream, validateTree answers 9 or 20 with invalid-enum and generateDts types the prop as the twelve literals. The claim listed this input in scope ("only in the shape objectui#10286 gave maxWidth"). Verified at the head: 'enum' is an arm of ComponentInputControlType (packages/types/src/base.ts 642 to 653) and the slot is enum?: string[] | { label: string; value: any }[] (base.ts 877), so numeric values are inside the published grammar; maxWidth on this same registration already uses the { label, value } object form. Right. One citation correction, which the dev measured and declared: the registration form dates from objectui#4889, and objectui#10286 narrowed only maxWidth's zod arm. The claim's intent (match maxWidth's registration form) is served; the number that gave it that form is ContainerSchema.maxWidth: false 是声明了却读不到的值 —— container.tsx 用 || 把它折成 max-w-xl #4889.

  4. Renderer branches: unchanged. Verified at the head: exactly twelve padding === N branches (container.tsx 63 to 74: 0 to 8, 10, 12, 16), schema.padding ?? 4 as the only default, defaultProps.padding: 4, no rounding or clamping; the thirteenth padding === occurrence (line 28) is a comment. The dev's H1 holds. Right, and it keeps the triage ⛔.

  5. Docs (content/docs/components/layout/container.mdx interface fence plus one paragraph; content/docs/guide/layout.md one sentence). Not a published package surface. The fence now states the union of item 2 and the paragraph states the set and the refusal; nothing beyond the set is claimed. Right in content; its scope standing is answered in ③.

  6. Tests (two new pin files). Not a public surface. Together they cover every triage pin: 9 and 20 refused with the set named on three faces; each mapped step parses; an absent key parses and still renders p-2 sm:p-3 md:p-4; and the components pin derives the mapped set by rendering the real container and holds the declaration and the registration enum to it. Right.

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

  • Changeset .changeset/11424-container-padding-set.md: @object-ui/types: minor, @object-ui/components: minor. The diff publishes changes in exactly those two packages (packages/types/src/**, packages/components/src/**); docs and tests publish nothing. Match.
  • The level is minor carrying a "Breaking for a container that carries any other padding number" banner, as the claim itself specified ("one changeset (minor, with the breaking authoring banner)") and as the PR title's ! signals. The dev cites AGENTS.md section 9 (objectui never declares major); the head's changeset gates (Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report) are all green, and the dev's local check-changeset-no-major exited 0. The banner states the old behaviour (parsed clean, rendered flush), the new refusal on both faces, the registration change, that the renderer is unchanged, and a migration. Right. Nit, not a defect: the TS-face consequence that a number-typed variable no longer assigns is implied by the stated union rather than spelled out.
  • Clause-②: no. The accept set shrinks from every number to twelve on the zod face, the TS face, the registration input and therefore the manifest; nothing widens anywhere in the diff. The card's claim (Clause-②: no, "narrowing") and the PR body agree. Right.

③ Boundary flags

Dev flags, from the PR body and the os-dev report's deviations:

  1. The 2026-08-17 ComponentInput.type ruling ("coarse arm plus description is the ceiling; spec is the sole judge of values"), flagged by the dev as read-as-not-barring. Answered: not a bar. The ruling text is not on the card, so this is judged from the dev's quotation and the grammar at the head: the change adds no constraint slot to ComponentInput; it uses the existing 'enum' arm and the existing enum slot (base.ts 642 to 653, 877), the same arm maxWidth already uses on this registration. On "spec is the sole judge of values": the dev measured @objectstack/spec 17.5.0 has no container row and no padding declaration (H3), so under the objectui#7759 ruling the read site decides, and the read site is the renderer's twelve branches, verified at the head. The on-card rulings point the same way: triage accepted a closed set "on both faces" and the claim put the registration input in scope. If the seat's copy of the 2026-08-17 ruling forbids a registration enum from restating a set the coarse arm admits, that is the one line to re-open; nothing in the inputs says it does.

  2. Two docs files outside the claim's listed file surface (container.mdx, layout.md); the claim said "stop on breach". Answered: accepted as a declared, consequent extension, not a breach. The container.mdx fence restates the ContainerSchema TS face that item ① 2 changes; left as padding?: number it would state a type the package no longer exports. Both edits say only what the set is and that the rest is refused; the dev declared them in the report's deviations and in the PR body rather than silently. No corpus document authored an unmapped value (H4), so the claim's conditional corpus item added nothing, which is consistent.

  3. container.tsx comment edits beyond the padding input. Answered: comments only; the branches are unchanged at the head (item ① 4).

  4. H2 spelling (dispatch said options; the form is type: 'enum' with an enum array). Answered: measured by the dev and verified here at the head; item ① 3.

  5. Toolchain (local commands on Node 22.23.3 because the container's 22.22.0 is under jsdom's floor). Answered: no bearing on the contract. The check-runs on the head are the gate verdicts; the dev's local run environment is not.

  6. Attribution trailers (repo AGENTS.md's model-free pair, not the harness line). Answered: a repo-rule matter, no bearing on the contract.

open_questions: []. Nothing to answer.

out_of_scope_findings, both correctly left out of this diff and raised to the seat, not to this record: (a) StackSchema.gap and the flex gap are bare z.number() with a renderer-mapped scale (stack maps 0 to 6, 8, 10; flex 0 to 8), the same family as #11424 and #10286, for the seat to fold into a family card; (b) grid.tsx builds an arbitrary gap class at runtime for an unmapped gap, unmeasured.

Reviewer's own observations, none moving the verdict:

  • A numeric enum on a registration input is inside the grammar (value: any), and the dev names the manifest serializer, validateTree and codegen as number-safe. Whether a designer property control round-trips a numeric enum value as a number rather than a string was not among the consumers the dev listed and is unmeasured here; if one stringifies, that is that consumer's defect against an existing grammar, not this PR's contract. Worth one probe by the seat that owns the designer surface, not a bar.
  • The PR reads mergeable_state: behind main; the claim recorded file-disjoint siblings and that whichever lands second merges main. This record is on head 5674cee1.

Check-runs on 5674cee1bd014fab5b8d3af709aff1b9560abff7, as read:

  • Read 1 at 2026-10-02T13:57:48Z: 42 runs; 28 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard)), 11 in_progress (Lint, Type Check, Spec Main Shape Gate, Test (shard 1/8) to (shard 8/8)). Green and bearing on this contract: Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report, Doc Snippet Type Check, Doc Component Type Check, Doc Example Id Check, Skill Example Check, Build Docs, Build & E2E, Test (dist pins), Line Citation Gate, Governed Surface Queue Guard.
  • Read 2 at 2026-10-02T14:03:41Z, just before this post: 42 runs; 32 success, 3 skipped, 7 in_progress (Spec Main Shape Gate, Test (shard 1/8), 2/8, 4/8, 5/8, 6/8, 7/8), no red. Turned green between the reads: Lint, Type Check (the TS twin, the @ts-expect-error pins and the mirror-parity ledger compile under it), Test (shard 3/8), Test (shard 8/8).
  • Bearing and pending: the six remaining test shards carry the new pin files and the existing zod-mirror-parity ledger somewhere among them, and Spec Main Shape Gate guards the spec-aligned shape of main. Nothing read is red, and this review does not wait on them: the verdict below is the contract verdict on the diff as read, with the dev's local runs (20/20 new pins, 8777 types tests, 3566 components tests, type-check exit 0, three ablation legs red as predicted) as the dev's own evidence. A red on any of those seven when they complete is the seat's signal to re-open this record; a green completes the gate side of this head.

Implemented-by: claude/issue-11424-container-padding-set
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: PASS

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

Labels

documentation Improvements or additions to documentation package: components package: types tests

Projects

None yet

2 participants