Skip to content

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

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11491-grid-columns-set
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11491-grid-columns-set

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11491

Clause-②: no (narrowing)

Why no (a narrowing): grid.columns shrinks 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's columns input gains an object arm (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 a type-mismatch WARNING in validateTree while rendering correctly; now it draws nothing. See "The manifest's object arm" below. The changeset is minor with the breaking meaning stated, because objectui never declares major (AGENTS.md section 9, version alignment).

Dispatched by the domain:spec @ objectui seat (objectui#10217) under triage's direction (comment 5958209450) and the seat's claim (comment 5959014547), session https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC. This closes the numeric-layout family after objectui#10286 (container.maxWidth), objectui#11424 (container.padding) and objectui#11474 (the three gap keys).

The accept-set change

surface before (base 1fe05ff3) after (head, see the gates section)
zod GridSchema.columns z.number() OR a partial breakpoint record of z.number(); describe "Number of columns (responsive)" ONE closed literal of 1 to 12, used as the bare arm AND as the record's value; the union's own refusal names the set; describe "Column count, one of 1, 2, ..., 12, bare or per breakpoint (default 2)"
TS GridSchema.columns number OR Partial of Record of BreakpointName to number GridColumnCount OR Partial of Record of BreakpointName to GridColumnCount, where GridColumnCount is the module-local literal union 1 to 12; @default 2 kept
grid registration, columns input type: 'number' type: ['enum', 'object'], enum of twelve { label, value } entries (container.padding's form), of: 'enum'
grid registration, smColumns / mdColumns / lgColumns / xlColumns type: 'number' type: 'enum', the same twelve entries
GridBuilder.columns() in @object-ui/core number the declaration's bare-count arm (Extract of the member's number arm)
renderer base class GRID_COLS[baseCols] || 'grid-cols-2' GRID_COLS[baseCols]; nothing rounds, clamps or substitutes (H3 below)

The shared zod helper is renamed in place, rendererSpacingSteps to rendererMappedSet, 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 built dist at base 1fe05ff3 and at head, compared with cmp): 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 a shape callback (a typed overload, no cast), which builds the union from the one closed literal.

The refusals as measured (head, safeValidateSchema and StrictAnyComponentSchema alike):

  • { "type": "grid", "columns": 13 } (and 0, -1, 2.5, 16): one invalid_union at columns whose message is "columns on a grid is 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 carries invalid_value with values = the twelve counts.
  • { "columns": { "md": 13 } } at every breakpoint: the same invalid_union at columns; its object arm carries invalid_value at md with the same values.
  • { "columns": { "xxl": 3 } }: unchanged, unrecognized_keys at columns (objectui#8516).

A union cannot report invalid_value at the top level (both arms abort), which is why the code differs from the gap family; 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, manifestFromConfigs plus validateTree), base then head:

document base head
columns: 3 nothing nothing
columns: 13 nothing error type-mismatch: expected one of the twelve counts or an object
columns: { xs: 1, md: 2 } warning type-mismatch: expected a number nothing
columns: { md: 13 } warning type-mismatch error member-type-mismatch: [md] is not one of the twelve
smColumns: 13 / mdColumns: 0 nothing error invalid-enum
smColumns: 2 nothing nothing

type: '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 an invalid-enum ERROR. The ruling closes every breakpoint key to 1 to 12; it does not refuse the object. The generated SDUI intrinsics type columns as the twelve literals or a record of them (codegen reads type and of). The objectstack JSX pages measured (command-center-jsx.page.ts) author columns={4} and columns={3} only.

Measurements (the dispatch's zone 2)

H1, the maps, holds. Re-read at 1fe05ff3: GRID_COLS and 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.css compiled exactly as scripts/build-css.mjs compiles it, rules read off leading class selectors): all 72 classes found, 12 of 12 at base, sm:, md:, lg:, xl: and 2xl:. 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. GridSchema declares no smColumns / mdColumns / lgColumns / xlColumns on either face; the renderer reads them (they override the object form), and the registration publishes them (and seeds mdColumns: 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 it number. check:component-surface-parity already reports all four as interface-missing-key (unchanged by this PR). This PR closes their registration lists only, as the ruling says; whether GridSchema declares 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:

  • absent columns: baseCols keeps its initial 2, mapped;
  • an object without xs (or {}): xs ?? 1, mapped;
  • an object with xs in 1 to 12: mapped;
  • a bare count of 1: mapped; a bare count of 2 to 12: the mobile-first ramp sets baseCols to 1, mapped (and sm to the smaller of 2 and the count, mapped);
  • the flat inputs never touch baseCols.

So no validated document reaches an unmapped baseCols. Removing the fallback changes only unvalidated input: columns: 0 / -1 / { xs: 13 } drew grid-cols-2 at base, and now draw no base column class, as an unmapped count at every other breakpoint already did. Rendered at head: absent grid-cols-2; {} grid-cols-1; bare 13 grid-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 isContainer registration in @object-ui/components; no other package registers a container with a numeric input: @object-ui/layout registers layout:page:card and app-schema-renderer, neither numeric).

