@motion-proto/live-tokens 0.77.0 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -0
- package/.claude/skills/live-tokens-create-page/SKILL.md +5 -4
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +7 -0
- package/CHANGELOG.md +53 -0
- package/bin/check-page.mjs +131 -9
- package/bin/cli.mjs +25 -3
- package/bin/contractRunner.mjs +343 -29
- package/bin/lib/pageRoutes.mjs +309 -0
- package/package.json +5 -3
- package/src/editor/overlay/ColumnsOverlay.svelte +1 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +1 -0
- package/src/editor/overlay/LiveTokensRouter.svelte +1 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
- package/src/editor/skill-atlas/trees/check-compliance.ts +18 -18
- package/src/editor/skill-atlas/trees/create-page.ts +26 -26
- package/src/editor/skill-atlas/trees/fix-findings.ts +46 -11
- package/src/testing-js/{chunk-7TI7Z6Y6.js → chunk-GNIUPIU2.js} +2 -2
- package/src/testing-js/{chunk-ZMSX6CXR.js → chunk-NB3NZRBM.js} +13 -2
- package/src/testing-js/chunk-NB3NZRBM.js.map +1 -0
- package/src/testing-js/{chunk-L73N4NSO.js → chunk-WIZ6W7UT.js} +2 -2
- package/src/testing-js/component-alias.contract.js +2 -2
- package/src/testing-js/component-editor.contract.js +3 -3
- package/src/testing-js/component-render.contract.js +3 -3
- package/src/testing-js/index.d.ts +8 -2
- package/src/testing-js/index.js +19 -1
- package/src/testing-js/index.js.map +1 -1
- package/src/testing-js/page-compliance.contract.js +829 -0
- package/src/testing-js/page-compliance.contract.js.map +1 -0
- package/src/testing-js/{vitest-BE6uGF31.d.ts → vitest-C-wNWcoA.d.ts} +21 -1
- package/src/testing-js/vitest.d.ts +1 -1
- package/src/testing-js/vitest.js +1 -1
- package/template/README.md +16 -9
- package/template/package.json +1 -1
- package/template/src/pages/Home.svelte +13 -0
- package/src/testing-js/chunk-ZMSX6CXR.js.map +0 -1
- /package/src/testing-js/{chunk-7TI7Z6Y6.js.map → chunk-GNIUPIU2.js.map} +0 -0
- /package/src/testing-js/{chunk-L73N4NSO.js.map → chunk-WIZ6W7UT.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../testing/page-compliance.contract.ts","../editor/ui/sections/textStyles.ts","../editor/core/palettes/contrast.ts","../testing/support/pageHarness.ts"],"sourcesContent":["import { test } from '@playwright/test';\nimport { TEXT_STYLES } from '../editor/ui/sections/textStyles';\nimport { allContracts } from './contracts';\nimport {\n PageHarness,\n pageTargets,\n pageViewports,\n restingPaintSpec,\n type ContractPaintSpec,\n} from './support/pageHarness';\n\nconst targets = pageTargets();\nconst viewports = pageViewports();\n\nconst bundles = TEXT_STYLES.map((style) => ({ name: style.name, prefix: style.prefix }));\n\nasync function paintSpecs(): Promise<ContractPaintSpec[]> {\n const contracts = await allContracts();\n return contracts\n .map((contract) => restingPaintSpec(contract))\n .filter((spec): spec is ContractPaintSpec => spec !== null);\n}\n\nasync function componentRoots(): Promise<string[]> {\n const contracts = await allContracts();\n return contracts\n .map((contract) => contract.parts[contract.root])\n .filter((declared): declared is string => typeof declared === 'string');\n}\n\nfor (const target of targets) {\n for (const viewport of viewports) {\n const at = `${viewport.width}x${viewport.height}`;\n\n test(`page-component-paint | ${target.source} | ${at}`, async ({ page }) => {\n const specs = await paintSpecs();\n const harness = await PageHarness.open(page, target, viewport);\n const inapplicable = await harness.assertComponentPaint(specs);\n test.skip(inapplicable !== null, inapplicable ?? '');\n });\n\n test(`page-text-style | ${target.source} | ${at}`, async ({ page }) => {\n const roots = await componentRoots();\n const harness = await PageHarness.open(page, target, viewport);\n const inapplicable = await harness.assertTextStyle(bundles, roots);\n test.skip(inapplicable !== null, inapplicable ?? '');\n });\n\n test(`page-contrast | ${target.source} | ${at}`, async ({ page }) => {\n const roots = await componentRoots();\n const harness = await PageHarness.open(page, target, viewport);\n const inapplicable = await harness.assertContrast(roots);\n test.skip(inapplicable !== null, inapplicable ?? '');\n });\n\n test(`page-grid | ${target.source} | ${at}`, async ({ page }) => {\n const harness = await PageHarness.open(page, target, viewport);\n const inapplicable = await harness.assertGrid();\n test.skip(inapplicable !== null, inapplicable ?? '');\n });\n\n test(`page-overflow | ${target.source} | ${at}`, async ({ page }) => {\n const specs = await paintSpecs();\n const harness = await PageHarness.open(page, target, viewport);\n const inapplicable = await harness.assertOverflow(specs);\n test.skip(inapplicable !== null, inapplicable ?? '');\n });\n }\n}\n","/**\n * Registry of the semantic text styles rendered by TextStylesSection.\n * Each style is a bundle of alias tokens declared in tokens.css; the prefix\n * joins with a kind suffix (`-font-family`, `-font-size`, …) to form each\n * axis variable the editor's pickers target.\n *\n * Family/size/weight/letter-spacing/line-height are editable on every style.\n * `hasTextTransform` marks the one style (eyebrow) that also exposes a\n * text-transform picker — the only axis that exists on just one style.\n */\n\nexport interface TextStyle {\n name: string;\n label: string;\n /** Variable prefix, e.g. `--heading-xl`; suffixes form each axis var. */\n prefix: string;\n /** Element (or class) this style is the default for. */\n defaultElement: string;\n preview: string;\n /** Whether this style exposes an editable `-text-transform` axis. */\n hasTextTransform?: boolean;\n}\n\nexport const TEXT_STYLES: TextStyle[] = [\n {\n name: 'heading-xl',\n label: 'Heading XL',\n prefix: '--heading-xl',\n defaultElement: 'h1',\n preview: 'Page title',\n },\n {\n name: 'heading-lg',\n label: 'Heading LG',\n prefix: '--heading-lg',\n defaultElement: 'h2',\n preview: 'Section heading',\n },\n {\n name: 'heading-md',\n label: 'Heading MD',\n prefix: '--heading-md',\n defaultElement: 'h3',\n preview: 'Subsection heading',\n },\n {\n name: 'heading-sm',\n label: 'Heading SM',\n prefix: '--heading-sm',\n defaultElement: 'h4',\n preview: 'Minor heading',\n },\n {\n name: 'body-md',\n label: 'Body MD',\n prefix: '--body-md',\n defaultElement: 'p',\n preview: 'Body copy for comfortable reading.',\n },\n {\n name: 'body-sm',\n label: 'Body SM',\n prefix: '--body-sm',\n defaultElement: 'small',\n preview: 'Captions and secondary text.',\n },\n {\n name: 'editorial-xl',\n label: 'Editorial XL',\n prefix: '--editorial-xl',\n defaultElement: 'editorial-xl',\n preview: 'Pull quotes and opening statements.',\n },\n {\n name: 'editorial-lg',\n label: 'Editorial LG',\n prefix: '--editorial-lg',\n defaultElement: 'editorial-lg',\n preview: 'Standfirsts and section ledes.',\n },\n {\n name: 'editorial-md',\n label: 'Editorial MD',\n prefix: '--editorial-md',\n defaultElement: 'editorial-md',\n preview: 'Long-form reading, set in the editorial face.',\n },\n {\n name: 'editorial-sm',\n label: 'Editorial SM',\n prefix: '--editorial-sm',\n defaultElement: 'editorial-sm',\n preview: 'Captions, credits, and asides.',\n },\n {\n name: 'code',\n label: 'Code',\n prefix: '--code',\n defaultElement: 'code',\n preview: 'const total = sum(items);',\n },\n {\n name: 'eyebrow',\n label: 'Eyebrow',\n prefix: '--eyebrow',\n defaultElement: 'eyebrow',\n preview: 'Overline label',\n hasTextTransform: true,\n },\n];\n","// WCAG 2.1 contrast math + an OKLCH-lightness solver.\n//\n// The solver is the engine behind the \"Derive accessible text\" action: given a\n// surface hex and a target ratio, it finds the lightness (at fixed hue) whose\n// gamut-clamped color clears the ratio. Contrast is monotonic in L only on one\n// side of the surface's own lightness, so `direction` selects the branch.\n\nimport { hexToOklch, oklchToHex, gamutClamp } from './oklch';\n\nexport const AA_BODY = 4.5;\nexport const AA_LARGE = 3.0;\n\nfunction linearizeChannel(v8: number): number {\n const s = v8 / 255;\n return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);\n}\n\nexport function relativeLuminance(hex: string): number {\n const r = linearizeChannel(parseInt(hex.slice(1, 3), 16));\n const g = linearizeChannel(parseInt(hex.slice(3, 5), 16));\n const b = linearizeChannel(parseInt(hex.slice(5, 7), 16));\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\nexport function contrastRatio(a: string, b: string): number {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n const hi = Math.max(la, lb);\n const lo = Math.min(la, lb);\n return (hi + 0.05) / (lo + 0.05);\n}\n\nexport interface FindLOptions {\n against: string;\n ratio: number;\n direction: 'lighter' | 'darker';\n c: number;\n h: number;\n margin?: number;\n /** Reachable-L window. The Text derivation multiplies seed L by a curve\n * factor clamped to [0,2], so a family can only reach L ∈ [0, min(1,2·seedL)].\n * The solver passes that ceiling as `lMax` so the returned L is realisable. */\n lMin?: number;\n lMax?: number;\n}\n\nexport interface FindLResult {\n l: number;\n c: number;\n hex: string;\n}\n\n/**\n * Binary-search OKLCH lightness for a color that clears `ratio` against\n * `against`, staying on the `direction` side of the surface (the monotonic\n * branch). If the ratio is unreachable at chroma `c` within the window, decay\n * chroma toward 0 and retry — achromatic extremes reach the full 1..21 range.\n * The result is re-verified with `contrastRatio` after the hex round-trip and\n * nudged one step if 8-bit quantization ate the margin (Global invariant 8).\n */\nexport function findLForContrast(opts: FindLOptions): FindLResult {\n const { against, ratio, direction, c, h } = opts;\n const margin = opts.margin ?? 0.05;\n const lMin = opts.lMin ?? 0;\n const lMax = opts.lMax ?? 1;\n const target = ratio + margin;\n const againstL = hexToOklch(against).l;\n\n const evalAt = (l: number, cc: number) => {\n const g = gamutClamp(l, cc, h);\n const hex = oklchToHex(g.l, g.c, g.h);\n return { hex, chroma: g.c, ratio: contrastRatio(hex, against) };\n };\n\n // Returns the boundary solution at a fixed chroma, or null if `target` is\n // unreachable within the window at that chroma. `extreme` is the window end\n // with the most contrast (top L for lighter, bottom L for darker).\n const solveAtChroma = (cc: number): { l: number; hex: string; chroma: number } | null => {\n const lighter = direction === 'lighter';\n const lo0 = lighter ? Math.max(lMin, againstL) : lMin;\n const hi0 = lighter ? lMax : Math.min(lMax, againstL);\n if (lo0 >= hi0) return null; // no room to move to the requested side within the window\n const extreme = lighter ? hi0 : lo0;\n if (evalAt(extreme, cc).ratio < target) return null;\n let lo = lo0;\n let hi = hi0;\n for (let i = 0; i < 24; i++) {\n const mid = (lo + hi) / 2;\n const meets = evalAt(mid, cc).ratio >= target;\n // Contrast rises toward `extreme`; keep the half that still meets target.\n if (lighter ? meets : !meets) hi = mid;\n else lo = mid;\n }\n const at = lighter ? hi : lo;\n const e = evalAt(at, cc);\n return { l: at, hex: e.hex, chroma: e.chroma };\n };\n\n let sol = solveAtChroma(c);\n if (!sol) {\n if (!solveAtChroma(0)) {\n // Even achromatic can't reach the target within the window — return the\n // extreme (best effort); the caller reports it as unmet.\n const extremeL = direction === 'lighter' ? lMax : lMin;\n const e = evalAt(extremeL, 0);\n return { l: extremeL, c: 0, hex: e.hex };\n }\n let lo = 0;\n let hi = c;\n for (let i = 0; i < 24; i++) {\n const mid = (lo + hi) / 2;\n if (solveAtChroma(mid)) lo = mid;\n else hi = mid;\n }\n sol = solveAtChroma(lo)!;\n }\n\n let { l, hex, chroma } = sol;\n const step = direction === 'lighter' ? 0.004 : -0.004;\n for (let k = 0; k < 12 && contrastRatio(hex, against) < ratio; k++) {\n l = Math.min(lMax, Math.max(lMin, l + step));\n const g = gamutClamp(l, chroma, h);\n l = g.l;\n chroma = g.c;\n hex = oklchToHex(g.l, g.c, g.h);\n }\n return { l, c: chroma, hex };\n}\n","import type { ElementHandle, Page } from '@playwright/test';\nimport {\n DEFAULT_PAGE_VIEWPORTS,\n PAGES_ENV,\n PAGE_VIEWPORTS_ENV,\n type PageViewport,\n} from '../config';\nimport { AA_BODY, AA_LARGE, contrastRatio } from '../../editor/core/palettes/contrast';\nimport { isInapplicable, type ComponentContract, type PaintMap } from '../componentContract';\n\nexport type PageRule =\n | 'page-component-paint'\n | 'page-text-style'\n | 'page-contrast'\n | 'page-grid'\n | 'page-overflow';\n\n/**\n * A failed obligation on a rendered page, carrying the rule id the CLI reports\n * it under and the page file line the finding anchors on.\n */\nexport class PageViolation extends Error {\n constructor(\n readonly rule: PageRule,\n readonly source: string,\n readonly line: number,\n message: string,\n ) {\n super(`[${rule}] ${source}:${line}: ${message}`);\n this.name = 'PageViolation';\n }\n}\n\nexport interface PageTarget {\n /** Page file the finding anchors on, relative to the project root. */\n source: string;\n route: string;\n}\n\n/** The wrapper `LiveTokensRouter` renders every consumer page inside. */\nconst PAGE_CONTAINER = '[data-live-tokens-page]';\n\n/** The router's own chrome: the editor overlay and the column guides. Inside\n * the container and never part of the page. */\nconst CHROME = '[data-live-tokens-chrome]';\n\nexport function pageTargets(): PageTarget[] {\n const raw = process.env[PAGES_ENV];\n if (!raw) return [];\n return JSON.parse(raw) as PageTarget[];\n}\n\nexport function pageViewports(): PageViewport[] {\n const raw = process.env[PAGE_VIEWPORTS_ENV];\n if (!raw) return DEFAULT_PAGE_VIEWPORTS;\n return JSON.parse(raw) as PageViewport[];\n}\n\n/**\n * A state a resting instance on a page is not in. The component contracts key\n * their paint maps by the editor's own tab labels, which name interaction\n * states (hover, focused), component states (disabled, selected, error, on),\n * and views that only exist once something is opened (an active tab, a menu\n * and its options). Everything else — a bare label, `base`, `default`, and the\n * structural part labels (Header, Body, Footer, ...) — paints at rest.\n */\nconst TRANSIENT_STATE = /\\b(?:hover|active|disabled|focused|error|selected|open|menu|option|on)\\b/;\n\nfunction restingState(label: string | undefined): boolean {\n return label === undefined || !TRANSIENT_STATE.test(label.toLowerCase());\n}\n\ninterface PaintCheck {\n part: string;\n /** Null for the contract's own root part, which is the instance itself. */\n selector: string | null;\n pseudo: string | null;\n css: string;\n variable: string;\n}\n\ninterface PaintEntry {\n /** The contract's own variant label, lowercased, as the finding names it. */\n variant: string | null;\n /** Source of the RegExp an instance's class tokens are tested against. Null\n * for an entry every instance is in. */\n pattern: string | null;\n checks: PaintCheck[];\n}\n\n/**\n * A class token names a variant when it is the variant's own word or carries\n * it as a suffix: the three spellings the shipped components use are bare\n * (`button primary`), component-prefixed (`badge badge-primary`), and\n * axis-prefixed (`es-root variant-divider`). A contract's variant label is the\n * editor's prose (`With Divider`), so each of its words is a key as well as\n * the whole label, which is what reaches the `divider` the markup spells.\n */\nfunction variantPattern(label: string | undefined | null): string | null {\n if (!label) return null;\n const words = label.toLowerCase().split(/\\s+/);\n const keys = [...new Set([words.join('-'), ...words])]\n .map((key) => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n return `(?:^|-)(?:${keys.join('|')})$`;\n}\n\nexport interface ContractPaintSpec {\n id: string;\n root: string;\n entries: PaintEntry[];\n}\n\nfunction paintChecks(contract: ComponentContract, map: PaintMap): PaintCheck[] {\n const checks: PaintCheck[] = [];\n for (const [part, properties] of Object.entries(map)) {\n const declared = contract.parts[part];\n if (declared === undefined) continue;\n const locator = typeof declared === 'string' ? { selector: declared } : declared;\n // A portalled part is rendered by an interaction, so no resting page\n // instance has one.\n if (typeof declared !== 'string' && declared.portal) continue;\n for (const [css, variable] of Object.entries(properties)) {\n checks.push({\n part,\n selector: part === contract.root ? null : locator.selector,\n pseudo: (typeof declared === 'string' ? undefined : declared.pseudo) ?? null,\n css,\n variable,\n });\n }\n }\n return checks;\n}\n\n/**\n * What a contract obliges of an instance sitting at rest on a page: the paint\n * maps that need no state tab, no editor control, and no interaction. A\n * variant-keyed entry carries the pattern its instances' root classes have to\n * name.\n */\nexport function restingPaintSpec(contract: ComponentContract): ContractPaintSpec | null {\n const rootSelector = contract.parts[contract.root];\n if (typeof rootSelector !== 'string') return null;\n const entries: PaintEntry[] = [];\n // `contract.view` opens the component editor: its `setup` drives preview\n // controls no page has and its `state` names a tab. Only `variant` carries\n // onto a page, so an entry inherits that and nothing else.\n const inherited = contract.view ?? {};\n for (const expectation of contract.properties) {\n if ((expectation.setup ?? []).length > 0) continue;\n if (!restingState(expectation.state)) continue;\n const checks = paintChecks(contract, expectation.paints);\n if (checks.length === 0) continue;\n const variant = (expectation.variant ?? inherited.variant)?.toLowerCase() ?? null;\n entries.push({ variant, pattern: variantPattern(variant), checks });\n }\n // A component whose only resting paint map hangs off a state tab still\n // paints its default state on a page.\n if (!isInapplicable(contract.states)) {\n for (const state of contract.states) {\n if (!state.paints || !restingState(state.state)) continue;\n if ((state.setup ?? []).length > 0) continue;\n const checks = paintChecks(contract, state.paints);\n if (checks.length === 0) continue;\n const variant = (state.variant ?? inherited.variant)?.toLowerCase() ?? null;\n entries.push({ variant, pattern: variantPattern(variant), checks });\n }\n }\n if (entries.length === 0) return null;\n return { id: contract.id, root: rootSelector, entries };\n}\n\nexport interface PaintFailure {\n id: string;\n variant: string | null;\n line: number;\n part: string;\n css: string;\n variable: string;\n actual: string;\n expected: string;\n}\n\nexport interface UncoveredInstance {\n id: string;\n line: number;\n /** The variants the contract offers, none of which this instance's root\n * classes name. */\n variants: string[];\n}\n\nexport interface PaintObservation {\n instances: number;\n asserted: number;\n failures: PaintFailure[];\n /** Instances every variant-keyed entry passed over: decision 6's exception,\n * reported per instance rather than silently passed. */\n uncovered: UncoveredInstance[];\n}\n\nexport interface TextObservation {\n elements: number;\n failures: { line: number; tag: string; nearest: string; axis: string; actual: string; expected: string }[];\n}\n\nexport interface TextBundle {\n name: string;\n prefix: string;\n}\n\nexport interface ContrastPair {\n line: number;\n tag: string;\n /** Computed text colour, composited over the surface when translucent. */\n color: string;\n background: string;\n colorToken: string | null;\n backgroundToken: string | null;\n fontSize: number;\n fontWeight: number;\n}\n\nexport interface ContrastObservation {\n pairs: ContrastPair[];\n /** Why a run of text carries no pair, counted by reason. */\n skipped: Record<string, number>;\n}\n\nexport interface GridChildFailure {\n line: number;\n tag: string;\n edge: 'left' | 'right';\n /** Distance from the nearest track edge, in px. */\n off: number;\n}\n\nexport interface GridObservation {\n grids: number;\n children: number;\n failures: GridChildFailure[];\n /** The page's own outermost element, which a missing grid anchors on. */\n pageLine: number;\n}\n\nexport interface OverflowFailure {\n line: number;\n tag: string;\n kind: 'document' | 'element' | 'instance';\n detail: string;\n}\n\nexport interface OverflowObservation {\n elements: number;\n failures: OverflowFailure[];\n}\n\nconst TEXT_AXES = ['fontFamily', 'fontSize', 'fontWeight', 'lineHeight', 'letterSpacing'] as const;\n\n/** Displays that make an element a block of its own. Text sitting directly in\n * anything else is a run inside a line, and the line's own block owns it. */\nconst BLOCK_DISPLAYS = ['block', 'flow-root', 'list-item', 'flex', 'grid', 'table-cell', 'table-caption'];\n\n/** Sub-pixel track positions and a browser's own rounding put an edge that is\n * on the line up to a pixel off it. */\nconst GRID_TOLERANCE = 1;\n\n/** Below this the page is one column, so the grid rule has nothing to hold a\n * section to. */\nconst GRID_VIEWPORT = 768;\n\n/** WCAG's large-text sizes: the AA floor drops to 3:1 at either. */\nconst LARGE_TEXT_PX = 24;\nconst LARGE_BOLD_PX = 18.66;\nconst BOLD = 700;\n\n/** Findings name CSS properties as a page's own stylesheet spells them. */\nconst cssName = (camel: string) => camel.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/** The line the finding anchors on, the rest named in the message, and what\n * the rule looked at to find them: one rerun after each repair walks the\n * whole list. */\nfunction report<T>(failures: T[], scope: string, describe: (failure: T) => string): string {\n const lines = failures.map(describe);\n return lines.length === 1 ? lines[0] : `${lines.length} of ${scope}\\n${lines.join('\\n')}`;\n}\n\nfunction hex(rgb: string): string {\n const [r, g, b] = rgb.match(/\\d+/g)!.map(Number);\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;\n}\n\n/** The AA floor the pair has to clear, which its own size and weight set. */\nfunction aaFloor(pair: ContrastPair): number {\n const large = pair.fontSize >= LARGE_TEXT_PX\n || (pair.fontSize >= LARGE_BOLD_PX && pair.fontWeight >= BOLD);\n return large ? AA_LARGE : AA_BODY;\n}\n\n/** A reason reads as prose: `a gradient (2), a blend mode (1)`. */\nfunction reasons(skipped: Record<string, number>): string {\n return Object.entries(skipped).map(([reason, count]) => `${reason} (${count})`).join(', ');\n}\n\n/** The page under test, and what the rules read off it. */\nexport class PageHarness {\n private constructor(\n readonly page: Page,\n readonly target: PageTarget,\n readonly viewport: PageViewport,\n ) {}\n\n static async open(page: Page, target: PageTarget, viewport: PageViewport): Promise<PageHarness> {\n await installPageProbe(page);\n await page.setViewportSize(viewport);\n await page.goto(target.route);\n await page.locator(PAGE_CONTAINER).waitFor();\n // The lazy route module, then the theme's own custom properties: a page\n // measured before either lands reads the browser's defaults as the\n // theme's values.\n await page.waitForFunction((selectors) => {\n const container = document.querySelector(selectors.container);\n if (!container) return false;\n const content = [...container.children].some((child) => !child.matches(selectors.chrome));\n const themed = getComputedStyle(document.documentElement)\n .getPropertyValue('--body-md-font-size').trim().length > 0;\n return content && themed;\n }, { container: PAGE_CONTAINER, chrome: CHROME });\n await page.waitForLoadState('networkidle');\n await page.evaluate(async () => {\n await document.fonts.ready;\n const frame = () => new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));\n await frame();\n await frame();\n });\n return new PageHarness(page, target, viewport);\n }\n\n fail(rule: PageRule, line: number, message: string): never {\n throw new PageViolation(rule, this.target.source, line, message);\n }\n\n /** Every shipped instance on the page, as the contract root selectors find\n * them, with the variant its own root classes name. */\n async instances(specs: ContractPaintSpec[]): Promise<{ id: string; variant: string | null; line: number }[]> {\n return this.page.evaluate(({ specs: list, container, chrome, source }) => {\n const found: { id: string; variant: string | null; line: number }[] = [];\n const root = document.querySelector(container);\n if (!root) return found;\n for (const spec of list) {\n const keyed: { variant: string | null; pattern: RegExp }[] = [];\n for (const entry of spec.entries) {\n if (entry.pattern !== null) keyed.push({ variant: entry.variant, pattern: new RegExp(entry.pattern) });\n }\n for (const element of root.querySelectorAll(spec.root)) {\n if (element.closest(chrome)) continue;\n const classes = [...element.classList];\n const matched = keyed.find(({ pattern }) => classes.some((name) => pattern.test(name)));\n found.push({\n id: spec.id,\n variant: matched?.variant ?? null,\n line: window.__liveTokensPageLine(element, source),\n });\n }\n }\n return found;\n }, { specs, container: PAGE_CONTAINER, chrome: CHROME, source: this.target.source });\n }\n\n /**\n * Every element that renders text of its own: block-level, inside the page\n * container, outside the router's chrome, and outside every shipped\n * component root, whose slot typography the component owns.\n */\n async textElements(componentRoots: string[]): Promise<{ line: number; tag: string; axes: Record<string, string> }[]> {\n return this.page.evaluate(({ roots, container, chrome, source, axes, displays }) => {\n const found: { line: number; tag: string; axes: Record<string, string> }[] = [];\n const root = document.querySelector(container);\n if (!root) return found;\n const inComponent = roots.join(', ');\n for (const element of root.querySelectorAll<HTMLElement>('*')) {\n if (element.closest(chrome)) continue;\n if (inComponent && element.closest(inComponent)) continue;\n const ownText = [...element.childNodes]\n .some((node) => node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim().length > 0);\n if (!ownText) continue;\n const style = getComputedStyle(element);\n if (!displays.includes(style.display)) continue;\n found.push({\n line: window.__liveTokensPageLine(element, source),\n tag: element.tagName.toLowerCase(),\n axes: Object.fromEntries(axes.map((axis) => [axis, (style as unknown as Record<string, string>)[axis] ?? ''])),\n });\n }\n return found;\n }, {\n roots: componentRoots,\n container: PAGE_CONTAINER,\n chrome: CHROME,\n source: this.target.source,\n axes: [...TEXT_AXES],\n displays: BLOCK_DISPLAYS,\n });\n }\n\n /** The page file line the element was written on, from Svelte's dev-build\n * element metadata. */\n async lineOf(element: ElementHandle<Element>): Promise<number> {\n return element.evaluate((node, source) => window.__liveTokensPageLine(node, source), this.target.source);\n }\n\n /** Every resting paint obligation, measured on the instances themselves. */\n async observePaints(specs: ContractPaintSpec[]): Promise<PaintObservation> {\n return this.page.evaluate(({ specs: list, container, chrome, source }) => {\n const failures: PaintObservation['failures'] = [];\n const uncovered: UncoveredInstance[] = [];\n let instances = 0;\n let asserted = 0;\n const root = document.querySelector(container);\n if (!root) return { instances, asserted, failures, uncovered };\n\n // The value a CSS property takes from the raw token, measured beside the\n // part so inherited font size and colour resolve the same way. A token\n // holding `#fff` and a computed `rgb(255, 255, 255)` are the same paint,\n // and only the browser can say so.\n //\n // The part's own font size is seeded onto the probe first: a unitless\n // line height and an `em` letter spacing resolve against it, and a\n // component that sets its own font size (every Button does) makes the\n // parent's the wrong ruler. A `font-size` token overwrites the seed and\n // still resolves its own relative units against the parent, as it must.\n const normalize = (node: Element, css: string, raw: string): string => {\n const probe = document.createElement('div');\n probe.style.display = 'none';\n // A border width computes to 0 while the border style is `none`, so\n // the probe would answer 0px for every stroke thickness.\n probe.style.borderStyle = 'solid';\n probe.style.outlineStyle = 'solid';\n probe.style.fontSize = getComputedStyle(node).fontSize;\n (node.parentElement ?? document.body).appendChild(probe);\n probe.style.setProperty(css.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`), raw);\n const computed = (getComputedStyle(probe) as unknown as Record<string, string>)[css];\n probe.remove();\n return (computed ?? '').trim();\n };\n\n for (const spec of list) {\n const patterns = spec.entries.map((entry) => (entry.pattern === null ? null : new RegExp(entry.pattern)));\n const variants = [...new Set(spec.entries.map((entry) => entry.variant).filter((v): v is string => v !== null))];\n for (const element of root.querySelectorAll(spec.root)) {\n if (element.closest(chrome)) continue;\n instances++;\n const classes = [...element.classList];\n const line = window.__liveTokensPageLine(element, source);\n let covered = false;\n for (const [index, entry] of spec.entries.entries()) {\n const pattern = patterns[index];\n if (pattern && !classes.some((name) => pattern.test(name))) continue;\n covered = true;\n for (const check of entry.checks) {\n const part = check.selector === null ? element : element.querySelector(check.selector);\n if (!part) continue;\n const style = getComputedStyle(part, check.pseudo ?? undefined);\n const token = style.getPropertyValue(check.variable).trim();\n if (!token) continue;\n const expected = normalize(part, check.css, token);\n const actual = ((style as unknown as Record<string, string>)[check.css] ?? '').trim();\n asserted++;\n if (actual !== expected) {\n failures.push({\n id: spec.id,\n variant: entry.variant,\n line,\n part: check.part,\n css: check.css,\n variable: check.variable,\n actual,\n expected,\n });\n }\n }\n }\n // One Badge naming its variant says nothing about the next one, so\n // decision 6's exception is counted per instance.\n if (!covered) uncovered.push({ id: spec.id, line, variants });\n }\n }\n return { instances, asserted, failures, uncovered };\n }, { specs, container: PAGE_CONTAINER, chrome: CHROME, source: this.target.source });\n }\n\n /** Every run of text on the page, against the theme's own style bundles as\n * they resolve at this viewport. */\n async observeTextStyles(bundles: TextBundle[], componentRoots: string[]): Promise<TextObservation> {\n const elements = await this.textElements(componentRoots);\n const expected = await this.page.evaluate(({ list, axes }) => {\n const style = getComputedStyle(document.documentElement);\n const probe = document.createElement('div');\n probe.style.position = 'absolute';\n probe.style.visibility = 'hidden';\n document.body.appendChild(probe);\n const resolved: Record<string, Record<string, string>> = {};\n for (const bundle of list) {\n // One probe per bundle, carrying all five axes at once: a unitless\n // line height and an `em` letter spacing resolve against the bundle's\n // own font size, so five separately measured values would not be the\n // bundle. The reset clears an axis the previous bundle set.\n probe.style.cssText = 'position:absolute;visibility:hidden';\n for (const axis of axes) {\n probe.style.setProperty(\n axis.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`),\n style.getPropertyValue(`${bundle.prefix}-${axis.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`).trim(),\n );\n }\n const computed = getComputedStyle(probe) as unknown as Record<string, string>;\n resolved[bundle.name] = Object.fromEntries(axes.map((axis) => [axis, (computed[axis] ?? '').trim()]));\n }\n probe.remove();\n return resolved;\n }, { list: bundles, axes: [...TEXT_AXES] });\n\n const failures: TextObservation['failures'] = [];\n for (const element of elements) {\n let best = { name: '', misses: [] as string[], distance: Number.POSITIVE_INFINITY };\n for (const bundle of bundles) {\n const misses: string[] = TEXT_AXES.filter((axis) => element.axes[axis] !== expected[bundle.name][axis]);\n if (misses.length === 0) { best = { name: bundle.name, misses: [], distance: 0 }; break; }\n // Family and size are what identify a bundle by eye, so a bundle that\n // matches both and misses a weight is nearer than one that misses the\n // face, even on the same count.\n const distance = misses.length\n + (misses.includes('fontFamily') ? 0.4 : 0)\n + (misses.includes('fontSize') ? 0.2 : 0);\n if (distance < best.distance) best = { name: bundle.name, misses, distance };\n }\n if (best.misses.length === 0) continue;\n const axis = best.misses[0];\n failures.push({\n line: element.line,\n tag: element.tag,\n nearest: best.name,\n axis,\n actual: element.axes[axis],\n expected: expected[best.name][axis],\n });\n }\n return { elements: elements.length, failures };\n }\n\n /**\n * Every text and surface pair the page composes, with the tokens whose\n * resolved values match them. The ratio itself is computed by the caller\n * from the editor's own WCAG helper, so one implementation answers for the\n * palette editor and for a page.\n */\n async observeContrast(componentRoots: string[]): Promise<ContrastObservation> {\n return this.page.evaluate(({ roots, container, chrome, source, displays }) => {\n const pairs: ContrastPair[] = [];\n const skipped: Record<string, number> = {};\n const skip = (reason: string) => { skipped[reason] = (skipped[reason] ?? 0) + 1; };\n const root = document.querySelector(container);\n if (!root) return { pairs, skipped };\n\n // The theme states its colours in `oklch()`, which Chromium keeps in the\n // computed value, so no string parse reaches the channels. A 1x1 canvas\n // in `copy` mode answers with the pixel the screen shows, gamut-clamped\n // the same way, for any colour syntax including a future one. An invalid\n // or `transparent` value leaves the cleared pixel, which reads as alpha 0\n // and is what \"paints nothing here\" means.\n const canvas = document.createElement('canvas');\n canvas.width = 1;\n canvas.height = 1;\n const ctx = canvas.getContext('2d', { willReadFrequently: true })!;\n ctx.globalCompositeOperation = 'copy';\n const rgba = (value: string): [number, number, number, number] => {\n ctx.fillStyle = 'rgba(0, 0, 0, 0)';\n ctx.fillStyle = value;\n ctx.fillRect(0, 0, 1, 1);\n const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;\n return [r, g, b, a / 255];\n };\n const key = ([r, g, b]: [number, number, number, number]) => `${r},${g},${b}`;\n const css = (c: [number, number, number, number]) =>\n (c[3] === 1 ? `rgb(${c[0]}, ${c[1]}, ${c[2]})` : `rgba(${c[0]}, ${c[1]}, ${c[2]}, ${c[3]})`);\n\n // A finding names the token when one resolves to the colour it measured.\n const probe = document.createElement('div');\n probe.style.display = 'none';\n document.body.appendChild(probe);\n const named = new Map<string, string>();\n const rootStyle = getComputedStyle(document.documentElement);\n for (const name of rootStyle) {\n if (!name.startsWith('--')) continue;\n probe.style.color = '';\n probe.style.color = `var(${name})`;\n const resolved = getComputedStyle(probe).color;\n if (!resolved) continue;\n const colour = rgba(resolved);\n if (colour[3] === 0) continue;\n if (!named.has(key(colour))) named.set(key(colour), name);\n }\n probe.remove();\n\n const over = (\n front: [number, number, number, number],\n back: [number, number, number, number],\n ): [number, number, number, number] => [\n Math.round(front[0] * front[3] + back[0] * (1 - front[3])),\n Math.round(front[1] * front[3] + back[1] * (1 - front[3])),\n Math.round(front[2] * front[3] + back[2] * (1 - front[3])),\n 1,\n ];\n\n const inComponent = roots.join(', ');\n for (const element of root.querySelectorAll<HTMLElement>('*')) {\n if (element.closest(chrome)) continue;\n if (inComponent && element.closest(inComponent)) continue;\n const ownText = [...element.childNodes]\n .some((node) => node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim().length > 0);\n if (!ownText) continue;\n const style = getComputedStyle(element);\n if (!displays.includes(style.display)) continue;\n if (style.visibility !== 'visible') continue;\n const colour = rgba(style.color);\n if (colour[3] === 0) continue;\n\n let surface: [number, number, number, number] | null = null;\n let reason: string | null = null;\n for (let node: Element | null = element; node; node = node.parentElement) {\n const behind = getComputedStyle(node);\n if (behind.mixBlendMode !== 'normal') { reason = 'a blend mode'; break; }\n if (behind.backgroundImage !== 'none') { reason = 'a gradient or an image behind the text'; break; }\n const paint = rgba(behind.backgroundColor);\n if (paint[3] === 1) { surface = paint; break; }\n if (paint[3] > 0) { reason = 'a translucent surface'; break; }\n }\n if (!surface) {\n skip(reason ?? 'no opaque surface behind the text');\n continue;\n }\n pairs.push({\n line: window.__liveTokensPageLine(element, source),\n tag: element.tagName.toLowerCase(),\n color: css(colour[3] === 1 ? colour : over(colour, surface)),\n background: css(surface),\n colorToken: named.get(key(colour[3] === 1 ? colour : over(colour, surface))) ?? null,\n backgroundToken: named.get(key(surface)) ?? null,\n fontSize: parseFloat(style.fontSize),\n fontWeight: parseInt(style.fontWeight, 10) || 400,\n });\n }\n return { pairs, skipped };\n }, {\n roots: componentRoots,\n container: PAGE_CONTAINER,\n chrome: CHROME,\n source: this.target.source,\n displays: BLOCK_DISPLAYS,\n });\n }\n\n /** Where each section of the page sits against the column grid it draws. */\n async observeGrid(): Promise<GridObservation> {\n return this.page.evaluate(({ container, chrome, source, tolerance }) => {\n const failures: GridChildFailure[] = [];\n let grids = 0;\n let children = 0;\n const root = document.querySelector(container);\n if (!root) return { grids, children, failures, pageLine: 1 };\n const own = [...root.children].find((child) => !child.matches(chrome));\n const pageLine = own ? window.__liveTokensPageLine(own, source) : 1;\n\n const columns = parseInt(\n getComputedStyle(document.documentElement).getPropertyValue('--columns-count').trim(),\n 10,\n );\n if (!columns) return { grids, children, failures, pageLine };\n\n for (const grid of root.querySelectorAll('*')) {\n if (grid.closest(chrome)) continue;\n const style = getComputedStyle(grid);\n const tracks = style.gridTemplateColumns.split(/\\s+/).map(parseFloat);\n if (tracks.length !== columns || tracks.some((track) => !Number.isFinite(track))) continue;\n grids++;\n const box = grid.getBoundingClientRect();\n const gap = parseFloat(style.columnGap) || 0;\n let offset = box.left + parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft);\n const lines: number[] = [];\n for (const track of tracks) {\n lines.push(offset);\n offset += track;\n lines.push(offset);\n offset += gap;\n }\n const nearest = (edge: number) =>\n lines.reduce((best, line) => (Math.abs(line - edge) < Math.abs(best - edge) ? line : best), lines[0]);\n\n const items = getComputedStyle(grid).justifyItems;\n for (const child of grid.children) {\n if (child.matches(chrome)) continue;\n const kid = getComputedStyle(child);\n if (kid.display === 'none' || kid.display === 'contents') continue;\n // Out of flow: the grid places its containing block, not the box.\n if (kid.position === 'absolute' || kid.position === 'fixed') continue;\n // The border box, which is the edge the eye reads. A margin that\n // insets a stretched section moves that edge off the line, and the\n // finding is the same one a reader would raise.\n const { left, right } = child.getBoundingClientRect();\n children++;\n // Self-alignment is the grid's own way of insetting a box in the\n // area it placed: a centred section is still on the grid, and only\n // the edge its alignment pins has to land on a line. A margin, a\n // width, or a transform is not the grid's vocabulary, so a stretched\n // item answers for both edges.\n const align = kid.justifySelf === 'auto' ? items : kid.justifySelf;\n const centred = align === 'center';\n const pinned: ('left' | 'right')[] = centred ? []\n : align === 'start' || align === 'flex-start' || align === 'left' ? ['left']\n : align === 'end' || align === 'flex-end' || align === 'right' ? ['right']\n : ['left', 'right'];\n if (centred) {\n const before = left - Math.max(...lines.filter((line) => line <= left + tolerance), lines[0]);\n const after = Math.min(...lines.filter((line) => line >= right - tolerance), lines[lines.length - 1]) - right;\n if (Math.abs(before - after) > tolerance) {\n failures.push({\n line: window.__liveTokensPageLine(child, source),\n tag: child.tagName.toLowerCase(),\n edge: before > after ? 'left' : 'right',\n off: Math.round((before - after) * 10) / 10,\n });\n }\n continue;\n }\n for (const edge of pinned) {\n const at = edge === 'left' ? left : right;\n const off = at - nearest(at);\n if (Math.abs(off) <= tolerance) continue;\n failures.push({\n line: window.__liveTokensPageLine(child, source),\n tag: child.tagName.toLowerCase(),\n edge,\n off: Math.round(off * 10) / 10,\n });\n }\n }\n }\n return { grids, children, failures, pageLine };\n }, { container: PAGE_CONTAINER, chrome: CHROME, source: this.target.source, tolerance: GRID_TOLERANCE });\n }\n\n /** What the page pushes past the viewport, past its own container, or past\n * the box that clips it. */\n async observeOverflow(specs: ContractPaintSpec[]): Promise<OverflowObservation> {\n return this.page.evaluate(({ specs: list, container, chrome, source, tolerance }) => {\n const failures: OverflowFailure[] = [];\n let elements = 0;\n const root = document.querySelector(container);\n if (!root) return { elements, failures };\n const own = [...root.children].find((child) => !child.matches(chrome));\n const pageLine = own ? window.__liveTokensPageLine(own, source) : 1;\n\n const page = document.documentElement;\n if (page.scrollWidth > window.innerWidth + tolerance) {\n failures.push({\n line: pageLine,\n tag: 'html',\n kind: 'document',\n detail: `the document scrolls to ${page.scrollWidth}px at a ${window.innerWidth}px viewport`,\n });\n }\n\n for (const element of root.querySelectorAll('*')) {\n if (element.closest(chrome)) continue;\n const style = getComputedStyle(element);\n if (style.display === 'none' || style.display === 'contents') continue;\n // An inline box reports 0 for both, so every one of them would read as\n // overflowing its own zero width.\n if (element.clientWidth === 0) continue;\n elements++;\n if (style.overflowX === 'auto' || style.overflowX === 'scroll') continue;\n if (element.scrollWidth > element.clientWidth + tolerance) {\n failures.push({\n line: window.__liveTokensPageLine(element, source),\n tag: element.tagName.toLowerCase(),\n kind: 'element',\n detail: `<${element.tagName.toLowerCase()}> holds ${element.scrollWidth}px of content in `\n + `${element.clientWidth}px, and its overflow-x is ${style.overflowX}`,\n });\n }\n }\n\n const clips = (style: CSSStyleDeclaration) =>\n style.overflowX !== 'visible' || style.overflowY !== 'visible';\n for (const spec of list) {\n for (const instance of root.querySelectorAll(spec.root)) {\n if (instance.closest(chrome)) continue;\n let clipper: Element | null = null;\n for (let node = instance.parentElement; node; node = node.parentElement) {\n if (clips(getComputedStyle(node))) { clipper = node; break; }\n }\n if (!clipper) continue;\n const box = instance.getBoundingClientRect();\n const bounds = clipper.getBoundingClientRect();\n const past = [\n box.left < bounds.left - tolerance ? `${Math.round(bounds.left - box.left)}px past the left edge` : null,\n box.right > bounds.right + tolerance ? `${Math.round(box.right - bounds.right)}px past the right edge` : null,\n ].filter((side): side is string => side !== null);\n if (past.length === 0) continue;\n failures.push({\n line: window.__liveTokensPageLine(instance, source),\n tag: spec.id,\n kind: 'instance',\n detail: `${spec.id} sits ${past.join(' and ')} of the `\n + `<${clipper.tagName.toLowerCase()}> that clips it`,\n });\n }\n }\n return { elements, failures };\n }, { specs, container: PAGE_CONTAINER, chrome: CHROME, source: this.target.source, tolerance: 1 });\n }\n\n /**\n * Every rule reports the same three ways: it throws a `PageViolation` the\n * runner turns into a finding, returns a reason the run records as\n * `inapplicable`, or returns null, which is the pass. The suite files hold\n * no rule of their own so the defect fixtures measure what a consumer runs.\n */\n async assertComponentPaint(specs: ContractPaintSpec[]): Promise<string | null> {\n const observed = await this.observePaints(specs);\n if (observed.instances === 0) return `${this.target.source} renders no shipped component`;\n if (observed.failures.length > 0) {\n this.fail(\n 'page-component-paint',\n observed.failures[0].line,\n report(observed.failures, `${observed.asserted} contracted paints`, (failure) =>\n `line ${failure.line}: ${failure.id}${failure.variant ? ` (${failure.variant})` : ''} `\n + `paints ${failure.part} ${cssName(failure.css)}: ${failure.actual}, `\n + `but ${failure.variable} resolves to ${failure.expected}`),\n );\n }\n // A page holding an instance the rule cannot place in a variant is a page\n // the rule did not prove.\n if (observed.uncovered.length > 0) {\n return `${this.target.source}: no entry names the variant of `\n + observed.uncovered\n .map((instance) => `${instance.id} at line ${instance.line}, which offers ${instance.variants.join(', ')}`)\n .join('; ');\n }\n if (observed.asserted === 0) {\n return `no contracted part of a shipped instance is rendered on ${this.target.source}`;\n }\n return null;\n }\n\n async assertTextStyle(bundles: TextBundle[], componentRoots: string[]): Promise<string | null> {\n const observed = await this.observeTextStyles(bundles, componentRoots);\n if (observed.elements === 0) return `${this.target.source} renders no text outside a shipped component`;\n if (observed.failures.length > 0) {\n this.fail(\n 'page-text-style',\n observed.failures[0].line,\n report(observed.failures, `${observed.elements} runs of text`, (failure) =>\n `line ${failure.line}: <${failure.tag}> is in no shipped text style. `\n + `Nearest is ${failure.nearest}: its ${cssName(failure.axis)} is ${failure.expected}, `\n + `the element's is ${failure.actual}`),\n );\n }\n return null;\n }\n\n async assertContrast(componentRoots: string[]): Promise<string | null> {\n const observed = await this.observeContrast(componentRoots);\n const failures = observed.pairs\n .map((pair) => ({ pair, ratio: contrastRatio(hex(pair.color), hex(pair.background)), floor: aaFloor(pair) }))\n .filter(({ ratio, floor }) => ratio < floor);\n if (failures.length > 0) {\n const names = (colour: string, token: string | null) => (token ? `${token} (${colour})` : colour);\n this.fail(\n 'page-contrast',\n failures[0].pair.line,\n report(failures, `${observed.pairs.length} text and surface pairs`, ({ pair, ratio, floor }) =>\n `line ${pair.line}: <${pair.tag}> at ${pair.fontSize}px/${pair.fontWeight} sets `\n + `${names(pair.color, pair.colorToken)} on ${names(pair.background, pair.backgroundToken)}, `\n + `a ratio of ${ratio.toFixed(2)} against the ${floor} AA floor`),\n );\n }\n if (observed.pairs.length === 0) {\n const why = reasons(observed.skipped);\n return why\n ? `no text on ${this.target.source} sits on a surface the rule can read: ${why}`\n : `${this.target.source} renders no text outside a shipped component`;\n }\n return null;\n }\n\n async assertGrid(): Promise<string | null> {\n if (this.viewport.width < GRID_VIEWPORT) {\n return `the page is one column at ${this.viewport.width}px, below the ${GRID_VIEWPORT}px the grid needs`;\n }\n const observed = await this.observeGrid();\n if (observed.grids === 0) {\n this.fail(\n 'page-grid',\n observed.pageLine,\n `${this.target.source} draws no column grid: no element on it has as many tracks as `\n + '--columns-count. The page is the column grid, so its sections sit on one',\n );\n }\n if (observed.failures.length > 0) {\n this.fail(\n 'page-grid',\n observed.failures[0].line,\n report(observed.failures, `${observed.children} sections on ${observed.grids} column grids`, (failure) =>\n `line ${failure.line}: <${failure.tag}> sits ${Math.abs(failure.off)}px `\n + `to the ${failure.off < 0 ? 'left' : 'right'} of the nearest column line on its ${failure.edge}`),\n );\n }\n return null;\n }\n\n async assertOverflow(specs: ContractPaintSpec[]): Promise<string | null> {\n const observed = await this.observeOverflow(specs);\n if (observed.failures.length > 0) {\n this.fail(\n 'page-overflow',\n observed.failures[0].line,\n report(observed.failures, `${observed.elements} boxes`, (failure) =>\n `line ${failure.line}: ${failure.detail}`),\n );\n }\n return null;\n }\n}\n\n/** Installed on every page the run opens, so each rule reads the line the same\n * way. Svelte's dev build stamps every element with the file and line it was\n * written on; an element a component rendered carries the component's file, so\n * the nearest ancestor the page file names is the line of the instance. */\nexport async function installPageProbe(page: Page): Promise<void> {\n await page.addInitScript(() => {\n window.__liveTokensPageLine = (node: Element, source: string): number => {\n for (let element: Element | null = node; element; element = element.parentElement) {\n const loc = (element as { __svelte_meta?: { loc?: { file?: string; line?: number } } }).__svelte_meta?.loc;\n if (!loc || typeof loc.file !== 'string' || typeof loc.line !== 'number') continue;\n if (loc.file.split('\\\\').join('/').endsWith(source)) return loc.line;\n }\n return 1;\n };\n });\n}\n\ndeclare global {\n interface Window {\n __liveTokensPageLine: (node: Element, source: string) => number;\n }\n}\n"],"mappings":"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","names":["hex","componentRoots","bundles","axis"]}
|
|
@@ -8,6 +8,17 @@ declare const TESTING_CONFIG_FILE = "live-tokens.testing.ts";
|
|
|
8
8
|
declare const COMPONENTS_PATH_ENV = "LIVE_TOKENS_COMPONENTS_PATH";
|
|
9
9
|
/** Narrows a run to one component. */
|
|
10
10
|
declare const COMPONENT_ENV = "LIVE_TOKENS_COMPONENT";
|
|
11
|
+
/** The page targets a run opens, as JSON: `[{ source, route }]`. */
|
|
12
|
+
declare const PAGES_ENV = "LIVE_TOKENS_PAGES";
|
|
13
|
+
/** The viewports every page rule runs at, as JSON: `[{ width, height }]`. */
|
|
14
|
+
declare const PAGE_VIEWPORTS_ENV = "LIVE_TOKENS_PAGE_VIEWPORTS";
|
|
15
|
+
interface PageViewport {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
/** The contract project's own desktop size, and the phone the text styles
|
|
20
|
+
* carry media overrides for. Nothing here is derived from a page. */
|
|
21
|
+
declare const DEFAULT_PAGE_VIEWPORTS: PageViewport[];
|
|
11
22
|
interface LiveTokensTestingConfig {
|
|
12
23
|
/** Vite config a generated Vitest config merges. Default `vite.config.ts`. */
|
|
13
24
|
viteConfig?: string;
|
|
@@ -31,6 +42,13 @@ interface LiveTokensTestingConfig {
|
|
|
31
42
|
* plus a dynamic import inside `selectedContracts()` is the only way a
|
|
32
43
|
* custom contract reaches them. */
|
|
33
44
|
contractsModule?: string;
|
|
45
|
+
/** Page source path to the concrete URL that renders it, for a route the
|
|
46
|
+
* `pages` object cannot express: one served by `resolve()`, or one whose
|
|
47
|
+
* parameters only the project knows. Page paths are relative to the
|
|
48
|
+
* project root. */
|
|
49
|
+
pageRoutes?: Record<string, string>;
|
|
50
|
+
/** Replaces the two sizes every page rule runs at. */
|
|
51
|
+
pageViewports?: PageViewport[];
|
|
34
52
|
}
|
|
35
53
|
interface ResolvedTestingConfig {
|
|
36
54
|
root: string;
|
|
@@ -41,6 +59,8 @@ interface ResolvedTestingConfig {
|
|
|
41
59
|
dataDir: string;
|
|
42
60
|
registrySetup?: string;
|
|
43
61
|
contractsModule?: string;
|
|
62
|
+
pageRoutes: Record<string, string>;
|
|
63
|
+
pageViewports: PageViewport[];
|
|
44
64
|
}
|
|
45
65
|
/** Types the settings file without importing the interface by hand. */
|
|
46
66
|
declare function defineTestingConfig(config: LiveTokensTestingConfig): LiveTokensTestingConfig;
|
|
@@ -70,4 +90,4 @@ interface VitestConfigOptions {
|
|
|
70
90
|
*/
|
|
71
91
|
declare function createVitestConfig(viteConfig: UserConfig, options?: VitestConfigOptions): UserConfig;
|
|
72
92
|
|
|
73
|
-
export { COMPONENTS_PATH_ENV as C, type LiveTokensTestingConfig as L, type ResolvedTestingConfig as R, TESTING_CONFIG_FILE as T, type VitestConfigOptions as V, COMPONENT_ENV as a,
|
|
93
|
+
export { COMPONENTS_PATH_ENV as C, DEFAULT_PAGE_VIEWPORTS as D, type LiveTokensTestingConfig as L, PAGES_ENV as P, type ResolvedTestingConfig as R, TESTING_CONFIG_FILE as T, type VitestConfigOptions as V, COMPONENT_ENV as a, PAGE_VIEWPORTS_ENV as b, type PageViewport as c, createVitestConfig as d, defineTestingConfig as e, devServerCommand as f, resolveTestingConfig as r };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'vite';
|
|
2
2
|
import 'vitest/config';
|
|
3
|
-
export { L as LiveTokensTestingConfig, R as ResolvedTestingConfig, V as VitestConfigOptions,
|
|
3
|
+
export { L as LiveTokensTestingConfig, R as ResolvedTestingConfig, V as VitestConfigOptions, d as createVitestConfig, e as defineTestingConfig, r as resolveTestingConfig } from './vitest-C-wNWcoA.js';
|
package/src/testing-js/vitest.js
CHANGED
package/template/README.md
CHANGED
|
@@ -41,20 +41,27 @@ your own editable component, install the Claude Code skills:
|
|
|
41
41
|
npx @motion-proto/live-tokens setup-claude
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
## Testing a component
|
|
44
|
+
## Testing a component or a page
|
|
45
45
|
|
|
46
|
-
`npm run test:design` runs `check-component --tests
|
|
47
|
-
the registry contract
|
|
48
|
-
|
|
49
|
-
a one-time
|
|
46
|
+
`npm run test:design` runs `check-component --tests` and `check-page --tests`:
|
|
47
|
+
the static checks plus the registry contract, the component contract suites,
|
|
48
|
+
and a page's own rendered output, in a browser, against this project.
|
|
49
|
+
Playwright, Vitest, and happy-dom are installed; the browser is a one-time
|
|
50
|
+
download:
|
|
50
51
|
|
|
51
52
|
```bash
|
|
52
53
|
npx playwright install chromium
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
`live-tokens.testing.ts` names the two files the run reads:
|
|
56
|
-
registers your components, and `tests/contracts.ts`
|
|
57
|
-
component, which is what the browser suites drive. The
|
|
58
|
-
writes both.
|
|
56
|
+
`live-tokens.testing.ts` names the two files the component run reads:
|
|
57
|
+
`src/registerComponents.ts` registers your components, and `tests/contracts.ts`
|
|
58
|
+
holds one contract per component, which is what the browser suites drive. The
|
|
59
|
+
create-component skill writes both.
|
|
60
|
+
|
|
61
|
+
`check-page --tests` needs nothing declared: it opens each page named in
|
|
62
|
+
`src/App.svelte`'s `pages` object at its own route, and proves that every
|
|
63
|
+
shipped component instance paints from its semantic properties, every run of
|
|
64
|
+
text sits in one shipped text style, every text and surface pair meets AA,
|
|
65
|
+
sections sit on the page grid, and nothing overflows.
|
|
59
66
|
|
|
60
67
|
A failing run leaves its report under `test-results/`, gitignored.
|
package/template/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"preview": "vite preview",
|
|
10
10
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
11
|
"check:design": "live-tokens check-page && live-tokens check-component",
|
|
12
|
-
"test:design": "live-tokens check-component --tests",
|
|
12
|
+
"test:design": "live-tokens check-component --tests && live-tokens check-page --tests",
|
|
13
13
|
"postinstall": "live-tokens setup-claude --force || exit 0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
@@ -57,4 +57,17 @@
|
|
|
57
57
|
flex-wrap: wrap;
|
|
58
58
|
margin-top: var(--space-20);
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
/* The theme's own column gutter alone exceeds a phone's width at 12
|
|
62
|
+
columns, so every page has to collapse to one column below 768px. */
|
|
63
|
+
@media (max-width: 767px) {
|
|
64
|
+
.home {
|
|
65
|
+
grid-template-columns: 1fr;
|
|
66
|
+
column-gap: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.stub {
|
|
70
|
+
grid-column: 1 / -1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
60
73
|
</style>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../testing/config.ts","../editor/core/routing/ownedRoutes.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { DEFAULT_COMPONENTS_PATH } from '../editor/core/routing/ownedRoutes';\n\n/** The file a project puts its shared test settings in, at the project root. */\nexport const TESTING_CONFIG_FILE = 'live-tokens.testing.ts';\n\n/** The components route the run drives, published from the settings so the\n * suites need no argument. */\nexport const COMPONENTS_PATH_ENV = 'LIVE_TOKENS_COMPONENTS_PATH';\n\n/** Narrows a run to one component. */\nexport const COMPONENT_ENV = 'LIVE_TOKENS_COMPONENT';\n\n/** What `resolveDataDirs` falls back to, restated because that resolver is\n * plugin-side and the tarball ships the plugin built rather than as source.\n * `vite-plugin/testingConfig.test.ts` pins the two together. */\nconst DEFAULT_DATA_DIR = 'src/live-tokens/data';\n\nconst DEFAULT_DEV_COMMAND = 'npm run dev -- --host {host} --port {port}';\n\nexport interface LiveTokensTestingConfig {\n /** Vite config a generated Vitest config merges. Default `vite.config.ts`. */\n viteConfig?: string;\n /** Command that starts the dev server. `{host}` and `{port}` are substituted.\n * Default `npm run dev -- --host {host} --port {port}`. */\n devCommand?: string;\n /** Fixed port for the dev server. Omitted, the run takes a free one. */\n port?: number;\n /** The components route, when `editorRoutes` moved it off the owned default. */\n componentsPath?: string;\n /** The plugin's data directory. Default: `dataDir` from\n * `live-tokens.config.json`, else `src/live-tokens/data`. The run never uses\n * it directly; it is the tree the isolated copy is made from. */\n dataDir?: string;\n /** Module that registers the project's components without mounting the app.\n * The registry contract imports it before it selects entries. */\n registrySetup?: string;\n /** Module exporting the `ComponentContract[]` a custom component adds to the\n * shipped list (default export or a named `contracts` export). The suite\n * files are static Playwright entry points, so an env var naming this path\n * plus a dynamic import inside `selectedContracts()` is the only way a\n * custom contract reaches them. */\n contractsModule?: string;\n}\n\nexport interface ResolvedTestingConfig {\n root: string;\n viteConfig: string;\n devCommand: string;\n port?: number;\n componentsPath: string;\n dataDir: string;\n registrySetup?: string;\n contractsModule?: string;\n}\n\n/** Types the settings file without importing the interface by hand. */\nexport function defineTestingConfig(config: LiveTokensTestingConfig): LiveTokensTestingConfig {\n return config;\n}\n\n/** `dataDir` as the plugin resolves it, from the same file the plugin reads. */\nfunction configuredDataDir(root: string): string {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(root, 'live-tokens.config.json'), 'utf-8'));\n if (parsed && typeof parsed === 'object' && typeof parsed.dataDir === 'string') {\n return path.resolve(root, parsed.dataDir);\n }\n } catch {\n // Missing or unparseable reads as absent, matching `readLiveTokensConfig`.\n }\n return path.resolve(root, DEFAULT_DATA_DIR);\n}\n\nexport function resolveTestingConfig(\n config: LiveTokensTestingConfig = {},\n root: string = process.cwd(),\n): ResolvedTestingConfig {\n const projectRoot = path.resolve(root);\n return {\n root: projectRoot,\n viteConfig: path.resolve(projectRoot, config.viteConfig ?? 'vite.config.ts'),\n devCommand: config.devCommand ?? DEFAULT_DEV_COMMAND,\n port: config.port,\n componentsPath: config.componentsPath ?? DEFAULT_COMPONENTS_PATH,\n dataDir: config.dataDir ? path.resolve(projectRoot, config.dataDir) : configuredDataDir(projectRoot),\n registrySetup: config.registrySetup\n ? path.resolve(projectRoot, config.registrySetup)\n : undefined,\n contractsModule: config.contractsModule\n ? path.resolve(projectRoot, config.contractsModule)\n : undefined,\n };\n}\n\nexport function devServerCommand(command: string, host: string, port: number): string {\n return command.replaceAll('{host}', host).replaceAll('{port}', String(port));\n}\n","// The package's dev-only editor surfaces live under a reserved `/live-tokens/*`\n// namespace so they can never collide with a consumer's own routes — the\n// consumer owns the rest of the URL space. Keeping the defaults in one place\n// is the guard against the nav rail and the dispatcher drifting onto different\n// paths (the collision/shadow class these constants exist to prevent).\n//\n// A consumer relocates or disables each surface via the `editorRoutes` prop on\n// <LiveTokensRouter>; these are only the defaults.\nexport const DEFAULT_EDITOR_PATH = '/live-tokens/editor';\nexport const DEFAULT_COMPONENTS_PATH = '/live-tokens/components';\nexport const DEFAULT_COLORS_PATH = '/live-tokens/colors';\nexport const DEFAULT_DOCS_PATH = '/live-tokens/docs';\n"],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;;;ACQV,IAAM,0BAA0B;;;ADJhC,IAAM,sBAAsB;AAI5B,IAAM,sBAAsB;AAG5B,IAAM,gBAAgB;AAK7B,IAAM,mBAAmB;AAEzB,IAAM,sBAAsB;AAuCrB,SAAS,oBAAoB,QAA0D;AAC5F,SAAO;AACT;AAGA,SAAS,kBAAkB,MAAsB;AAC/C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG,aAAa,KAAK,KAAK,MAAM,yBAAyB,GAAG,OAAO,CAAC;AAC9F,QAAI,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,YAAY,UAAU;AAC9E,aAAO,KAAK,QAAQ,MAAM,OAAO,OAAO;AAAA,IAC1C;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,KAAK,QAAQ,MAAM,gBAAgB;AAC5C;AAEO,SAAS,qBACd,SAAkC,CAAC,GACnC,OAAe,QAAQ,IAAI,GACJ;AACvB,QAAM,cAAc,KAAK,QAAQ,IAAI;AACrC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY,KAAK,QAAQ,aAAa,OAAO,cAAc,gBAAgB;AAAA,IAC3E,YAAY,OAAO,cAAc;AAAA,IACjC,MAAM,OAAO;AAAA,IACb,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,SAAS,OAAO,UAAU,KAAK,QAAQ,aAAa,OAAO,OAAO,IAAI,kBAAkB,WAAW;AAAA,IACnG,eAAe,OAAO,gBAClB,KAAK,QAAQ,aAAa,OAAO,aAAa,IAC9C;AAAA,IACJ,iBAAiB,OAAO,kBACpB,KAAK,QAAQ,aAAa,OAAO,eAAe,IAChD;AAAA,EACN;AACF;AAEO,SAAS,iBAAiB,SAAiB,MAAc,MAAsB;AACpF,SAAO,QAAQ,WAAW,UAAU,IAAI,EAAE,WAAW,UAAU,OAAO,IAAI,CAAC;AAC7E;","names":[]}
|
|
File without changes
|
|
File without changes
|