Repository navigation
Commit 4abc0aa
fix(types,components,core)!: stack, flex and grid gap are each one of their renderer's steps, on both faces and the registrations; one enumeration pin for every layout spacing key (objectui#11474) (#11489)
Fixes #11474
Clause-②: no
Why `no` (a narrowing): `stack.gap`, `flex.gap` and `grid.gap` shrink
from every number to each renderer's mapped set, and the `flex` / `grid`
describes stop advertising "0-8". Nothing widens. The changeset is
`minor` and states the breaking authoring meaning, because objectui
never declares `major` (AGENTS.md section 9, version alignment).
Dispatched by the `domain:spec @ objectui` seat (objectui#10217) under
triage's family direction (comment `5955082167`) and the seat's claim
(comment `5956674102`), session
`https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC`. This is the
family's last step after objectui#10286 (`container.maxWidth`) and
objectui#11424 (`container.padding`).
## The accept-set change
| surface | before (base `50c73fed0`) | after (head `1db960ca`) |
|---|---|---|
| zod `StackSchema.gap` | `z.number()` | literal set 0, 1, 2, 3, 4, 5,
6, 8, 10; the message lists the set |
| zod `FlexSchema.gap`, and the authored bag (`properties.gap`, the same
schema object by reference) | `z.number()`, describe "Tailwind scale
0-8" | literal set 0 to 8; describe "Gap step, one of 0, 1, 2, 3, 4, 5,
6, 7, 8; 0 is none (default 2)" |
| zod `GridSchema.gap` | `z.number()`, describe "Tailwind scale 0-8" |
literal set 0, 1, 2, 3, 4, 5, 6, 8, 10, 12 |
| TS `FlexLayoutProps.gap` (shared by `FlexSchema` and the bag type
`FlexBlockNode`) | `number` | literal union 0 to 8, `@default 2` kept |
| TS `StackSchema.gap` | `number`, inherited from `FlexLayoutProps` |
its own literal union 0 to 6, 8, 10; the heritage clause is `Omit` of
`gap` over `FlexLayoutProps` (no index signature there, so no member is
erased; `stack-schema-emitted-members.test.ts` measures the emitted
declaration and stays green) |
| TS `GridSchema.gap` | `number` | literal union of the ten grid steps |
| `stack` / `flex` / `grid` registration, `gap` input | `type: 'number'`
| `type: 'enum'`, `{ label, value }` entries with numeric values, in
`container.padding`'s object form |
| `GridBuilder.gap()` / `FlexBuilder.gap()` in `@object-ui/core` |
`number` | the declared set (`NonNullable` of the member) |
| renderers | `gap === N` branches; `grid`'s `GAPS` map and its
runtime-built fallback | unchanged; comments only at the registration
inputs. Nothing rounds or clamps. |
`container.padding` moved onto the same shared helper
(`rendererSpacingSteps` in `layout.zod.ts`, the dispatch's suggested
route: the spelling now repeats four times). Its refusal text is
byte-identical to base (compared against the built `dist`), and its
describe is unchanged.
The three refusals as stored (read from the built `dist` at head):
- stack: "`gap` on a `stack` is one of 0, 1, 2, 3, 4, 5, 6, 8, 10
(objectui#11474): those are the steps the renderer maps to a gap class,
and `0` means none. Any other number drew NO gap class at all, not even
the default `2`, so it is refused here rather than rendered with no gap.
Pick the step you meant from that set."
- flex: the same sentence with the set 0 to 8.
- grid: "... Any other number built a class at runtime that no compiled
stylesheet defines, so the grid rendered with no gap at all, not even
the default `4`; it is refused here instead. ..."
## Measurements (the dispatch's zone 2)
**H1, the three sets, derived by rendering** (a throwaway probe through
the real `SchemaRenderer` and registry at base `50c73fed0`, candidates 0
to 32 plus -1, 0.5, 1.5, 2.5, 9.5): `stack` draws a gap class for 0 to
6, 8, 10 and none for 7, 9, 11 to 32 or any fraction. `flex` (flat or
bag) draws one for 0 to 8 and none for 9 upward. `grid` draws `gap-0` to
`gap-6`, `gap-8`, `gap-10`, `gap-12` from its map and a runtime-built
`gap-[N*0.25rem]` for everything else (`gap-[2.25rem]` for 9,
`gap-[-0.25rem]` for -1). At base, every candidate on all four spellings
parsed green on `safeValidateSchema` and on `StrictAnyComponentSchema`.
H1 holds as the dispatch stated it.
**H3, `grid.gap` measured first: closed.** Instrument: both Tailwind
entry sheets compiled exactly as their builds compile them (`postcss`
plus `@tailwindcss/postcss`, `from` set to the entry so its `@source`
lines resolve): `apps/console/src/index.css` (482,775 bytes) and
`packages/components/src/index.css`, the sheet the package ships as
`style.css` (158,414 bytes). Control: every mapped grid class (`gap-0`
... `gap-12`), `md:gap-10` and `gap-1.5` are present in both sheets. The
arbitrary-value lookup is live: `max-w-[16rem]` and `px-[0.3rem]` are
found in both by the same matcher. Reading: all 28 runtime-built classes
(for 7, 9, 11, 13 to 32, -1, 0.5, 1.5, 2.5, 9.5) are absent from both.
So an unmapped grid gap reaches no rule, and `grid.gap` closes like the
other two. The pin re-derives this against the package's own sheet on
every run. The console reading was taken once and nothing re-derives it.
**H2, the flex bag.** Closing `FlexSchema.gap` flowed into the bag and
into `FlexBlockNode` with no edit there. `{ type: 'flex', properties: {
gap: 9 } }` is refused at `properties.gap` on both faces with the set in
the issue. `{ type: 'flex', gap: 9 }` stays refused by name toward
`properties.gap`, as every flat `flex` prop already was. `tsc` refuses
`{ type: 'flex', properties: { gap: 9 } }` typed as `FlexBlockNode` (a
compile-time pin under the package's `type-check`).
**H4, the enumeration.** Derived by the new pin: every registration in
`@object-ui/components` with `isContainer`, and every input on it whose
values are numbers. It finds `aspect-ratio.ratio`, `container.padding`,
`flex.gap`, `grid.columns`, `grid.smColumns`, `grid.mdColumns`,
`grid.lgColumns`, `grid.xlColumns`, `grid.gap` and `stack.gap`.
Rendering classifies four as spacing keys: `container.padding`,
`flex.gap`, `grid.gap`, `stack.gap`. One other open member of the family
is reported, ⛔ not closed here: see "Reported, not closed" below. `card`
has no numeric input, and no layout node has a responsive `gap` object.
**H5, corpus.** `git grep` of every `gap` literal in tracked files
(CHANGELOGs and the lockfile excluded), triaged by node. No corpus
document authors an unmapped `gap` on a `stack`, `flex` or `grid`. The
off-set literals are inline CSS (`style` objects in
`content/docs/guide/react-pages.md`, an old changeset, two plugin demos,
`DatasetReportRenderer.tsx`), `DashboardConfigSchema.gap` (a different
schema), and a prose comment in the schema-catalog test. The catalog
values (0, 1, 2, 3, 4, 6, 8) sit inside every set. The same sweep over
the objectstack checkout (`examples/**`, `packages/*/src`) found inline
CSS only. Re-judged by running: `examples/schema-catalog` (40 files,
including `safe-validate-corpus-6318.test.ts`), `pnpm
check:doc-snippets`, `pnpm check:doc-examples` and `pnpm
check:skill-examples`, all green.
## The pins
-
**`packages/components/src/__tests__/layout-spacing-sets-11474.test.tsx`**
generalises `container-padding-set-11424.test.tsx`, which is deleted.
Its assertions are held here in general form; the absent-key control now
compares with the registration's default step instead of a literal class
list. It enumerates as above. A candidate counts as MAPPED when the
spacing utilities its value draws are all rules in the package's
compiled stylesheet. Each spacing key is then held to three things. Its
declaration accepts exactly that set on the tolerant and strict faces,
at the authored spelling: flat, or the `properties` bag when the node
refuses the key flat; the spelling is derived from the declaration. Its
registration input is a closed enum of exactly that set. An absent key
draws exactly what the registration's default step draws. An unmapped
number draws no spacing rule, so nothing is rounded or clamped. The
non-spacing numeric inputs are classified and held to nothing else. Lit
controls: the stylesheet reader sees a variant, an escaped dot and an
arbitrary value; the enumeration finds `container.padding`.
- **`packages/types/src/__tests__/layout-gap-sets-11474.test.ts`**: per
key and per face, unmapped numbers (including a fraction and -1) are
refused at the key with code `invalid_value`, the set in `issue.values`
and in the message. Every mapped step parses, and an absent key parses.
The flat `flex` spelling stays refused by name. The describes state the
set and no longer say "0-8". Compile-time pins check that the TS faces
refuse 7 on `stack`, 9 and 10 on `flex` (node, `FlexLayoutProps` and
`FlexBlockNode`), and 9 on `grid`, and that each zod and TS pair states
one set.
- **Fixture re-judged**: `flex-properties-bag-11276.test.ts` pinned
`gap: '4'` in the bag as `invalid_type`. A literal union judges by
value, so it is now `invalid_value` at the same path. The assertion's
purpose (the bag keeps the mirror's verdict, at its own path) is
unchanged, so the code was updated in place.
`container-padding-set-11424.test.ts` and `container.tsx` only had their
pointer to the deleted file repointed.
## Ablations (committed implementation, `node
../objectstack/scripts/ablation-replace.mjs` wrap mode, trap-armed
restore, each restore proven by blob equal to HEAD and an empty `git
diff HEAD`)
| leg | mutation (landed on disk: anchor count and blob moved) |
expected | observed |
|---|---|---|---|
| A1b | `stack` registration drops `10` from its enum | red | red:
`stack.gap registration enum: expected [ +0, 1, 2, 3, 4, 5, 6, 8 ] to
deeply equal [ +0, 1, 2, 3, 4, 5, 6, 8, 10 ]` |
| A2b | `stack.tsx` gains a `gap === 7` branch | red | red: `stack.gap
on the tolerant face: expected [ +0, 1, 2, 3, 4, 5, 6, 8, 10 ] to deeply
equal [ +0, 1, 2, 3, 4, 5, 6, 7, 8, 10 ]` |
| A3 | `STACK_GAP_STEPS` gains 7 | red | red in both pins (types: mirror
set, describe, and each "refuses gap N" on both faces; components:
`stack.gap`) |
| A4 | `grid`'s `GAPS` map drops `10` (10 then builds `gap-[2.5rem]`) |
red: shows the stylesheet criterion is live, not just "a class string is
present" | red: `grid.gap` (1 failed, 11 passed) |
| A5 | TS `StackSchema.gap` gains 7 | `tsc -p tsconfig.test.json` red |
red: unused `@ts-expect-error` and `true` not assignable to `false` in
the new types pin, plus the existing `zod-mirror-parity.test.ts` parity
pin |
First attempts at A1 and A2 were no-ops. The tool refused both before
running anything: A1 was a delete passed as an empty replacement, and
A2's anchor was a substring of its replacement. Both were re-run with
corrected anchors as A1b and A2b above.
## Gates at head `1db960ca`
- Build first: `pnpm --filter '@object-ui/components^...' --filter
@object-ui/components run build` (9 packages), then `turbo run build
--filter='./packages/*' --concurrency=2` (39 tasks) for the doc gates.
- `type-check` (script name echoed): `@object-ui/types` 0,
`@object-ui/core` 0, `@object-ui/components` 0. The test tsconfigs
include the new pins (`--listFilesOnly`).
- vitest from the worktree root: `packages/types/` 334 files, 8840
passed. `packages/core/` 193 files, 3844 passed, 27 skipped.
`packages/components/` 353 passed, 1 skipped; 3584 tests passed.
`examples/schema-catalog/` 40 files, 2259 passed.
`packages/sdui-parser/` 20 files, 289 passed. Registry-reading suites (9
files, 584 passed): console `component-input-union-specimens`,
`ga-honoured-inputs-author-reach`, `html-tier-manifest`,
`public-contract`, `registry-inputs-spec-parity`; app-shell
`widget-dom-leak-sweep`; layout `containment-declared-slot-9910`;
plugin-designer `designerRegistrationInputs-11434`;
`scripts/__tests__/check-component-surface-parity.test.ts`.
- exit 0: `check:component-surface-parity`, `check:doc-types`,
`check:doc-snippets` (776 of 776 judged, 0 failed),
`check:doc-examples`, `check:skill-examples`, `check:doc-fences`,
`check:new-line-citations`, `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:registry-bare-names`,
`check:unreferenced-sources`, `check:test-path-roots`.
- SDUI manifest, measured once by a throwaway probe (real registry,
`manifestFromConfigs` plus `validateTree`): `stack` gap 7, `flex` gap 9,
`grid` gap 9 and `container` padding 9 each answer `invalid-enum` naming
the set. Controls `stack` 4, `flex` 8, `grid` 12 and `container` 8
answer nothing.
- lint, narrowed: `eslint --format json` over the 11 changed
`.ts`/`.tsx` files. All 11 are linted by the root config
(`isPathIgnored` false for each), and the JSON reports 11 files with 0
errors. The 9 warnings are all on untouched lines (`no-explicit-any`,
`react-refresh`). 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 the registration keys named by the `sideEffects` arrays survive
bundling. This diff moves no `sideEffects` array and no registration
key, only an input's type. CI runs it.
## Reported, not closed
- **`grid.columns`** (and the four flat `smColumns` to `xlColumns`
inputs, and the responsive object's values). This is the same family
shape: declared `z.number()` / `number`, while the renderer maps 1 to 12
through its `GRID_COLS*` maps. Readings through the real
`SchemaRenderer`: `columns: 13` draws `grid-cols-1 sm:grid-cols-2`, so
the md count is silently dropped. `columns: 0` and `columns: -1` draw
`grid-cols-2`, a substituted value. `columns: { md: 13 }` draws
`grid-cols-1`. All four parse green on both faces. The dispatch says
report, ⛔ not close; the report names it for the seat.
## Acceptance notes (observations, not filed)
- `examples/schema-catalog/test/layout-props-conversion.test.tsx` keeps
its own hand-written `GAP_LADDER` and `CONTAINER_PADDING` sets. They
agree with the derived sets today and could now read the declarations
instead. That is unexercised drift, so it is noted here and not filed.
Carrier: none.
- `grid.tsx`'s gap line still carries the comment "Fallback for
arbitrary values if not in map", which no longer describes a reachable
authored case and never produced a compiled rule. It sits on a renderer
branch line, which the claim's file surface excludes. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f560ded commit 4abc0aa
18 files changed
Lines changed: 771 additions & 143 deletions
File tree
- .changeset
- content/docs/components/layout
- packages
- components/src
- __tests__
- renderers/layout
- core/src/builder
- types/src
- __tests__
- zod
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
Lines changed: 0 additions & 88 deletions
This file was deleted.
0 commit comments