input family before (base) after (head)
aspect-ratio.ratio other (moves no class) held to nothing held to nothing
container.padding spacing held, 0-8, 10, 12, 16 unchanged
flex.gap spacing held, 0-8 unchanged
grid.gap spacing held, 0-6, 8, 10, 12 unchanged
stack.gap spacing held, 0-6, 8, 10 unchanged
grid.columns count classified "not spacing", held to nothing held 1-12 at 7 positions (bare and xs ... 2xl): rendered = both faces = registration enum (of for the object)
grid.smColumns / mdColumns / lgColumns / xlColumns count held to nothing rendered = registration enum, 1-12; declaration reading held by the ledger (H2)

No key is any-number while its renderer maps a closed set after this change, so nothing else is folded in.

H5, corpus. git grep of every numeric columns literal and every breakpoint columns object in examples/**, content/**, skills/**, package READMEs, apps/*/src and packages/*/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, including grid-columns-key.test.tsx and safe-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 an object arm adds one position per breakpoint (the vocabulary is a satisfies Record of BreakpointName literal, 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 the object arm and of: 'enum'; the absent control keeps the spacing keys' default-step equality and holds every position to defined classes only; a two-way REGISTERED_NOT_DECLARED ledger. 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 as invalid_union at columns, message naming the set, the arm carrying invalid_value with the counts (at the breakpoint's path for the object); every count parses bare and at every breakpoint; absent and {} parse; xxl stays unrecognized_keys; a lit control; compile-time @ts-expect-error for 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 a tsc error.
  • Fixtures re-judged: grid-columns-breakpoint-narrowing-8505.test.ts pinned the member shape with number; the shape rows now carry the count set. Its boundary row "a computed Record of string to number still assigns" was measured with tsc -p tsconfig.test.json and 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 gained as const so a widened number does 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.mjs wrap mode: anchor hit counted, blob moved, restore proven by blob equal to HEAD and an empty git diff HEAD)

The subjects resolve to source (the vitest alias map points @object-ui/types, /zod and @object-ui/core at src, and the components pin imports ../renderers), so no rebuild sits between mutation and reading.

leg mutation (landed: anchor 1 to 0, blob moved) expected observed
A1 grid.tsx base class regains || 'grid-cols-2' red red, 2 failed / 17 passed: grid.columns 0: expected [ 'grid-cols-2' ] to deeply equal [], same at grid.columns.xs
A2 columns input back to type: 'number' (enum, object arm, of removed) red red, 2 failed: the enumeration no longer expands grid.columns.xs; grid.columns registration input: expected [ 'number' ] to include 'enum'
A3 GRID_COLUMN_COUNTS gains 13 red in both pins red, 30 failed / 22 passed across the two files (mirror set, every refusal's values, all 7 grid.columns positions)
A4 TS GridSchema.columns back to number tsc -p tsconfig.test.json red red: 3 unused @ts-expect-error and the faces-agree Same in the new pin, the 8505 shape and key rows, the 8516 face row, and the existing zod-mirror-parity.test.ts pair

Pending 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-claims leads). Dated notes appended to the three whose sentences became false:

  • 8505-grid-columns-breakpoint-narrowing.md: it states the member type with number values.
  • 11441-retire-nav-responsive-grid-layout.md and 11441-known-types-retired-layout-keys.md: their migration says grid takes "the same breakpoint columns object" as responsive-grid; each C count must now be 1 to 12 (every count ResponsiveGrid drew, 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 (absent columns renders from let baseCols = 2), 8516-8556-mirror-partial-record-narrowing.md (the partial record over six breakpoints), 11474-layout-spacing-sets.md (the gap sets and the gap registration inputs), grid-dom-attribute-whitelist-4787.md and 8912-grid-children-items.md (DOM pass-through and children), 9910-children-slot-declare-and-pin.md; the other check:changeset-claims leads name schema-reference.md, layout.ts or layout.zod.ts for other keys.

Docs

  • content/docs/components/layout/grid.mdx: the interface block showed columns?: number; it now shows the count set and the breakpoint object, with a paragraph on the refusal.
  • content/docs/api/schema-reference.md, GridSchema table: the columns row now states the count set. Its gap row still said number ("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 66c328cd

  • Build first: pnpm --filter '@object-ui/components^...' run build, then turbo run build --filter='./packages/*' --concurrency=2 (39 of 39) for the doc gates.
  • type-check (script name echoed): @object-ui/types 0, @object-ui/core 0, @object-ui/components 0. The test programs include the edited pins (--listFilesOnly).
  • vitest from the worktree root: 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 (console component-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-shell widget-dom-leak-sweep; layout containment-declared-slot-9910; plugin-designer designerRegistrationInputs-11434; scripts/__tests__/check-component-surface-parity.test.ts). The full suites ran at 66c328cd, 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).
  • exit 0: 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 four grid flat-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.
  • lint, narrowed: eslint --no-inline-config --format json over the 9 changed .ts/.tsx files. All 9 are linted by the root config (isPathIgnored false for each); the JSON reports 9 files, 0 errors, 4 warnings, all no-explicit-any on untouched lines. The config enables no type-aware linting, so this diff cannot move a verdict on an untouched file. The repo-wide pnpm lint belongs to CI.

NOT MEASURED: check:sdui-registration-pins. Reason: it weighs apps/console/dist/assets, which needs a full console build. It pins that registration keys named by the sideEffects arrays survive bundling; this diff moves no sideEffects array 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-built gap-[...] class for a number outside GAPS (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.md still says the CLI shows Code: invalid_key for { xxl: 6 }; since zod 4.6 (objectui#11073) it is unrecognized_keys. Not moved by this diff, so no dated note here. Carrier: none.

Generated by Claude Code

claude added 2 commits October 2, 2026 19:05
…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>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 29 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/11068-grid-declared-keys.md

  • names schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was wrong. ObjectGridSchema declared eight keys the grid never read. An author who wrote description, emptyState, name, placeholder, rowSpecActions or bulkSpecActions on an object-grid got no type error, no validator refusal and no effect. The reference example in schema-reference.md taught description and showFilters as if they worked. It now authors only keys the grid reads.

.changeset/3917-retire-action-condition-branch.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • ActionCondition is removed from @object-ui/types (and from the barrel export). - ActionSchema.condition is retyped to the predicate the runtime actually honours: boolean | string | { dialect?: string; source: string } — the same three arms ActionRunner's own ActionDef.condition carries, and the same vocabulary visible and disabled use. - ActionConditionSchema is removed from @object-ui/types/zod (and from the zod barrel); the condition key now validates against that predicate union. - The two teaching sites (content/docs/core/enhanced-actions.mdx Conditional Execution, content/docs/api/schema-reference.md ActionSchema table) are rewritten to the live vocabulary: condition is a gate; a branch is expressed as separate actions with mutually exclusive conditions.

.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/6170-retire-timeline-dead-keys.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Also in this change: the in-repo example packages/types/examples/data-display-examples.json (its timeline node authored all three) is migrated to items / variant; the two content/docs/api/schema-reference.md snippets that authored events: [] now author items: []; and the plugin-timeline docs callout says retired rather than deprecated.

.changeset/6896-retire-chart-inline-data.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ One correction to the record the ruling rests on. The ruling states zero authorship of a populated series[].data outside tests across packages/ / apps/ / examples/. The re-measurement finds one such site inside those roots — packages/types/examples/data-display-examples.json (2 series) — plus four outside them, in documentation: content/docs/api/schema-reference.md (3) and content/docs/core/report-schema.mdx (1).

.changeset/6939-kanban-column-cards.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.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/6951-tree-view-data-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was measured, on this branch's base. TreeViewSchema declared two spellings for its one inline-nodes slot — nodes (read second) and data (read third: boundData || schema.nodes || schema.data || [] at renderers/data-display/tree-view.tsx:105), both declared by objectui#6150. data had been REQUIRED until 777e5c6f4 (PR fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) #7533) made it optional, so this retirement starts from a declared-and-optional member on both faces. The in-repo corpus at the retirement: seven tree-view nodes under examples/schema-catalog and packages/types/examples plus one content/docs fence — six on nodes, two on data (packages/types/examples/data-display-examples.json and content/docs/api/schema-reference.md), both rewritten; no package source authored either spelling.

.changeset/7097-grid-2xl-breakpoint.md

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

    The drop was in two layers, and both are fixed. grid.tsx had neither a 2xl read arm nor a GRID_COLS_2XL static class map. Adding the read arm alone would have produced a class name Tailwind never compiles — Tailwind v4 finds utilities by scanning source text, so a 2xl:grid-cols-${n} assembled at runtime is not a utility that exists, and the node would have rendered unstyled while a unit test went green. The twelve literal class strings are what make the class real; measured against the package's own Tailwind build, the 2xl:grid-cols-* rules go from 0 to 12 in the compiled stylesheet, with the twelve xl:grid-cols-* rules unchanged as the control.

.changeset/7664-kanban-arm-plugin-dialect.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Migration. Author boards in the plugin dialect — objectName + groupBy for an object-bound board, or columns[].cards[] with badges for a static one. Replace DeclarativeKanbanSchema imports with KanbanSchema (from @object-ui/types, or the Zod KanbanSchema from @object-ui/types/zod; @object-ui/plugin-kanban re-exports the same KanbanSchema type). Delete draggable (drag-and-drop is always on) and column color (style a lane through className). content/docs/api/schema-reference.md's kanban section now documents this dialect.

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

  • 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/7997-detail-view-related-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Documentation. packages/plugin-detail/README.md and content/docs/api/schema-reference.md stop teaching the retired array and gain a migration block each.

.changeset/8268-testid-emitted-as-data-testid.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    The other direction — retiring the promise — was considered and declined. It is what c1fe272ad did for BaseSchema.hidden, but that key had a working behaviour to describe and zero named consumers, and the ruling's decline turned on exactly that. This promise already has carriers outside the type declaration: content/docs/api/schema-reference.md states it as a table row and authors testId in that page's own base-schema example, @object-ui/cli's OBJECTUI_STRUCTURAL_KEYS identifies a file as an ObjectUI schema node by this key, at this change ObjectGridSlotKey / ObjectFormSlotKey pin it, SchemaBuilder.testId() writes it, and ADR-0054 C4 — shipped — reads "the renderer emits data-testid … derived from metadata".

.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/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 |

  • names __tests__/mirror-partial-record-narrowing-8516.test.ts → packages/types/src/__tests__/mirror-partial-record-narrowing-8516.test.ts — edited by this change

    The spelling is z.partialRecord, and that is load-bearing. ⛔ Not z.record(z.enum([…]), …): measured on zod 4.4.3, the plain record over an enum key REQUIRES every member, so { md: 2 } stops parsing — it would trade this divergence for its exact opposite, and on exportConfigs it would re-impose the total-Record authoring face objectui#6121's maintainer ruling removed. That measurement is pinned executably in __tests__/mirror-partial-record-narrowing-8516.test.ts, at compile time (the inferred map is Partial<RecordANGLE-BRACKETS(…)>, not RecordANGLE-BRACKETS(…)) and at run time (one accepting row per member), so it cannot rot into folklore.

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.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.

.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/9187-record-highlights-layout-two-values.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ The census behind this narrowing covers this repository only, and it found no in-repo authoring to migrate: every in-tree layout: 'grid' belongs to a different component (detail-view in content/docs/api/schema-reference.md and phase2-schemas.test.ts, ai-recommendations in packages/plugin-ai/README.md), and the one in-repo consumer of this interface that writes a layout (p1-spec-alignment.test.ts) writes 'horizontal'. So no document in this repository stops type-checking. A TypeScript consumer outside this repo that wrote grid is not observable from here and gets a compile error (TS2322) naming the key — which is why the FROM/TO is spelled out above.

.changeset/calendar-view-schema-converge.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Runtime renderer behaviour is unchanged. @object-ui/plugin-calendar's README and content/docs/api/schema-reference.md are repaired to the converged surface in the same change, so no copy of the old contradiction survives.

.changeset/grid-dom-attribute-whitelist-4787.md

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

    grid.tsx ended in a bare {...gridProps} spread that removed only data-obj-* and style, so everything else SchemaRenderer hands a registered component reached the element. Measured on a canary node, eight attributes leaked: columns="4", gap="4", mdcolumns="2", smcolumns="2", name="grid_node", props="[object Object]", colorvariant="x" (the flattened props container) and an unknown authored zzcanary="leak". A responsive columns object rendered as columns="[object Object]". Layout was unaffected, so every catalog grid example rendered with them — the reason this went unnoticed.

.changeset/issue-5373-retire-crud-schema.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    crud had four declaration faces and no registered renderer, for the whole life of the key: the TS interface (packages/types/src/crud.ts), the zod mirror (packages/types/src/zod/crud.zod.ts), a dedicated branch in validateSchema that affirmatively PASSED it, and CRUDBuilder in @object-ui/core. A node spelling it painted the OBJUI-001 "Unknown component type" panel, and content/docs/api/schema-reference.md published it as reference material — so a reader (or an AI author) who copied the page got a red panel.

  • names api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Authoring crud is now REFUSED BY NAME rather than passed or silently ignored. validateSchema returns an error with code: 'RETIRED_TYPE' on schema.type — at any depth, since it is what validateChildren recurses with — so assertValidSchema throws and isValidSchema answers false. The message names the migration: object-grid for the record table with its toolbar, filters, pagination and row/batch actions, object-form for the create/edit form, and detail for the record view. api/schema-reference.md is rewritten around those shapes.

.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.

.changeset/object-view-unmirrored-keys-7779.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    listViews stays unmirrored, on the ruling's own fallback clause. The declaration's value is the local NamedListView — 64 declared top-level members (⚠️ re-taken at objectui#8980, which added the seventeen the protocol declares on this surface to the 47 this entry first measured), of which the renderer reads 21 off a named view. data is one of the 21 now: it used to reach the renderer through an as any cast on the named-view config in packages/plugin-view/src/ObjectView.tsx and be declared nowhere, and the objectui#8980 ruling declared it by name — objectui#7928's open half, answered. The spec's ViewSchema.listViews is a record of the STRICT ObjectListViewSchema, which requires columns and refuses options, ObjectQL tuple filters and default — that is, it refused the named views this package's own README and content/docs/api/schema-reference.md taught when this entry was written ({ label: 'All Users' } fails at columns; filter: [["owner", "=", "..."]] fails at filter.0), and objectui#8255 has since rewritten them in the spec shape. Mirroring the spec value would have lost documented behaviour; mirroring the local value would enforce 43 unread members (64 declared, minus the 21 that are both declared and read) into the contract — the very thing ruling B refused for the six local keys. The key therefore stays in the parity ledger with that measurement, pinned, until the maintainer decides its value type. It is not papered over with z.any().

.changeset/table-renderer-declared-column-contract-5350.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What makes this site different from its three siblings is that the alias was not merely tolerated, it was published. content/docs/api/schema-reference.md §TableSchema shipped a copyable { "name": "id", "label": "#" } example and a property row reading "Column definitions with name, label, …", while packages/types declared the opposite pair. Docs and type disagreed about one type they both call TableColumn, each internally consistent. Retiring the alias without correcting the page would have turned a documented, working example into a silently broken one, so both halves land together: the page now authors accessorKey/header. The same row also advertised a render property that TableColumn has never declared — the renderer's hook is cell — and that claim is dropped rather than re-spelled.

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 f68e0a080 (merge-base with origin/main): 11 file(s) changed outside .changeset/, read against 2041 pending declaration(s) that publish a body (2670 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.2 KB 3574.6 KB
Main entry chunk (gzip) 150.3 KB 350 KB
Entry file index-DQPOjEZC.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.80KB 136.98KB
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.51KB 66.22KB
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.36KB 11.18KB
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) 142.20KB 38.29KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.43KB 64.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 233.13KB 63.89KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.63KB 28.97KB
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) 4.14KB 1.96KB
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) 19.93KB 7.25KB
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: 66c328cd8df7da0c7632bf42bcc45f4e24859455
Local-runs: none

Rendered 2026-10-02T19:54Z by the domain:spec @ objectui seat's contract-review subagent (objectui#10217), from these inputs only: card objectui#11491 (body and all four comments — triage 5958209450, the claim 5959014547, the os-dev report 5960014047, the seat's answers 5960061319), PR #11504 (body, the 15-file list, the net diff against main at the head), every pending .changeset/*.md at the head (2673 entries enumerated from the git tree, each blob read by its sha and size-matched to the listing) and the head's check-runs. Nothing was checked out, built, run or re-run. The dev's tests / gates / ablation prose was not adopted: where a claim is accepted below, the diff, a file read at the head, or a check-run conclusion shows it.

① Derived judgments

Every accept-set and public-surface move the diff implies, judged against triage's direction (5958209450) and the claim (5959014547):

  1. Zod face, GridSchema.columns (packages/types/src/zod/layout.zod.ts) — RIGHT. Was a union of z.number() and a partial record over the six breakpoints of z.number(); is now ONE closed literal of 1 to 12 used as the bare arm AND as the record's value (objectArm.valueType IS countArm, pinned). The union carries the refusal for invalid_union and the literal carries it for invalid_value, so the set is named whichever arm the value missed; { xxl: 3 } still reports unrecognized_keys at columns (the object arm is the one non-aborting arm, so zod 4 returns its issues directly — consistent with the new pin and the PR body). Both doors, safeValidateSchema and StrictAnyComponentSchema, are held by grid-columns-set-11491.test.ts. Bare and at every breakpoint, refusal naming the set: exactly the ruling.
  2. TS face, GridSchema.columns (packages/types/src/layout.ts) — RIGHT. GridColumnCount (module-local, not exported) replaces number in both arms; @default 2 kept; the two faces are held equal at compile time (the Same pin), and the 8505 / 8516 shape rows are re-judged, not weakened. The 8505 escape (a computed record of string to number still assigns) is restated as covering values too, with the mirror judging them; Type Check green on the head, with the pins in the test program, confirms that row compiles.
  3. Registration columns input (grid.tsx) — RIGHT, with one move named. type: 'number' becomes type: ['enum', 'object'], enum of the twelve { label, value } entries, of: 'enum'. The twelve-count close is the ruling. The object arm is not in the ruling's words, but the declaration takes the breakpoint object on both faces, and enum alone would make the registration refuse a declared-legal shape — the registration follows the declaration. Judged in ② as the Clause-② question. Residual observation, not a defect: the manifest's object arm carries member values (of) and no key vocabulary, so validateTree is silent on { xxl: 3 } where the declaration refuses it; the declaration is the door of record and refuses it on both faces.
  4. Registration smColumns / mdColumns / lgColumns / xlColumns — RIGHT. type: 'number' to type: 'enum' of the same list. The ruling closes the lists; it does not decide whether GridSchema declares the keys. The PR holds the declared-vs-registered split in a two-way REGISTERED_NOT_DECLARED ledger (tolerant face passes any value, strict face refuses the key), which goes red on either future decision — the right interim shape. See ③ Q1.
  5. Renderer base class — RIGHT (fallback measured unreachable, removed). Read at the head: let baseCols = 2; a bare count sets baseCols to it and, with no flat override, the ramp sets it to 1 (sm to the lesser of 2 and the count, md to the count); with a flat override it stays the authored count; the object arm takes xs ?? 1; the flat inputs never touch baseCols. Every path a validated document takes lands baseCols in 1 to 12, so GRID_COLS[baseCols] || 'grid-cols-2' was reachable only by unvalidated input (0, -1, { xs: 13 }), which now draws no base column class — the answer an unmapped count already got at every other breakpoint. Nothing rounds, clamps or substitutes: the ruling. The extended pin's no-substitute assertion (an unmapped candidate may draw only classes two or more mapped candidates share) is in the diff and is what catches a restored fallback; the Test rollup green on the head runs it.
  6. GridBuilder.columns() (@object-ui/core) — RIGHT. number to the Extract of the declaration's own bare-count arm, so the builder cannot author a count the declaration refuses; pinned (columns(13) is a tsc error, columns(12) passes both faces). The builder had no object arm before and gains none.
  7. Helper rename rendererSpacingSteps to rendererMappedSet — RIGHT, no public surface. Still module-local (no export), one helper, four spacing call sites changed in name only. Read off the template code: with unit: 'step', maps: 'a gap class' / 'a padding class', zero: true, forms: '', the refusal and describe strings render byte-identical to the old spellings for gap and padding; layout-gap-sets-11474.test.ts and container-padding-set-11424.test.ts are untouched and green on the head.
  8. Docs — RIGHT. grid.mdx and the GridSchema table in schema-reference.md state the set and the refusal. The gap row repaired in the same table (stale number since objectui#11474) is the bounded in-place rule on the same family's row — accepted. Observation: schema-reference.md writes the type as ColumnCount, a term the row defines inline; the exported TS face has no such alias (GridColumnCount is module-local). Harmless, named so no reader goes looking for an export.
  9. Pins — RIGHT. The objectui#11474 enumeration pin is extended in place to every numeric layout input (multi-arm inputs; one position per breakpoint for an object arm; spacing / count / other classification with unclassified failing; a spells-the-value mapped criterion); the file name is kept, which the claim allowed. grid-columns-set-11491.test.ts is new; the 8505 and 8516 fixtures carry the count set where they said number. No key in the family is left any-number over a closed renderer set (H4), and no other package registers a numeric container input — the claim's fold-in measurement, answered.

No governed surface is in the file list (.changeset/, content/docs/, packages/{types,components,core}/src/), and the PR is draft with no auto-merge, as a dispatched dev's terminal state requires.

② Semver level

Changeset .changeset/11491-grid-columns-set.md: minor for @object-ui/types, @object-ui/components, @object-ui/core — matches what the diff publishes. Those are exactly the three released packages whose src/ moves (all three in the one fixed group of .changeset/config.json, read at the head). minor, not major, is this repo's rule for its own breaking changes; the body says Breaking in its first bold line, states before and after on each face, and carries the migration (replace the count with the one meant; leave a breakpoint out for none; 1 for a single column). Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Claim Re-read are green on the head.

Clause-②: no (narrowing) — stands (Q2, answered A by the seat in 5960061319; judged here independently). The accept set of record is the declaration, on both faces: it accepted the breakpoint object before this change and accepts it after; what it refuses only grows (every unmapped count, bare and at every breakpoint). The one move the other way is the SDUI manifest's columns input gaining an object arm: a breakpoint object of mapped counts went from a validateTree WARNING (type-mismatch, never a refusal; the renderer drew it) to silence. That is a registration that misdescribed its own declaration being corrected to match it — no document any door refused is now accepted, no renderer behaviour moves, no new capability exists; the derived intrinsics type that codegen reads off type and of follows the same correction. So no public accept set widens; the line is no (narrowing), BREAKING at the counts, and the changeset discloses the manifest move to consumers in its own words. enum alone would have made the registration refuse a declared-legal shape — the spec-first order inverted — so the arm is the right call, not scope creep.

Pending changesets (2673 at the head, every one read as text). The readings this change makes false, and what carries each:

  • 8505-grid-columns-breakpoint-narrowing.md — stated the member as number or a partial record of number, plus the compile-time escape for keys. Dated note appended (2026-10-02, objectui#11491; append-only at the tail; house form: the reading at the time, what is true now, pointer to the shipping entry, the rest kept). Content judged true against the diff: a literal { md: 13 } stops compiling; the computed-record escape still holds for values, and the mirror judges them. ✔
  • 11441-retire-nav-responsive-grid-layout.md — its migration says grid takes "the same breakpoint columns object" as responsive-grid for any C. Dated note appended, same form. Its claim that every count ResponsiveGrid drew is in the set was verified against packages/layout/src/ResponsiveGrid.tsx at the head: that map is exactly 1, 2, 3, 4, 6 and 12 at each of six breakpoints. ✔
  • 11441-known-types-retired-layout-keys.md — the same migration sentence; dated note appended, same form, same verified claim. ✔

Left alone, each judged still true at the head: 8516-8556-mirror-partial-record-narrowing.md (its subject is the KEY vocabulary — "a PARTIAL record over the six breakpoints" — and it names no post-change value type; the partial record over six keys is unchanged); 7097-grid-2xl-breakpoint.md (the 2xl map and the bare-number ramp; its 4 row still renders grid-cols-1 sm:grid-cols-2 md:grid-cols-4; its grid-cols-2 is the ramp's sm class, not the fallback; "the flat channel unchanged" is a reading of that change); 7735-zod-mirrors-stop-authoring-defaults.md (let baseCols = 2 is still the renderer's default, read at the head); 11474-layout-spacing-sets.md (gap sets, gap inputs, GridBuilder.gap() — none moved); grid-dom-attribute-whitelist-4787.md, 8912-grid-children-items.md, 9910-children-slot-declare-and-pin.md (DOM pass-through, children, "authors write grid with a breakpoint columns object" — all still true); span-full-tier-ladder-9244.md and 4783-drawerform-shared-container-grid-cols.md (a form's container-query ladder and DrawerForm's own table — other components). No pending entry names rendererSpacingSteps, GridBuilder.columns, the || 'grid-cols-2' fallback, or the five registration inputs as number (grepped over all 2673: the helper 0 hits; the smColumns family only 7097 and the new entry; grid-cols-2 only 7097, 9244 and the new entry; GRID_COLS only 7097 and 4783). The 125 entries pairing columns with grid were triaged by content: the rest are object-grid list columns, record-details / object-form section columns, element:definition-list.columns, kanban.columns and the dashboard grid — none describes GridSchema.columns.

No pending reading made false by this change is without a note: ② passes.

③ Boundary flags

Every dev flag (report 5960014047: deviations, gates, open_questions, out_of_scope_findings) and both seat answers (5960061319):

  • Q1, the four flat inputs (declared-vs-registered split): answered by the seat — C for this PR, the fork filed as objectui#11505. Judged right: the ruling closes the lists, and the PR does only that; retiring or declaring the flat channel changes a renderer branch or widens the declaration, outside this card. The ledger keeps the split visible either way. The dev's recommendation (A, retire) travels on the new card as a recommendation, not a ruling.
  • Q2, the Clause-② reading: answered A by the seat; judged in ② above — stands.
  • check:sdui-registration-pins NOT MEASURED locally: it runs in Bundle Analysis (performance-budget.yml, read at the head) — green on the head. Answered.
  • Repo-wide pnpm lint left to CI: Lint green on the head. Answered.
  • CI: in_progress at report time: every check-run on the head has concluded. Read at 2026-10-02T19:53Z: 43 check-runs, 40 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard) — path skips, not verdicts), 0 failure, 0 pending. Type Check, the eight Test shards and the Test rollup, Test (dist pins), Build & E2E, Governed Surface Queue Guard, Spec Main Shape Gate and Line Citation Gate are among the greens. No red or pending check bears on the contract.
  • Node v22.23.3 from a verified tarball; container restart mid-run, resumed at the same head with no repeated write: environment deviations with no contract effect; the PR, the report and the assignee write each exist once (the card's comments and the PR's assignee were read). Noted.
  • Pin extended in place, file name kept: allowed by the claim; the header states the new scope. Accepted.
  • Bounded in-place doc repair of the gap row: same table, same family, declared in the PR body. Accepted (① item 8).
  • Model-free trailer pair on commits: the repo's own rule; commits are outside this review's inputs. Noted.
  • out_of_scope_findings, carrier none, judged: (a) grid.tsx's gap line still builds gap-[...] at runtime for a number outside GAPS — unreachable for a validated document since objectui#11474, the renderer branch that ruling left; stays an acceptance note, not a card. (b) 8516-8556-mirror-partial-record-narrowing.md says the CLI shows Code: invalid_key for { xxl: 6 }, stale since zod 4.6 (objectui#11073) — NOT moved by this diff, so not a ② finding here, but it is pending release-owned text that will ship a wrong code name: ESCALATED to the seat — the objectui#11073 owner, or the seat, appends a dated note to that entry before it is consumed. (c) 11441-known-types-retired-layout-keys.md's migration line names gap with no objectui#11474 note while its sibling has one — true today (every ResponsiveGrid gap step, 0 to 6 and 8, is in grid's ten, verified at the head), so stale-but-true; ESCALATED as a one-line note the seat may append at its next touch of that entry.
  • The card's own line addresses ("about :584", ":15–:66") sit in the card body, the seat's text, not the dev's; nothing in the PR body or the diff cites a cross-file line address (Line Citation Gate green). Noted for the seat, not the dev.

Implemented-by: claude/issue-11491-grid-columns-set
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 19:57
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 19:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit aea682a Oct 2, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11491-grid-columns-set branch October 2, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants