From 545d47148c95d95720e53f55ca1e8bdff4b1d3c1 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 22 Apr 2026 01:15:26 +0200 Subject: [PATCH] =?UTF-8?q?morfo:=20permutation=20runner=20v3=20=E2=80=94?= =?UTF-8?q?=20per-demo=20axis=20matrix=20via=20``?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2 ran every demo under a hard-coded `ltr` + `rtl` matrix. v3 lets each demo declare which additional axes it honours — `orientation`, `disabled`, `readonly`, `invalid`, `loading` — and the runner adds ONE variant per declared axis (axis-at-a-time, not cartesian, so the matrix stays bounded at O(N) instead of O(2^N)). ## Convention ```svelte ``` `dir` is universal (always runs). The other axes are opt-in: they only produce variants when the demo both lists them in the meta AND wires the corresponding state from `Perms.getOr(...)`. Without the meta, only `ltr` + `rtl` run. ## Runner changes - New `discoverAxes(base, morfoKebab)` pass: loads the baseline URL, reads ``, returns the declared axis list. - `buildVariants(declaredAxes)` composes `ltr` + `rtl` + one `?perm.X=Y` variant per declared axis from the `AXIS_FLIP` map. - `[initial seed]` step validates morfo at each URL before any click, so URL-seeded state alone is exercised (catches seed regressions even when the click cycle never fires). ## Demo opt-in (this commit) - `dialog` — `perm-axes="dir"` (no orientation / disabled / loading axes apply to Dialog). - `tabs` — `perm-axes="dir,orientation,disabled"` + wires `orientation` + `disabled` to the first `Tabs.Provider`. - `toolbar` — `perm-axes="dir,orientation"` + wires `orientation` to the first `Toolbar.Provider`. Result: 3 demos, 9 URL variants, 34 step validations per CI run (v2 was 22 steps across 6 variants). ## Docs `src/uix/morfo/PERMUTATION_RUNNER.md` v3 section: supported axis table, 3-step opt-in recipe, roadmap updated (v4 = morfo-inferred value cycling, v5 = MutationObserver ordering for Sema). ## Verification - `npm run check`: 0 errors in soma/morfo/test-route scope. - `npm run smoke`: 65/65 routes. - `npm run morfo:check`: 66/66 morfos. - `npm run perm:check`: 3/3 demos · 34 steps · 9 variants. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/permutation-check.ts | 110 ++++++++++++++++++---- src/routes/test/soma/dialog/+page.svelte | 3 + src/routes/test/soma/tabs/+page.svelte | 11 ++- src/routes/test/soma/toolbar/+page.svelte | 12 ++- src/uix/morfo/PERMUTATION_RUNNER.md | 67 +++++++++++-- 5 files changed, 172 insertions(+), 31 deletions(-) diff --git a/scripts/permutation-check.ts b/scripts/permutation-check.ts index d2e85313a..48e35dada 100644 --- a/scripts/permutation-check.ts +++ b/scripts/permutation-check.ts @@ -218,24 +218,77 @@ async function executeStep(page: Page, step: PermStep): Promise { await page.waitForTimeout(step.settle); } -// ── URL variant matrix (v2) ───────────────────────────────────────────────── +// ── URL variant matrix (v2 + v3 axis-at-a-time) ──────────────────────────── /** - * Universal URL variants applied to every instrumented demo. The layout at - * `src/routes/test/soma/+layout.svelte` reads `?perm.dir` / `?perm.locale` - * and re-seeds the presentation context; the runner validates the full - * click cycle under each variant so RTL-only reactivity bugs (e.g. the - * slider thumb-transform regression) fail cleanly instead of shipping. + * Universal baseline: every instrumented demo runs at least under `ltr` + * (plain URL) and `rtl` (`?perm.dir=rtl`). Additional per-component axes + * are discovered at runtime from the demo's `` tag; + * each declared axis adds ONE variant that flips that axis from baseline + * while keeping everything else at the default — linear, not cartesian, + * so the matrix stays bounded. * - * Per-component axes (component-specific state like `?perm.open=true`, - * `?perm.variant=alertdialog`, `?perm.value=tab-2`) are declared by the - * demo itself via ` Tabs · Soma + +
@@ -23,7 +30,7 @@

1. Horizontal (automatic)

value: {value1}

- + Account Password diff --git a/src/routes/test/soma/toolbar/+page.svelte b/src/routes/test/soma/toolbar/+page.svelte index e31f451b8..5d90028f4 100644 --- a/src/routes/test/soma/toolbar/+page.svelte +++ b/src/routes/test/soma/toolbar/+page.svelte @@ -2,8 +2,9 @@ import * as Toolbar from '$soma/components/toolbar'; import { Perms } from '../_perms.svelte'; - // Perm-runner v2 seed — `?perm.format=bold,italic` starts with those - // toggles on; `?perm.align=center` overrides the alignment. + // Perm-runner v2/v3 seed. `?perm.format=bold,italic` preselects toggles; + // `?perm.align=center` overrides alignment; `?perm.orientation=vertical` + // flips the first Toolbar to vertical (covered by perm-axes meta below). const perms = Perms.getOr({} as Record); function parseList(v: unknown, fallback: string[]): string[] { if (typeof v !== 'string' || v.length === 0) return fallback; @@ -11,10 +12,15 @@ } let format = $state(parseList(perms.format, [])); let align = $state(parseList(perms.align, ['left'])); + let orientation = $state<'horizontal' | 'vertical'>( + (perms.orientation as 'horizontal' | 'vertical') ?? 'horizontal' + ); Toolbar · Soma + +
@@ -26,7 +32,7 @@

1. Horizontal toolbar

format: {JSON.stringify(format)} | align: {JSON.stringify(align)}

- + alert('Undo')}>Undo alert('Redo')}>Redo diff --git a/src/uix/morfo/PERMUTATION_RUNNER.md b/src/uix/morfo/PERMUTATION_RUNNER.md index 876b28227..59d827ad3 100644 --- a/src/uix/morfo/PERMUTATION_RUNNER.md +++ b/src/uix/morfo/PERMUTATION_RUNNER.md @@ -160,16 +160,62 @@ Read the perms and use them as **initial** state (not live-bound — the user ca Type coercion is naive: `'true'` / `'false'` → boolean, bare digits → number, everything else stays a string. Cast to the component's actual union at the read site. -### URL variants +### URL variants (v2 baseline + v3 axis-at-a-time) -The runner runs each instrumented demo under a matrix of base URL variants defined in `scripts/permutation-check.ts`: +The runner composes each demo's variant list from two sources: -| Variant | URL suffix | -|---------|------------| -| `ltr` | `` (no params) | -| `rtl` | `?perm.dir=rtl` | +1. **Universal baseline** — every instrumented demo runs at `ltr` (plain URL) and `rtl` (`?perm.dir=rtl`). These catch direction-specific bugs like the slider thumb transform regression. +2. **Per-demo axes** (v3) — the demo declares which axes it honours via ``. The runner adds ONE variant per declared axis, flipping that axis from baseline while keeping everything else at default (linear, not cartesian — the matrix stays bounded). -Per-component axes (`?perm.open=true`, `?perm.variant=alertdialog`, `?perm.value=tab-2`) are NOT enumerated by the runner yet — they're just defaults the demo honours when the URL provides them. Adding them to the matrix is a v3 task (the runner would read the morfo's `states` + `data.values` to generate the cartesian product). +Supported axes (v3 initial set): + +| Axis | Baseline | Flipped variant | Typical use | +|------|----------|-----------------|-------------| +| `dir` | `ltr` | `rtl` | Always run (universal). | +| `orientation` | `horizontal` | `vertical` | Toolbars, Tabs, Splitters, RadioGroup, Slider. | +| `disabled` | unset | `true` | Any component with a `disabled` prop. | +| `readonly` | unset | `true` | Inputs, Editable, Calendar. | +| `invalid` | unset | `true` | Form-participating components. | +| `loading` | unset | `true` | Async components with pending states. | + +Demos opt in by listing the axes they honour. The runner skips meta-less demos at the `dir` axis only (universal baseline always applies). + +### v3 opt-in — 3 steps + +1. Add the meta tag (list only the axes your demo actually wires): + + ```svelte + + Tabs · Soma + + + ``` + +2. Seed the `$state` from perms for each declared axis: + + ```svelte + + ``` + +3. Wire the state to the Provider: + + ```svelte + + ... + + ``` + +The runner now visits `/test/soma/tabs?perm.orientation=vertical` and `/test/soma/tabs?perm.disabled=true` in addition to `ltr` / `rtl`, and re-runs the full `data-perm-step` click cycle at each URL. + +Per-component value seeds (`?perm.value=tab-2`, `?perm.open=true`, `?perm.format=bold,italic`) are NOT enumerated by the runner — they're just defaults the demo honours when testing manually or via direct URL navigation. Adding them to the enumerated matrix is a v4 task (the runner would need to read the morfo's `states` + `data.values` to know which values are meaningful). ### `[initial seed]` step @@ -181,10 +227,11 @@ At each URL variant, the runner validates morfo BEFORE running any click step. T - **v1 (shipped 2026-04-22)** — click-based state transitions, morfo re-validation after each step. - **v2 (shipped 2026-04-22)** — URL-seeded initial state, universal axes (`dir`, `locale`), `[initial seed]` validation step before clicks. Demo layout reads `?perm.*` params, demos seed initial `$state` from them. -- **v3** — derive the per-component axis matrix automatically from the morfo's `states` and `data.values`. No per-demo instrumentation needed for the baseline cycles; the runner generates `?perm.X=Y` URLs from the morfo itself. -- **v4** — integrate with `MutationObserver` to validate `data-last-action` / `data-state` ordering for Sema. +- **v3 (shipped 2026-04-22)** — per-demo axis matrix via ``. Runner adds one variant per declared axis (axis-at-a-time, not cartesian). Supported axes: `dir` (universal), `orientation`, `disabled`, `readonly`, `invalid`, `loading`. +- **v4** — morfo-inferred value cycling. Reads `states[]` + `data.values` from each morfo and enumerates meaningful value combinations automatically (would cover `?perm.value=tab-2` / `?perm.variant=alertdialog` / `?perm.open=true` without per-demo declaration). +- **v5** — integrate with `MutationObserver` to validate `data-last-action` / `data-state` ordering for Sema. -The convention (`data-perm-step` / `data-perm-mode` / `perm.X=Y`) is forward-compatible: v3+ infrastructure adds automation without invalidating v1/v2 annotations. +The convention (`data-perm-step` / `data-perm-mode` / `perm.X=Y` / `perm-axes` meta) is forward-compatible: v4+ infrastructure adds automation without invalidating earlier annotations. ---