fix(types,components,core)!: grid columns is one of the renderer's counts, 1 to 12, bare and at every breakpoint; the family pin holds every numeric layout input (objectui#11491) - #11504
Conversation
…unts, 1 to 12, bare and at every breakpoint; the family pin holds every numeric layout input (objectui#11491) GridSchema.columns closes to 1..12 on both faces, for the bare number and every breakpoint of the object form; the refusal names the set. The grid registration's columns / smColumns / mdColumns / lgColumns / xlColumns inputs become closed enums (columns also publishes the breakpoint object, members closed through `of`). The renderer's `|| 'grid-cols-2'` base fallback is removed: no validated document reaches an unmapped base count. GridBuilder.columns() takes the declared counts. The objectui#11474 enumeration pin is extended in place to every numeric layout input, counts included, with a ledger for the registration inputs no face declares. The shared zod helper is renamed rendererMappedSet. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…columns; say so instead of pinning the opposite (objectui#11491) tsc -p tsconfig.test.json measured the 8505 boundary row: TypeScript does not judge an index signature's value type against optional target members, so the computed-record escape covers values as well as keys. The row keeps its original record, the changeset and the 8505 dated note state the measured reading. 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: Rendered 2026-10-02T19:54Z by the ① Derived judgmentsEvery accept-set and public-surface move the diff implies, judged against triage's direction (
No governed surface is in the file list ( ② Semver levelChangeset
Pending changesets (2673 at the head, every one read as text). The readings this change makes false, and what carries each:
Left alone, each judged still true at the head: No pending reading made false by this change is without a note: ② passes. ③ Boundary flagsEvery dev flag (report
Implemented-by: VERDICT: PASS |
Fixes #11491
Clause-②: no (narrowing)
Why
no(a narrowing):grid.columnsshrinks from every number to the renderer's 1 to 12, at the bare number and at every breakpoint of the object form, on both declaration faces and on the five registration inputs. One surface moves the other way and is named here so the contract record can judge it: the SDUI manifest'scolumnsinput gains anobjectarm (the breakpoint object both declaration faces already accept), whose members are closed to the same twelve counts. Before, a breakpoint object of in-set counts drew atype-mismatchWARNING invalidateTreewhile rendering correctly; now it draws nothing. See "The manifest's object arm" below. The changeset isminorwith the breaking meaning stated, because objectui never declaresmajor(AGENTS.md section 9, version alignment).Dispatched by the
domain:spec @ objectuiseat (objectui#10217) under triage's direction (comment5958209450) and the seat's claim (comment5959014547), sessionhttps://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC. This closes the numeric-layout family after objectui#10286 (container.maxWidth), objectui#11424 (container.padding) and objectui#11474 (the threegapkeys).The accept-set change
1fe05ff3)GridSchema.columnsz.number()OR a partial breakpoint record ofz.number(); describe "Number of columns (responsive)"GridSchema.columnsnumberOR Partial of Record of BreakpointName tonumberGridColumnCountOR Partial of Record of BreakpointName toGridColumnCount, whereGridColumnCountis the module-local literal union 1 to 12;@default 2keptgridregistration,columnsinputtype: 'number'type: ['enum', 'object'],enumof twelve{ label, value }entries (container.padding's form),of: 'enum'gridregistration,smColumns/mdColumns/lgColumns/xlColumnstype: 'number'type: 'enum', the same twelve entriesGridBuilder.columns()in@object-ui/corenumberExtractof the member'snumberarm)GRID_COLS[baseCols] || 'grid-cols-2'GRID_COLS[baseCols]; nothing rounds, clamps or substitutes (H3 below)The shared zod helper is renamed in place,
rendererSpacingStepstorendererMappedSet, because "spacing" became false for a count; it stays module-local, and no second helper was added. The four existing call sites are unchanged apart from the name. Their refusal and describe text is byte-identical (all four keys' refusal message and describe, read from the builtdistat base1fe05ff3and at head, compared withcmp): the spacing wording now comes from a small per-key table, and the four keys' pins (layout-gap-sets-11474.test.ts,container-padding-set-11424.test.ts) stay green unchanged. A key that also takes a breakpoint object passes ashapecallback (a typed overload, no cast), which builds the union from the one closed literal.The refusals as measured (head,
safeValidateSchemaandStrictAnyComponentSchemaalike):{ "type": "grid", "columns": 13 }(and 0, -1, 2.5, 16): oneinvalid_unionatcolumnswhose message is "columnson agridis one of 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 (objectui#11491): those are the counts the renderer maps to a column class, as the bare number and at every breakpoint of the object form. Any other count drew no column class where it was authored: ... Pick the count you meant from that set." Its count arm carriesinvalid_valuewithvalues= the twelve counts.{ "columns": { "md": 13 } }at every breakpoint: the sameinvalid_unionatcolumns; its object arm carriesinvalid_valueatmdwith the samevalues.{ "columns": { "xxl": 3 } }: unchanged,unrecognized_keysatcolumns(objectui#8516).A union cannot report
invalid_valueat the top level (both arms abort), which is why the code differs from thegapfamily; the set is named in the message and carried structurally in the arms.The manifest's object arm (the one widening)
Measured with a throwaway probe (real registry,
manifestFromConfigsplusvalidateTree), base then head:columns: 3columns: 13type-mismatch: expected one of the twelve counts or an objectcolumns: { xs: 1, md: 2 }type-mismatch: expected a numbercolumns: { md: 13 }type-mismatchmember-type-mismatch: [md] is not one of the twelvesmColumns: 13/mdColumns: 0invalid-enumsmColumns: 2type: 'enum'alone was measured as the alternative and not taken: it turns{ xs: 1, md: 2 }, a shape both declaration faces accept and the renderer draws, from a warning into aninvalid-enumERROR. The ruling closes every breakpoint key to 1 to 12; it does not refuse the object. The generated SDUI intrinsics typecolumnsas the twelve literals or a record of them (codegen readstypeandof). The objectstack JSX pages measured (command-center-jsx.page.ts) authorcolumns={4}andcolumns={3}only.Measurements (the dispatch's zone 2)
H1, the maps, holds. Re-read at
1fe05ff3:GRID_COLSand its five breakpoint twins (_SM,_MD,_LG,_XL,_2XL) each spell 1 to 12 and nothing else. Stylesheet criterion (the objectui#11474 pin's:src/index.csscompiled exactly asscripts/build-css.mjscompiles it, rules read off leading class selectors): all 72 classes found, 12 of 12 at base,sm:,md:,lg:,xl:and2xl:. Controls absent:grid-cols-13,md:grid-cols-13,grid-cols-0. The set for every breakpoint is 1 to 12.H2, the flat inputs: a declared-vs-registered split, measured and NOT decided here.
GridSchemadeclares nosmColumns/mdColumns/lgColumns/xlColumnson either face; the renderer reads them (they override the object form), and the registration publishes them (and seedsmdColumns: 2,lgColumns: 4). With any value: the tolerant face passes the key through (.passthrough()), the strict face refuses the key itself (unrecognized_keys), and the manifest typed itnumber.check:component-surface-parityalready reports all four asinterface-missing-key(unchanged by this PR). This PR closes their registration lists only, as the ruling says; whetherGridSchemadeclares them or the flat channel retires in favour of the breakpoint object is put to the seat as an open question in the dev report. The pin holds the reading in a two-way ledger (REGISTERED_NOT_DECLARED), so either decision turns it red on purpose.H3, the fallback: unreachable for a validated document, removed. Every path into the base class once the declaration is closed:
columns:baseColskeeps its initial2, mapped;xs(or{}):xs ?? 1, mapped;xsin 1 to 12: mapped;baseColsto 1, mapped (andsmto the smaller of 2 and the count, mapped);baseCols.So no validated document reaches an unmapped
baseCols. Removing the fallback changes only unvalidated input:columns: 0/-1/{ xs: 13 }drewgrid-cols-2at base, and now draw no base column class, as an unmapped count at every other breakpoint already did. Rendered at head: absentgrid-cols-2;{}grid-cols-1; bare 13grid-cols-1 sm:grid-cols-2(the ramp, no count of its own); bare 0 nothing.H4, the numeric population. The pin's enumeration (every
isContainerregistration in@object-ui/components; no other package registers a container with a numeric input:@object-ui/layoutregisterslayout:page:cardandapp-schema-renderer, neither numeric).aspect-ratio.ratiocontainer.paddingflex.gapgrid.gapstack.gapgrid.columnsxs...2xl): rendered = both faces = registration enum (offor the object)grid.smColumns/mdColumns/lgColumns/xlColumnsNo key is any-number while its renderer maps a closed set after this change, so nothing else is folded in.
H5, corpus.
git grepof every numericcolumnsliteral and every breakpointcolumnsobject inexamples/**,content/**,skills/**, package READMEs,apps/*/srcandpackages/*/src(CHANGELOGs excluded): every authored value is in 1 to 12 (bare 2, 3, 4; objects with 1 to 6). The out-of-set literals are test fixtures that render unvalidated nodes on purpose (grid-breakpoint-columns-7097.test.tsx:xl: 99,'2xl': 99, which still draw no class at that breakpoint) or a non-numeric control (xs: 'two'in the 8505 pin). The objectstack checkout's grid nodes author 3 and 4. Re-judged by running:examples/schema-catalog(all files, includinggrid-columns-key.test.tsxandsafe-validate-corpus-6318.test.ts),check:doc-snippets,check:doc-examples,check:skill-examples, all green.The pins
packages/components/src/__tests__/layout-spacing-sets-11474.test.tsx, extended IN PLACE (the file name is the family's, kept so the five pointers to it stay true; the header says the scope is every numeric layout input). Changes: the numeric predicate reads every arm of a multi-arm input; an input with anobjectarm adds one position per breakpoint (the vocabulary is asatisfies Record of BreakpointNameliteral, exhaustive at compile time); each position is classified by the classes its value moves (spacing, count, other; anything else fails as unclassified); a candidate is MAPPED when its drawn classes are all stylesheet rules AND one spells the value (so the ramp's classes do not count as a bare 13's own); an unmapped number may draw only classes several mapped values share, never one a single other value draws (this is the no-round/clamp/substitute assertion, and it is what catches the fallback); breakpoint positions require theobjectarm andof: 'enum'; the absent control keeps the spacing keys' default-step equality and holds every position to defined classes only; a two-wayREGISTERED_NOT_DECLAREDledger. 19 tests, 16 positions.packages/types/src/__tests__/grid-columns-set-11491.test.ts(new): the mirror's two arms are ONE closed literal of 1 to 12 over the six breakpoints; the describe states the set; per face, bare 0 / 13 / 16 / -1 / 2.5 and an unmapped count at each breakpoint refuse asinvalid_unionatcolumns, message naming the set, the arm carryinginvalid_valuewith the counts (at the breakpoint's path for the object); every count parses bare and at every breakpoint; absent and{}parse;xxlstaysunrecognized_keys; a lit control; compile-time@ts-expect-errorfor 13, 0 and{ md: 13 }; the two faces state one set.packages/core/src/builder/__tests__/schema-builder.test.ts:grid().columns(12)passes both faces;grid().columns(13)is atscerror.grid-columns-breakpoint-narrowing-8505.test.tspinned the member shape withnumber; the shape rows now carry the count set. Its boundary row "a computedRecordof string to number still assigns" was measured withtsc -p tsconfig.test.jsonand STILL compiles (TypeScript does not judge an index signature's value type against optional members), so the row keeps its record and says the escape covers values too; the zod mirror judges a computed record's counts. Its non-fresh row gainedas constso a widenednumberdoes not hide the key fact it pins.mirror-partial-record-narrowing-8516.test.ts's partiality row now compares against the count set.Ablations (committed implementation at
66c328cd,node ../objectstack/scripts/ablation-replace.mjswrap mode: anchor hit counted, blob moved, restore proven by blob equal to HEAD and an emptygit diff HEAD)The subjects resolve to source (the vitest alias map points
@object-ui/types,/zodand@object-ui/coreatsrc, and the components pin imports../renderers), so no rebuild sits between mutation and reading.grid.tsxbase class regains|| 'grid-cols-2'grid.columns 0: expected [ 'grid-cols-2' ] to deeply equal [], same atgrid.columns.xscolumnsinput back totype: 'number'(enum, object arm,ofremoved)grid.columns.xs;grid.columns registration input: expected [ 'number' ] to include 'enum'GRID_COLUMN_COUNTSgains 13values, all 7grid.columnspositions)GridSchema.columnsback tonumbertsc -p tsconfig.test.jsonred@ts-expect-errorand the faces-agreeSamein the new pin, the 8505 shape and key rows, the 8516 face row, and the existingzod-mirror-parity.test.tspairPending changesets (the sweep)
Read every pending entry that names a symbol, file or behaviour this diff moves (a targeted grep for the grid keys, maps, helper, pin and builder, plus the 29
check:changeset-claimsleads). Dated notes appended to the three whose sentences became false:8505-grid-columns-breakpoint-narrowing.md: it states the member type withnumbervalues.11441-retire-nav-responsive-grid-layout.mdand11441-known-types-retired-layout-keys.md: their migration saysgridtakes "the same breakpointcolumnsobject" asresponsive-grid; eachCcount must now be 1 to 12 (every countResponsiveGriddrew, 1, 2, 3, 4, 6 and 12, is).Left alone, each still true at head:
7097-grid-2xl-breakpoint.md(the 2xl map and the ramp),7735-zod-mirrors-stop-authoring-defaults.md(absentcolumnsrenders fromlet baseCols = 2),8516-8556-mirror-partial-record-narrowing.md(the partial record over six breakpoints),11474-layout-spacing-sets.md(the gap sets and thegapregistration inputs),grid-dom-attribute-whitelist-4787.mdand8912-grid-children-items.md(DOM pass-through andchildren),9910-children-slot-declare-and-pin.md; the othercheck:changeset-claimsleads nameschema-reference.md,layout.tsorlayout.zod.tsfor other keys.Docs
content/docs/components/layout/grid.mdx: the interface block showedcolumns?: number; it now shows the count set and the breakpoint object, with a paragraph on the refusal.content/docs/api/schema-reference.md,GridSchematable: thecolumnsrow now states the count set. Itsgaprow still saidnumber("Tailwind spacing scale") after objectui#11474; it now states the ten steps. That second row is the same family's stale doc on the same table, repaired here under the bounded in-place rule (same class, mechanical, the claim's docs surface, same doc gates).Gates at head
66c328cdpnpm --filter '@object-ui/components^...' run build, thenturbo run build --filter='./packages/*' --concurrency=2(39 of 39) for the doc gates.type-check(script name echoed):@object-ui/types0,@object-ui/core0,@object-ui/components0. The test programs include the edited pins (--listFilesOnly).packages/types/339 files, 9080 passed.packages/core/194 files, 3850 passed, 27 skipped.packages/components/354 passed, 1 skipped; 3597 passed, 24 skipped.examples/schema-catalog/,packages/sdui-parser/and the registry-reading suites together 71 files, 3145 passed (consolecomponent-input-union-specimens,ga-honoured-inputs-author-reach,html-tier-manifest,public-contract,registry-inputs-spec-parity,sdui-intrinsics-compile-11075,sdui-manifest-build-11403; app-shellwidget-dom-leak-sweep; layoutcontainment-declared-slot-9910; plugin-designerdesignerRegistrationInputs-11434;scripts/__tests__/check-component-surface-parity.test.ts). The full suites ran at66c328cd, before the container restart; after it the build was redone and the type-checks and the targeted pins re-ran green at the same head (12 files, 345 passed).check:doc-snippets(776 of 776 judged, 0 failed),check:doc-examples,check:skill-examples,check:doc-types,check:doc-fences,check:component-surface-parity(report-only; totals unchanged, 221, and the fourgridflat-input rows unchanged),check:new-line-citations(0 new),check:control-bytes,check:changeset-claims,check:pending-changeset-literals,check-changeset-no-major.mjs,check-changeset-presence.mjs,check:i18n-designer-parity,check:i18n-keys,check:designer-field-key-parity,check:unreferenced-sources,check:handler-key-reads,check:doc-example-readers,check:readme-exports,check:dist-completeness,check:spec-symbols,check:registry-bare-names,check:test-path-roots.eslint --no-inline-config --format jsonover the 9 changed.ts/.tsxfiles. All 9 are linted by the root config (isPathIgnoredfalse for each); the JSON reports 9 files, 0 errors, 4 warnings, allno-explicit-anyon untouched lines. The config enables no type-aware linting, so this diff cannot move a verdict on an untouched file. The repo-widepnpm lintbelongs to CI.NOT MEASURED:
check:sdui-registration-pins. Reason: it weighsapps/console/dist/assets, which needs a full console build. It pins that registration keys named by thesideEffectsarrays survive bundling; this diff moves nosideEffectsarray and no registration key, only input types. CI runs it.Acceptance notes (observations, not filed)
grid.tsx's gap line still ends with a runtime-builtgap-[...]class for a number outsideGAPS(noted on objectui#11474). It is a renderer branch this card's file surface excludes; unreachable for a validated document since objectui#11474. Carrier: none.8516-8556-mirror-partial-record-narrowing.mdstill says the CLI showsCode: invalid_keyfor{ xxl: 6 }; since zod 4.6 (objectui#11073) it isunrecognized_keys. Not moved by this diff, so no dated note here. Carrier: none.Generated by Claude Code