From c04b6a241a7d8ca27eeaec42c375cefd24488404 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 16 May 2026 20:48:34 +0200 Subject: [PATCH] Reconnect permutation runner routes --- continue.md | 11 ++++ scripts/permutation-check.ts | 81 +++++++++++++++++++++-------- src/uix/morfo/PERMUTATION_RUNNER.md | 38 +++++++++----- 3 files changed, 94 insertions(+), 36 deletions(-) diff --git a/continue.md b/continue.md index e93c1f4b9..9c49c2171 100644 --- a/continue.md +++ b/continue.md @@ -232,6 +232,17 @@ Actualizacion smoke 2026-05-16: - Pendiente deliberado: `scripts/permutation-check.ts` sigue siendo runner historico del arbol `/test/soma`; no usarlo como release gate hasta reconectarlo a una route map real del docs shell actual. +- `scripts/permutation-check.ts` reconectado al shell actual: + - usa `/uix/components/{kebab}` por defecto; + - admite `PERM_ROUTE_PREFIX` para otros shells; + - salta morfos sin ruta como `SKIP no route`; + - salta rutas sin `data-perm-step` ni `perm-axes` como + `SKIP no perm instrumentation`; + - si todavia no hay demos instrumentadas, sale limpio con + "No instrumented routed demos found". +- `src/uix/morfo/PERMUTATION_RUNNER.md` actualizado: la antigua infraestructura + `_perms.svelte.ts` queda marcada como retirada; las paginas actuales deben + leer `?perm.*` desde su ruta si optan a permutations. Actualizacion Eidos visual wrappers 2026-05-16: diff --git a/scripts/permutation-check.ts b/scripts/permutation-check.ts index 73955f001..0f2967139 100644 --- a/scripts/permutation-check.ts +++ b/scripts/permutation-check.ts @@ -11,14 +11,17 @@ * each settle. See `src/uix/morfo/PERMUTATION_RUNNER.md` for the full * authoring convention. * + * Current route map: `/uix/components/{kebab}`. Morfos without a routed demo + * or without `data-perm-*` instrumentation are skipped. + * * Exit codes: - * 0 — every demo with `data-perm-step` passes all permutations. + * 0 — every instrumented routed demo passes all permutations. * 1 — at least one permutation failed. * 2 — no dev server / no morfos found. */ import { chromium, type Page } from 'playwright'; -import { readdirSync } from 'node:fs'; +import { existsSync, readdirSync } from 'node:fs'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { dirname, join } from 'node:path'; import type { Morfo, MorfoPart, MorfoData } from '../src/uix/morfo/types'; @@ -26,6 +29,29 @@ import { validateMorfo } from '../src/uix/morfo/schema'; const __dirname = dirname(fileURLToPath(import.meta.url)); const MORFOS_DIR = join(__dirname, '..', 'src', 'uix', 'morfo', 'components'); +const ROUTES_DIR = join(__dirname, '..', 'web', 'routes'); +const DEFAULT_ROUTE_PREFIX = '/uix/components'; +const routePrefix = normaliseRoutePrefix(process.env.PERM_ROUTE_PREFIX ?? DEFAULT_ROUTE_PREFIX); + +function normaliseRoutePrefix(value: string): string { + const trimmed = value.trim().replace(/\/+$/, ''); + if (!trimmed || trimmed === '/') return ''; + return trimmed.startsWith('/') ? trimmed : `/${trimmed}`; +} + +function routeToPageFile(route: string): string { + const segments = route + .split('/') + .filter(Boolean) + .filter((segment) => !(segment.startsWith('(') && segment.endsWith(')'))); + + return join(ROUTES_DIR, ...segments, '+page.svelte'); +} + +function routeForMorfo(morfo: Morfo): string | null { + const route = `${routePrefix}/${morfo.kebab}`.replace(/\/+/g, '/'); + return existsSync(routeToPageFile(route)) ? route : null; +} // ── Shared with morfo-check.ts ────────────────────────────────────────────── @@ -329,7 +355,8 @@ console.error(`Loaded ${morfos.length} morfo${morfos.length === 1 ? '' : 's'}`); const browser = await chromium.launch(); const ctx = await browser.newContext(); const results: RouteResult[] = []; -const skipped: string[] = []; +const skippedNoRoute: string[] = []; +const skippedNotInstrumented: string[] = []; /** Run one URL variant: load, cycle click-driven steps, report. */ async function runVariant( @@ -472,11 +499,14 @@ async function runVariant( */ async function probeDemo( base: string, - morfoKebab: string -): Promise<{ axes: string[]; hasSteps: boolean } | 'no-demo'> { + morfo: Morfo +): Promise<{ route: string; axes: string[]; hasSteps: boolean } | 'no-route' | 'no-demo'> { + const route = routeForMorfo(morfo); + if (!route) return 'no-route'; + const page = await ctx.newPage(); try { - const res = await page.goto(`${base}/test/soma/${morfoKebab}`, { + const res = await page.goto(`${base}${route}`, { waitUntil: 'networkidle', timeout: 20000 }); @@ -484,7 +514,7 @@ async function probeDemo( await page.waitForTimeout(200); const axes = await readDeclaredAxes(page); const hasSteps = (await collectSteps(page)).length > 0; - return { axes, hasSteps }; + return { route, axes, hasSteps }; } catch { return 'no-demo'; } finally { @@ -495,9 +525,9 @@ async function probeDemo( for (const morfo of morfos) { const variantResults: VariantResult[] = []; - const probe = await probeDemo(BASE, morfo.kebab); - if (probe === 'no-demo') { - skipped.push(morfo.kebab); + const probe = await probeDemo(BASE, morfo); + if (probe === 'no-route' || probe === 'no-demo') { + skippedNoRoute.push(morfo.kebab); continue; } @@ -506,21 +536,21 @@ for (const morfo of morfos) { // validation at each URL variant; step-only demos get the v1 click cycle // at baseline; declaring both is the full v1+v2+v3 treatment. if (probe.axes.length === 0 && !probe.hasSteps) { - skipped.push(morfo.kebab); + skippedNotInstrumented.push(morfo.kebab); continue; } const variants = buildVariants(probe.axes); for (const v of variants) { - const url = `/test/soma/${morfo.kebab}${v.suffix}`; + const url = `${probe.route}${v.suffix}`; const r = await runVariant(BASE, url, v.label, morfo); if (r === 'no-steps') continue; variantResults.push(r); } if (variantResults.length === 0) { - skipped.push(morfo.kebab); + skippedNotInstrumented.push(morfo.kebab); continue; } @@ -570,20 +600,27 @@ for (const r of results) { } console.log(''); -if (skipped.length > 0) { +if (skippedNoRoute.length > 0) { + console.log( + `SKIPPED no route (${skippedNoRoute.length}): ${skippedNoRoute.slice(0, 10).join(', ')}${skippedNoRoute.length > 10 ? ', …' : ''}` + ); +} +if (skippedNotInstrumented.length > 0) { console.log( - `SKIPPED (${skipped.length}): ${skipped.slice(0, 10).join(', ')}${skipped.length > 10 ? ', …' : ''}` + `SKIPPED no perm instrumentation (${skippedNotInstrumented.length}): ${skippedNotInstrumented.slice(0, 10).join(', ')}${skippedNotInstrumented.length > 10 ? ', …' : ''}` ); - console.log(' (demo has no `data-perm-step` annotations yet — see src/uix/morfo/PERMUTATION_RUNNER.md)'); + console.log(' (demo has no `data-perm-step` / `perm-axes` opt-in yet — see src/uix/morfo/PERMUTATION_RUNNER.md)'); } console.log(''); const totalVariants = results.reduce((acc, r) => acc + r.variants.length, 0); const summary = - failedRoutes === 0 - ? `All ${passedRoutes} instrumented demo${passedRoutes === 1 ? '' : 's'} passed. (${totalSteps} step${totalSteps === 1 ? '' : 's'} across ${totalVariants} URL variant${totalVariants === 1 ? '' : 's'}.)` - : `${failedRoutes} demo${failedRoutes === 1 ? '' : 's'} failed (${totalFailed}/${totalSteps} steps): ${results - .filter((r) => r.variants.some((v) => v.steps.some((s) => s.kind !== 'pass'))) - .map((r) => r.morfo) - .join(', ')}`; + results.length === 0 + ? `No instrumented routed demos found under ${routePrefix}.` + : failedRoutes === 0 + ? `All ${passedRoutes} instrumented demo${passedRoutes === 1 ? '' : 's'} passed. (${totalSteps} step${totalSteps === 1 ? '' : 's'} across ${totalVariants} URL variant${totalVariants === 1 ? '' : 's'}.)` + : `${failedRoutes} demo${failedRoutes === 1 ? '' : 's'} failed (${totalFailed}/${totalSteps} steps): ${results + .filter((r) => r.variants.some((v) => v.steps.some((s) => s.kind !== 'pass'))) + .map((r) => r.morfo) + .join(', ')}`; console.log(summary); process.exit(failedRoutes === 0 ? 0 : 1); diff --git a/src/uix/morfo/PERMUTATION_RUNNER.md b/src/uix/morfo/PERMUTATION_RUNNER.md index 40123000e..82b43a2c7 100644 --- a/src/uix/morfo/PERMUTATION_RUNNER.md +++ b/src/uix/morfo/PERMUTATION_RUNNER.md @@ -123,13 +123,21 @@ FAIL toolbar 3 permutations, 1 failed npm run perm:check ``` +The current docs-shell route map is `/uix/components/{kebab}`. Components +without a routed page are reported as `SKIP no route`; routed pages without +`data-perm-step` or `perm-axes` opt-in are reported as +`SKIP no perm instrumentation`. Override the prefix with +`PERM_ROUTE_PREFIX=/some/path` when testing another shell. + Exit codes: -- `0` — every demo with `data-perm-step` passes all permutations. +- `0` — every instrumented routed demo passes all permutations. If no routed + demo is instrumented yet, the runner exits cleanly and says so. - `1` — at least one permutation failed. - `2` — no dev server / no morfos found. -Demos without `data-perm-step` are skipped with a `SKIP` line (they haven't opted in yet). +Demos without `data-perm-step` / `perm-axes` are skipped with a `SKIP` line +(they haven't opted in yet). --- @@ -139,9 +147,12 @@ v1 covers "click through a state sequence and validate after each step." v2 adds ### Layout plumbing -`web/routes/+layout.svelte` parses `?perm.*` params once at mount, exposes them via a context at `web/routes/_perms.svelte.ts`, and also honours `?perm.dir=ltr|rtl` and `?perm.locale=es|en` by routing through the existing `App.setDir` / `App.setLocale` path (so the control strip stays in sync). - -> **Note (2026-05-08):** routes were relocated from `src/routes/test/soma/` to `web/routes/` during the active-uix cleanup. The `_perms.svelte.ts` context wiring referenced above predates that move and may need re-implementation when permutation testing is re-enabled on the new demo tree. +The old `/test/soma` shell used a shared `_perms.svelte.ts` context. That +shell was retired; the current runner only provides the URL variants and +expects each routed page under `web/routes` to read `?perm.*` params directly +or through a new shell-level helper when we decide to add one. Until a page +does that and declares `perm-axes` / `data-perm-step`, it is skipped rather +than counted as coverage. ### Demo opt-in @@ -149,14 +160,13 @@ Read the perms and use them as **initial** state (not live-bound — the user ca ```svelte ``` @@ -197,13 +207,13 @@ Demos opt in by listing the axes they honour. The runner skips meta-less demos a ```svelte ```