@motion-proto/live-tokens 0.77.0 → 0.79.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 +60 -29
- package/.claude/skills/live-tokens-create-component/SKILL.md +23 -28
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +61 -0
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +6 -15
- package/.claude/skills/live-tokens-create-page/SKILL.md +9 -6
- package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
- package/.claude/skills/live-tokens-pick-component/SKILL.md +3 -3
- package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +1 -1
- package/.claude/skills/live-tokens-set-geometry/SKILL.md +1 -1
- package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +1 -1
- package/CHANGELOG.md +284 -0
- package/README.md +9 -15
- package/bin/check-component.mjs +152 -551
- package/bin/check-page.mjs +56 -420
- package/bin/cli.mjs +83 -15
- package/bin/contractRunner.mjs +380 -120
- package/bin/lib/catalogue.mjs +161 -34
- package/bin/lib/componentSource.mjs +152 -0
- package/bin/lib/cssValues.mjs +9 -0
- package/bin/lib/dataDir.mjs +126 -0
- package/bin/lib/findings.mjs +103 -12
- package/bin/lib/fixers.mjs +64 -0
- package/bin/lib/geometry.mjs +92 -0
- package/bin/lib/pageRoutes.mjs +309 -0
- package/bin/lib/pageSource.mjs +230 -0
- package/bin/lib/report.mjs +57 -59
- package/bin/lib/tokenVocabulary.mjs +104 -34
- package/bin/rules/componentStructure.mjs +344 -0
- package/bin/rules/componentUse.mjs +313 -0
- package/bin/rules/importsAndRoutes.mjs +136 -0
- package/bin/rules/testRuns.mjs +122 -0
- package/bin/rules/tokens.mjs +363 -0
- package/bin/setup-claude.mjs +1 -2
- package/dist-plugin/{chunk-PDNL4NC5.js → chunk-D4WRIKEZ.js} +7 -2
- package/dist-plugin/{chunk-SWXRVZKT.js → chunk-REBHE3ZM.js} +414 -1
- package/dist-plugin/index.cjs +432 -15
- package/dist-plugin/index.js +8 -9
- package/dist-plugin/migrateData/index.cjs +422 -4
- package/dist-plugin/migrateData/index.js +2 -2
- package/dist-plugin/setColors/index.cjs +414 -1
- package/dist-plugin/setColors/index.d.cts +1 -1
- package/dist-plugin/setColors/index.d.ts +1 -1
- package/dist-plugin/setColors/index.js +1 -1
- package/dist-plugin/setGeometry/index.cjs +423 -13
- package/dist-plugin/setGeometry/index.d.cts +3 -3
- package/dist-plugin/setGeometry/index.d.ts +3 -3
- package/dist-plugin/setGeometry/index.js +10 -13
- package/dist-plugin/setType/index.d.cts +1 -1
- package/dist-plugin/setType/index.d.ts +1 -1
- package/dist-plugin/{themeTypes-BxRtuN5V.d.cts → themeTypes-B8_Idrp4.d.cts} +2 -2
- package/dist-plugin/{themeTypes-BxRtuN5V.d.ts → themeTypes-B8_Idrp4.d.ts} +2 -2
- package/package.json +6 -4
- package/src/editor/component-editor/CalloutEditor.svelte +2 -2
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +14 -15
- package/src/editor/component-editor/CornerBadgeEditor.svelte +14 -14
- package/src/editor/component-editor/DialogEditor.svelte +5 -5
- package/src/editor/component-editor/InlineEditActionsEditor.svelte +2 -2
- package/src/editor/component-editor/RadioButtonEditor.svelte +21 -21
- package/src/editor/component-editor/SectionDividerEditor.svelte +7 -7
- package/src/editor/component-editor/SegmentedControlEditor.svelte +5 -5
- package/src/editor/component-editor/SideNavigationEditor.svelte +25 -25
- package/src/editor/component-editor/TabBarEditor.svelte +6 -6
- package/src/editor/component-editor/TableEditor.svelte +6 -6
- package/src/editor/component-editor/ToggleEditor.svelte +2 -2
- package/src/editor/component-editor/index.ts +3 -0
- package/src/editor/component-editor/registry.ts +57 -1
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +14 -14
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +3 -0
- package/src/editor/component-editor/scaffolding/types.ts +15 -0
- package/src/editor/core/components/adjustAliases.ts +4 -4
- package/src/editor/core/components/aliasKinds.ts +20 -19
- package/src/editor/core/sketch/sketchLayer.ts +3 -3
- package/src/editor/core/themes/migrateComponentConfig.ts +14 -6
- package/src/editor/core/themes/migrations/2026-09-13-badge-brand.ts +42 -0
- package/src/editor/core/themes/migrations/2026-09-13-collapsiblesection-open.ts +33 -0
- package/src/editor/core/themes/migrations/2026-09-13-cornerbadge-prefix.ts +70 -0
- package/src/editor/core/themes/migrations/2026-09-13-hairline.ts +92 -0
- package/src/editor/core/themes/migrations/2026-09-13-indicator.ts +54 -0
- package/src/editor/core/themes/migrations/2026-09-13-sectiondivider-surface.ts +36 -0
- package/src/editor/core/themes/migrations/2026-09-13-selected-state.ts +124 -0
- package/src/editor/core/themes/migrations/2026-09-13-toggle-label.ts +31 -0
- package/src/editor/core/themes/migrations/index.ts +16 -0
- package/src/editor/core/themes/themeService.ts +3 -3
- package/src/editor/core/themes/themeTypes.ts +13 -15
- package/src/editor/docs/Docs.svelte +1 -1
- package/src/editor/docs/content/light-and-dark.md +3 -3
- package/src/editor/docs/content.generated.ts +1 -1
- package/src/editor/index.ts +1 -0
- package/src/editor/overlay/ColumnsOverlay.svelte +1 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +16 -0
- package/src/editor/overlay/LiveTokensRouter.svelte +1 -0
- package/src/editor/skill-atlas/SkillAtlas.svelte +4 -4
- package/src/editor/skill-atlas/TreeNodeCard.svelte +4 -4
- package/src/editor/skill-atlas/skillSources.generated.ts +11 -14
- package/src/editor/skill-atlas/skillTrees.ts +1 -3
- package/src/editor/skill-atlas/trees/check-compliance.ts +258 -93
- package/src/editor/skill-atlas/trees/create-component.ts +73 -62
- package/src/editor/skill-atlas/trees/create-page.ts +54 -33
- package/src/editor/skill-atlas/trees/pick-component.ts +3 -3
- package/src/editor/skill-atlas/trees/set-geometry.ts +1 -1
- package/src/editor/ui/UIPaletteSelector.svelte +11 -15
- package/src/editor/ui/variantScales.ts +8 -8
- package/src/live-tokens/data/themes/autumn.json +188 -188
- package/src/live-tokens/data/themes/halloween.json +188 -188
- package/src/live-tokens/data/themes/midnight-study.json +245 -245
- package/src/live-tokens/data/themes/ocean.json +188 -188
- package/src/live-tokens/data/themes/royal-velvet.json +188 -188
- package/src/live-tokens/data/themes/sketchy.json +188 -188
- package/src/live-tokens/data/themes/spring-meadow.json +188 -188
- package/src/live-tokens/data/themes/sunset.json +188 -188
- package/src/system/components/Badge.svelte +27 -23
- package/src/system/components/Button.svelte +13 -7
- package/src/system/components/Callout.svelte +16 -11
- package/src/system/components/Card.svelte +22 -15
- package/src/system/components/CodeSnippet.svelte +10 -6
- package/src/system/components/CollapsibleSection.svelte +68 -63
- package/src/system/components/CornerBadge.svelte +78 -72
- package/src/system/components/Dialog.svelte +21 -18
- package/src/system/components/IconButton.svelte +13 -9
- package/src/system/components/Image.svelte +14 -8
- package/src/system/components/ImageLightbox.svelte +10 -6
- package/src/system/components/InlineEditActions.svelte +17 -14
- package/src/system/components/Input.svelte +13 -7
- package/src/system/components/MenuSelect.svelte +13 -7
- package/src/system/components/Notification.svelte +17 -11
- package/src/system/components/Panel.svelte +13 -6
- package/src/system/components/ProgressBar.svelte +10 -5
- package/src/system/components/RadioButton.svelte +33 -30
- package/src/system/components/SectionDivider.svelte +28 -21
- package/src/system/components/SegmentedControl.svelte +27 -23
- package/src/system/components/SideNavigation.svelte +175 -171
- package/src/system/components/Slider.svelte +11 -7
- package/src/system/components/TabBar.svelte +53 -49
- package/src/system/components/Table.svelte +20 -15
- package/src/system/components/Toggle.svelte +14 -10
- package/src/system/components/Tooltip.svelte +10 -6
- package/src/system/styles/CONVENTIONS.md +3 -4
- package/src/testing-js/chunk-3UKGXCDL.js +48 -0
- package/src/testing-js/chunk-3UKGXCDL.js.map +1 -0
- 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-FAFOAWYL.js → chunk-Q3YIAAG3.js} +17 -3
- package/src/testing-js/chunk-Q3YIAAG3.js.map +1 -0
- package/src/testing-js/{chunk-7TI7Z6Y6.js → chunk-U7OJE5DU.js} +19 -19
- package/src/testing-js/chunk-U7OJE5DU.js.map +1 -0
- package/src/testing-js/{chunk-L73N4NSO.js → chunk-WIZ6W7UT.js} +2 -2
- package/src/testing-js/{chunk-4JQX6WWL.js → chunk-ZMZQZ33J.js} +474 -166
- package/src/testing-js/chunk-ZMZQZ33J.js.map +1 -0
- package/src/testing-js/component-alias.contract.js +2 -2
- package/src/testing-js/component-behavior.contract.js +155 -0
- package/src/testing-js/component-behavior.contract.js.map +1 -0
- package/src/testing-js/component-editor.contract.js +7 -5
- package/src/testing-js/component-editor.contract.js.map +1 -1
- package/src/testing-js/component-render.contract.js +15 -11
- package/src/testing-js/component-render.contract.js.map +1 -1
- package/src/testing-js/index.d.ts +50 -4
- package/src/testing-js/index.js +32 -8
- package/src/testing-js/index.js.map +1 -1
- package/src/testing-js/page-compliance.contract.js +867 -0
- package/src/testing-js/page-compliance.contract.js.map +1 -0
- package/src/testing-js/registry.contract.js +5 -3
- package/src/testing-js/registry.contract.js.map +1 -1
- package/src/testing-js/{vitest-BE6uGF31.d.ts → vitest-BMLIbDs2.d.ts} +29 -3
- package/src/testing-js/vitest.d.ts +1 -1
- package/src/testing-js/vitest.js +3 -2
- package/template/README.md +16 -9
- package/template/package.json +2 -2
- package/template/src/pages/Home.svelte +13 -0
- package/.claude/skills/live-tokens-create-component/SKILL copy.md +0 -196
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +0 -98
- package/src/editor/skill-atlas/trees/fix-findings.ts +0 -469
- package/src/testing-js/chunk-4JQX6WWL.js.map +0 -1
- package/src/testing-js/chunk-7TI7Z6Y6.js.map +0 -1
- package/src/testing-js/chunk-FAFOAWYL.js.map +0 -1
- package/src/testing-js/chunk-ZMSX6CXR.js.map +0 -1
- /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-brand`), and\n * axis-prefixed (`es-root variant-hairline`). A contract's variant label is the\n * editor's prose (`With Hairline`), so each of its words is a key as well as\n * the whole label, which is what reaches the `hairline` 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/** How long the page has to hold still before a rule measures it, and how\n * long to wait for that before measuring anyway. */\nconst SETTLE_QUIET_MS = 300;\nconst SETTLE_BUDGET_MS = 5_000;\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: until it mounts, the container holds the dev\n // overlay and nothing of the page itself.\n await page.waitForFunction((selectors) => {\n const container = document.querySelector(selectors.container);\n if (!container) return false;\n return [...container.children].some((child) => !child.matches(selectors.chrome));\n }, { container: PAGE_CONTAINER, chrome: CHROME });\n await page.waitForLoadState('networkidle');\n await PageHarness.settle(page);\n return new PageHarness(page, target, viewport);\n }\n\n /**\n * Every rule here measures geometry, and the geometry moves for as long as\n * the theme's own values, the route's stylesheet, and the fonts are still\n * arriving. A twelve-column grid inside 32px of padding reaches 384px at a\n * 390px viewport with the theme's 32px `--columns-gutter`, and 120px with\n * the 8px gutter `tokens.css` computes there on its own, so the same page\n * reads as overflowing or not depending on what has landed. Waiting on a single custom property settles nothing:\n * `--body-md-font-size`, the property this gate used to read, is one\n * `tokens.css` sets itself, and a trace of the boot has it true 84ms before\n * the page container exists at all. What a rule needs instead is a page that\n * has stopped moving. The budget bounds a page that never does.\n */\n private static async settle(page: Page): Promise<void> {\n await page.evaluate(async ({ quietMs, budgetMs }) => {\n await document.fonts.ready;\n const root = document.documentElement;\n // `root.style.length` counts the custom properties the dev-time theme\n // projection writes inline, which land without moving anything.\n const signature = () => [\n root.scrollWidth, root.scrollHeight, root.clientWidth, root.clientHeight,\n root.style.length, document.fonts.size, document.fonts.status,\n ].join('|');\n const frame = () => new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));\n const deadline = performance.now() + budgetMs;\n let last = signature();\n let quietSince = performance.now();\n while (performance.now() < deadline) {\n await frame();\n const now = signature();\n if (now !== last) {\n last = now;\n quietSince = performance.now();\n continue;\n }\n if (performance.now() - quietSince >= quietMs) return;\n }\n }, { quietMs: SETTLE_QUIET_MS, budgetMs: SETTLE_BUDGET_MS });\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"]}
|
|
@@ -25,9 +25,11 @@ describe("component registry contract", () => {
|
|
|
25
25
|
requested ? `no component is registered under the id "${requested}"` : "no component is registered"
|
|
26
26
|
).not.toEqual([]);
|
|
27
27
|
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
describe.each(authored.map((id) => [id]))("%s", (id) => {
|
|
29
|
+
it("is registered", () => {
|
|
30
|
+
const ids = new Set(entries.map((entry) => entry.id));
|
|
31
|
+
expect(ids.has(id), `${id} is authored in the project and registered nowhere`).toBe(true);
|
|
32
|
+
});
|
|
31
33
|
});
|
|
32
34
|
describe.each(targets.map((entry) => [entry.id, entry]))("%s", (_id, entry) => {
|
|
33
35
|
it("meets the registry contract", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../testing/registry.contract.ts"],"sourcesContent":["// @vitest-environment happy-dom\n/**\n * The registry contract, run against the project's own registrations.\n *\n * The setup module is what puts a consumer's components in the registry.\n * Importing an editor does not register it, and importing the app's entry point\n * would mount the app, so the project names a module that registers and stops\n * there. Without one, the run covers the components the package registers\n * itself.\n */\nimport path from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { describe, expect, it } from 'vitest';\nimport { checkRegistryEntry } from '../editor/component-editor/contract';\nimport { getComponentRegistryEntries } from '../editor/component-editor/registry';\n// @ts-expect-error — plain .mjs module, no types\nimport { discoverComponents } from '../../bin/check-component.mjs';\n\nconst REGISTRY_SETUP_ENV = 'LIVE_TOKENS_REGISTRY_SETUP';\n\nconst setupModule = process.env[REGISTRY_SETUP_ENV];\nif (setupModule) await import(pathToFileURL(path.resolve(setupModule)).href);\n\n/** `<package>/src/testing/` is this file's directory in the repository and in\n * an installed copy alike, so the package root is two levels up. */\nconst packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');\nconst consumerRoot = process.cwd();\nconst componentConfigsDir = path.resolve(\n process.env.LIVE_TOKENS_DATA_DIR ?? 'src/live-tokens/data',\n 'component-configs',\n);\n\nconst entries = getComponentRegistryEntries();\nconst requested = process.env.LIVE_TOKENS_COMPONENT;\nconst targets = requested ? entries.filter((entry) => entry.id === requested) : entries;\n\n/** Ids authored in this project. `discoverComponents` reports the runtime and\n * editor pair on disk, which is a different question from what the registry\n * holds: a component can exist as files and never be registered. */\nconst authored: string[] = discoverComponents(consumerRoot);\n\ndescribe('component registry contract', () => {\n it('selects at least one component', () => {\n expect(\n targets.map((entry) => entry.id),\n requested\n ? `no component is registered under the id \"${requested}\"`\n : 'no component is registered',\n ).not.toEqual([]);\n });\n\n
|
|
1
|
+
{"version":3,"sources":["../testing/registry.contract.ts"],"sourcesContent":["// @vitest-environment happy-dom\n/**\n * The registry contract, run against the project's own registrations.\n *\n * The setup module is what puts a consumer's components in the registry.\n * Importing an editor does not register it, and importing the app's entry point\n * would mount the app, so the project names a module that registers and stops\n * there. Without one, the run covers the components the package registers\n * itself.\n */\nimport path from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { describe, expect, it } from 'vitest';\nimport { checkRegistryEntry } from '../editor/component-editor/contract';\nimport { getComponentRegistryEntries } from '../editor/component-editor/registry';\n// @ts-expect-error — plain .mjs module, no types\nimport { discoverComponents } from '../../bin/check-component.mjs';\n\nconst REGISTRY_SETUP_ENV = 'LIVE_TOKENS_REGISTRY_SETUP';\n\nconst setupModule = process.env[REGISTRY_SETUP_ENV];\nif (setupModule) await import(pathToFileURL(path.resolve(setupModule)).href);\n\n/** `<package>/src/testing/` is this file's directory in the repository and in\n * an installed copy alike, so the package root is two levels up. */\nconst packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');\nconst consumerRoot = process.cwd();\nconst componentConfigsDir = path.resolve(\n process.env.LIVE_TOKENS_DATA_DIR ?? 'src/live-tokens/data',\n 'component-configs',\n);\n\nconst entries = getComponentRegistryEntries();\nconst requested = process.env.LIVE_TOKENS_COMPONENT;\nconst targets = requested ? entries.filter((entry) => entry.id === requested) : entries;\n\n/** Ids authored in this project. `discoverComponents` reports the runtime and\n * editor pair on disk, which is a different question from what the registry\n * holds: a component can exist as files and never be registered. */\nconst authored: string[] = discoverComponents(consumerRoot);\n\ndescribe('component registry contract', () => {\n it('selects at least one component', () => {\n expect(\n targets.map((entry) => entry.id),\n requested\n ? `no component is registered under the id \"${requested}\"`\n : 'no component is registered',\n ).not.toEqual([]);\n });\n\n // One assertion per authored id, not one over the whole list: a failure that\n // names no component reaches the CLI as `tests-setup`, which means the\n // tooling never ran, and buries the id that is actually missing.\n describe.each(authored.map((id) => [id] as const))('%s', (id) => {\n it('is registered', () => {\n const ids = new Set(entries.map((entry) => entry.id));\n expect(ids.has(id), `${id} is authored in the project and registered nowhere`).toBe(true);\n });\n });\n\n describe.each(targets.map((entry) => [entry.id, entry] as const))('%s', (_id, entry) => {\n it('meets the registry contract', () => {\n // A shipped component's `sourceFile` is relative to the package; a\n // consumer's is relative to their own project.\n const projectRoot = entry.origin === 'system' ? packageRoot : consumerRoot;\n expect(checkRegistryEntry(entry, { projectRoot, componentConfigsDir })).toEqual([]);\n });\n });\n});\n"],"mappings":";AAUA,OAAO,UAAU;AACjB,SAAS,eAAe,qBAAqB;AAC7C,SAAS,UAAU,QAAQ,UAAU;AACrC,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAE5C,SAAS,0BAA0B;AAEnC,IAAM,qBAAqB;AAE3B,IAAM,cAAc,QAAQ,IAAI,kBAAkB;AAClD,IAAI,YAAa,OAAM,OAAO,cAAc,KAAK,QAAQ,WAAW,CAAC,EAAE;AAIvE,IAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,MAAM,IAAI;AACzF,IAAM,eAAe,QAAQ,IAAI;AACjC,IAAM,sBAAsB,KAAK;AAAA,EAC/B,QAAQ,IAAI,wBAAwB;AAAA,EACpC;AACF;AAEA,IAAM,UAAU,4BAA4B;AAC5C,IAAM,YAAY,QAAQ,IAAI;AAC9B,IAAM,UAAU,YAAY,QAAQ,OAAO,CAAC,UAAU,MAAM,OAAO,SAAS,IAAI;AAKhF,IAAM,WAAqB,mBAAmB,YAAY;AAE1D,SAAS,+BAA+B,MAAM;AAC5C,KAAG,kCAAkC,MAAM;AACzC;AAAA,MACE,QAAQ,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,MAC/B,YACI,4CAA4C,SAAS,MACrD;AAAA,IACN,EAAE,IAAI,QAAQ,CAAC,CAAC;AAAA,EAClB,CAAC;AAKD,WAAS,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,EAAE,CAAU,CAAC,EAAE,MAAM,CAAC,OAAO;AAC/D,OAAG,iBAAiB,MAAM;AACxB,YAAM,MAAM,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,EAAE,CAAC;AACpD,aAAO,IAAI,IAAI,EAAE,GAAG,GAAG,EAAE,oDAAoD,EAAE,KAAK,IAAI;AAAA,IAC1F,CAAC;AAAA,EACH,CAAC;AAED,WAAS,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAU,CAAC,EAAE,MAAM,CAAC,KAAK,UAAU;AACtF,OAAG,+BAA+B,MAAM;AAGtC,YAAM,cAAc,MAAM,WAAW,WAAW,cAAc;AAC9D,aAAO,mBAAmB,OAAO,EAAE,aAAa,oBAAoB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,IACpF,CAAC;AAAA,EACH,CAAC;AACH,CAAC;","names":[]}
|
|
@@ -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;
|
|
@@ -48,8 +68,8 @@ declare function resolveTestingConfig(config?: LiveTokensTestingConfig, root?: s
|
|
|
48
68
|
declare function devServerCommand(command: string, host: string, port: number): string;
|
|
49
69
|
|
|
50
70
|
interface VitestConfigOptions {
|
|
51
|
-
/** Files the run collects. Default: the shipped registry
|
|
52
|
-
* both in this package and in an installed copy
|
|
71
|
+
/** Files the run collects. Default: the shipped registry and behavior
|
|
72
|
+
* contracts, matched both in this package and in an installed copy. */
|
|
53
73
|
include?: string[];
|
|
54
74
|
exclude?: string[];
|
|
55
75
|
setupFiles?: string[];
|
|
@@ -59,6 +79,12 @@ interface VitestConfigOptions {
|
|
|
59
79
|
* components. Reaches the run as an environment variable because the
|
|
60
80
|
* contract file resolves the path itself. */
|
|
61
81
|
registrySetup?: string;
|
|
82
|
+
/** Module `selectedContracts()` imports a project's own `ComponentContract[]`
|
|
83
|
+
* from, same env-var seam as `registrySetup` and mirroring
|
|
84
|
+
* `createPlaywrightConfig`'s own use of it: the behavior contract's
|
|
85
|
+
* `describe.each` runs at module top, before a test can resolve anything
|
|
86
|
+
* for it. */
|
|
87
|
+
contractsModule?: string;
|
|
62
88
|
}
|
|
63
89
|
/**
|
|
64
90
|
* The project's Vite config plus what the registry contract needs on top.
|
|
@@ -70,4 +96,4 @@ interface VitestConfigOptions {
|
|
|
70
96
|
*/
|
|
71
97
|
declare function createVitestConfig(viteConfig: UserConfig, options?: VitestConfigOptions): UserConfig;
|
|
72
98
|
|
|
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,
|
|
99
|
+
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-BMLIbDs2.js';
|
package/src/testing-js/vitest.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVitestConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Q3YIAAG3.js";
|
|
4
4
|
import {
|
|
5
5
|
defineTestingConfig,
|
|
6
6
|
resolveTestingConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NB3NZRBM.js";
|
|
8
|
+
import "./chunk-ZMZQZ33J.js";
|
|
8
9
|
export {
|
|
9
10
|
createVitestConfig,
|
|
10
11
|
defineTestingConfig,
|
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
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"build": "npm run check:design && vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
10
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
-
"check:design": "live-tokens check-page && live-tokens check-component",
|
|
12
|
-
"test:design": "live-tokens check-component --tests",
|
|
11
|
+
"check:design": "live-tokens check-page --no-fix && live-tokens check-component --no-fix",
|
|
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,196 +0,0 @@
|
|
|
1
|
-
app---
|
|
2
|
-
name: live-tokens-create-component
|
|
3
|
-
description: Create a custom Svelte component in a @motion-proto/live-tokens project with semantic properties that reference the existing theme tokens, a live editor, and registration. Use for a new component or to make an existing component editable. Use live-tokens-create-page to place existing components on a page.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Creating a Live Tokens component
|
|
7
|
-
|
|
8
|
-
Create a component whose structure and behavior serve the user's purpose. Give each editable visual property a semantic name and assign its default from the design system's existing token vocabulary. Deliver the runtime component, its editor, and its registration together.
|
|
9
|
-
|
|
10
|
-
## Design model
|
|
11
|
-
|
|
12
|
-
Live Tokens has two token layers:
|
|
13
|
-
|
|
14
|
-
| Layer | Responsibility | Example |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| Theme tokens | Define the available colors, typography, geometry, and motion values. Theme presets change these values. | `--space-16`, `--radius-md`, `--surface-neutral` |
|
|
17
|
-
| Component properties | Name the visual roles within a component and reference theme tokens. Component presets record these assignments. | `--statcard-frame-padding: var(--space-16)` |
|
|
18
|
-
|
|
19
|
-
The rendering chain is **theme token → semantic component property → CSS declaration**. The editor changes the assignment; the runtime reads the property. Keep the assignment as a token reference so a theme change reaches the component.
|
|
20
|
-
|
|
21
|
-
A property describes its purpose: `--statcard-value-text`, `--statcard-frame-radius`, `--statcard-label-font-size`. Its name stays stable when its assigned color or size changes. Use role names such as `surface` and `text`, and full words for component ids and parts.
|
|
22
|
-
|
|
23
|
-
Build distinct components through their anatomy, proportions, content hierarchy, behavior, and choice of existing tokens. Create only the variants and states the task requires. Keep the theme vocabulary intact during component creation; a new theme token belongs in a separate theme change.
|
|
24
|
-
|
|
25
|
-
Component props carry content and behavior, such as `value`, `label`, `selected`, and callbacks. Semantic CSS properties carry the editable appearance. Expose variants through props when they represent meaningful component choices.
|
|
26
|
-
|
|
27
|
-
## Source inspection
|
|
28
|
-
|
|
29
|
-
Read the project's `package.json`, `live-tokens.config.json`, and application bootstrap before writing files. Run `npx live-tokens components` to inspect the catalogue and `npx live-tokens components <id>` for a candidate's props. Use an existing component when it fulfills the request; create a new component when the request needs distinct structure or behavior.
|
|
30
|
-
|
|
31
|
-
Locate the installed package at `node_modules/@motion-proto/live-tokens`. When working inside the Live Tokens repository, use the repository root instead. Read these sources from that root:
|
|
32
|
-
|
|
33
|
-
- `src/system/styles/tokens.css` for the available default tokens, and the project's theme files for overrides. Treat `tokens.generated.css` as generated output.
|
|
34
|
-
- `src/system/components/<Name>.svelte` and `src/editor/component-editor/<Name>Editor.svelte` for a matching runtime/editor pair. Use `Toggle` for interaction states, `Badge` for variants and linking, and `Card` for separate text and container parts.
|
|
35
|
-
- `src/editor/core/components/aliasKinds.ts` for the suffixes that select editor controls.
|
|
36
|
-
- `src/editor/component-editor/index.ts` and the package exports for available authoring APIs.
|
|
37
|
-
|
|
38
|
-
Use the current source as the contract when older prose differs. For example, the current brand color family uses `--surface-brand`, `--border-brand`, and `--text-brand`; `--text-primary` names neutral primary text.
|
|
39
|
-
|
|
40
|
-
Read [references/token-naming.md](references/token-naming.md) when choosing property suffixes. Read the other references at the steps that need them.
|
|
41
|
-
|
|
42
|
-
## Property design
|
|
43
|
-
|
|
44
|
-
Before implementation, identify the component's parts, text roles, variants, and states. Make a short property map that connects each editable role to an existing default token and the CSS property it controls. Keep separate roles independent even when they start with the same value.
|
|
45
|
-
|
|
46
|
-
| Component property | Default assignment | CSS use |
|
|
47
|
-
|---|---|---|
|
|
48
|
-
| `--statcard-frame-surface` | `--surface-neutral` | `background` |
|
|
49
|
-
| `--statcard-frame-border` | `--border-neutral` | `border-color` |
|
|
50
|
-
| `--statcard-frame-border-width` | `--border-width-1` | `border-width` |
|
|
51
|
-
| `--statcard-frame-radius` | `--radius-md` | `border-radius` |
|
|
52
|
-
| `--statcard-frame-padding` | `--space-16` | `padding` |
|
|
53
|
-
| `--statcard-value-text` | `--text-primary` | `color` |
|
|
54
|
-
| `--statcard-label-text` | `--text-secondary` | `color` |
|
|
55
|
-
|
|
56
|
-
Choose defaults from tokens that exist in the target project. Prefer the color role that matches the purpose: surfaces for fills, borders for outlines, and text tokens for text. Select geometry from the relevant spacing, radius, stroke, and icon scales. Give each text role its own family, size, weight, line height, and letter spacing properties, using existing typography tokens.
|
|
57
|
-
|
|
58
|
-
Name properties with the component id first and the property suffix last:
|
|
59
|
-
|
|
60
|
-
```text
|
|
61
|
-
--<componentId>-<part-or-variant>[-<state>][-<element>]-<property>
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Use a lowercase id with no dashes that matches the runtime filename: `StatCard.svelte` has id `statcard`. Include the segments that distinguish the role. Follow the closest shipped component for components with both variants and parts. Put state segments before the final property: `--statcard-frame-hover-surface`.
|
|
65
|
-
|
|
66
|
-
The suffix selects the editor control. Use `-surface` for a fill, `-border` for border color, `-border-width` for stroke thickness, `-radius` for corners, and `-padding` or `-gap` for spacing. Use the full typography suffixes such as `-font-size`. Verify other suffixes against `aliasKinds.ts`.
|
|
67
|
-
|
|
68
|
-
## Runtime component
|
|
69
|
-
|
|
70
|
-
Create `src/system/components/StatCard.svelte`. Use Svelte 5 props and snippets, semantic HTML, and the behavior the task requires. Start with a short HTML comment that states the component's purpose, suitable uses, and an alternative for uses outside its scope. The component CLI reads this comment and `interface Props` to describe the component.
|
|
71
|
-
|
|
72
|
-
Declare every editable property explicitly in a literal `:global(:root)` block. The discovery parser reads the Svelte source to seed `component-configs/<id>/default.json`; it needs concrete property names. Keep SCSS loops and interpolation out of this declaration block.
|
|
73
|
-
|
|
74
|
-
```svelte
|
|
75
|
-
<style>
|
|
76
|
-
:global(:root) {
|
|
77
|
-
--statcard-frame-surface: var(--surface-neutral);
|
|
78
|
-
--statcard-frame-border: var(--border-neutral);
|
|
79
|
-
--statcard-frame-border-width: var(--border-width-1);
|
|
80
|
-
--statcard-frame-radius: var(--radius-md);
|
|
81
|
-
--statcard-frame-padding: var(--space-16);
|
|
82
|
-
--statcard-value-text: var(--text-primary);
|
|
83
|
-
--statcard-label-text: var(--text-secondary);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.statcard {
|
|
87
|
-
display: grid;
|
|
88
|
-
background: var(--statcard-frame-surface);
|
|
89
|
-
border: var(--statcard-frame-border-width) solid var(--statcard-frame-border);
|
|
90
|
-
border-radius: var(--statcard-frame-radius);
|
|
91
|
-
padding: var(--statcard-frame-padding);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.value { color: var(--statcard-value-text); }
|
|
95
|
-
.label { color: var(--statcard-label-text); }
|
|
96
|
-
</style>
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
This excerpt shows the assignment chain. Add typography, spacing, and other properties from the component's actual property map.
|
|
100
|
-
|
|
101
|
-
Route editable styling through component properties. Keep structural CSS such as `display: grid`, `width: 100%`, and `align-items: center` in the component's layout rules. Use token references for visual defaults. A token expression such as `calc(var(--space-64) * 4)` can express a dimension beyond the scale when the component needs it; prefer a direct assignment when a preset value fits.
|
|
102
|
-
|
|
103
|
-
For persistent structural choices such as alignment or visibility, read [references/intrinsics.md](references/intrinsics.md). Declare an `IntrinsicSpec`, mirror its default in `:global(:root)`, and register it through the `intrinsics` field. Keep these choices outside `allTokens`.
|
|
104
|
-
|
|
105
|
-
## Variants and states
|
|
106
|
-
|
|
107
|
-
Parts coexist, variants provide alternative presentations, and states reflect runtime conditions. Preserve those distinctions in both the component API and the editor.
|
|
108
|
-
|
|
109
|
-
Use the default state for shared geometry and typography. Add state properties for the values that change. Follow `Toggle` for a component state such as `on` with an interaction state such as `hover`. Live Tokens treats `disabled` as terminal: its editor fieldset is flat, and its tokens carry no combined hover or selected state.
|
|
110
|
-
|
|
111
|
-
Make the preview render the state whose properties the user edits. Pair each hover selector with a `.force-hover` selector and expose a class prop so the editor can show hover without a pointer. Preserve native disabled behavior, keyboard operation, and visible focus for interactive controls.
|
|
112
|
-
|
|
113
|
-
For action emphasis, use `primary` for the single action that completes the page's main task, `secondary` for supporting or directly related actions, and `outline` for unrelated or informational actions. Use `danger` for actions that destroy saved work. The page chooses the one primary action; a component supplies the relevant variants.
|
|
114
|
-
|
|
115
|
-
## Component editor
|
|
116
|
-
|
|
117
|
-
Create `src/system/components/StatCardEditor.svelte` beside the custom runtime file. Shipped editors use a separate internal directory; custom components keep both files together.
|
|
118
|
-
|
|
119
|
-
In `<script module lang="ts">`, declare `const component = 'statcard'`, define the token lists, and export their flat union as `allTokens: Token[]`. Each editable runtime property has one schema entry with the exact variable name. Use `element` to group rows by part and a short `label` to name the property.
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
import type { Token } from '@motion-proto/live-tokens/component-editor';
|
|
123
|
-
|
|
124
|
-
const component = 'statcard';
|
|
125
|
-
const states: Record<string, Token[]> = {
|
|
126
|
-
default: [
|
|
127
|
-
{ label: 'surface', element: 'frame', variable: '--statcard-frame-surface' },
|
|
128
|
-
{ label: 'padding', element: 'frame', variable: '--statcard-frame-padding' },
|
|
129
|
-
{ label: 'text', element: 'value', variable: '--statcard-value-text' },
|
|
130
|
-
{ label: 'text', element: 'label', variable: '--statcard-label-text' },
|
|
131
|
-
],
|
|
132
|
-
};
|
|
133
|
-
export const allTokens: Token[] = Object.values(states).flat();
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Expand this illustrative schema to cover the full property map. In the instance script, import the runtime component and the editor primitives. Mount `ComponentEditorBase` with `component` and `tokens={allTokens}`, then a `VariantGroup` for each variant with its `states`, `component`, and runtime preview. Follow the matching shipped editor for the exact snippet API.
|
|
137
|
-
|
|
138
|
-
Use public imports in consumer projects:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
import { ComponentEditorBase, VariantGroup } from '@motion-proto/live-tokens/component-editor';
|
|
142
|
-
import StatCard from './StatCard.svelte';
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Read shipped source files for examples; import package APIs through their public exports. Use `--ui-*` tokens for custom editor chrome and let the runtime preview consume the theme through its component properties.
|
|
146
|
-
|
|
147
|
-
When variants should share properties, read [references/linked-siblings.md](references/linked-siblings.md). Declare intentional sibling sets with `groupKey`, enable the linked controls with `canBeLinked`, and wire the linked block using the shipped helpers. Scope keys to the role: `value-font-size` and `label-font-size` stay independent. When using `buildTypeGroup*` helpers, pass `{ component, variants }` so they derive keys for each slot.
|
|
148
|
-
|
|
149
|
-
## Registration and persistence
|
|
150
|
-
|
|
151
|
-
Add the custom component to the existing `bootLiveTokens` call, preserving the project's other registrations and setup:
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
import StatCardEditor, { allTokens as statCardTokens } from './system/components/StatCardEditor.svelte';
|
|
155
|
-
|
|
156
|
-
bootLiveTokens(App, '#app', {
|
|
157
|
-
components: [{
|
|
158
|
-
id: 'statcard',
|
|
159
|
-
label: 'Stat Card',
|
|
160
|
-
icon: 'fas fa-chart-simple',
|
|
161
|
-
sourceFile: 'src/system/components/StatCard.svelte',
|
|
162
|
-
editorComponent: StatCardEditor,
|
|
163
|
-
schema: statCardTokens,
|
|
164
|
-
}],
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
`bootLiveTokens` registers components after editor initialization and before theme initialization. Use that registration window. For a manual bootstrap, follow the package initialization order and register before mounting the app. Choose a unique id so the registration preserves built-in entries.
|
|
169
|
-
|
|
170
|
-
The runtime root declarations supply the initial assignments. The plugin derives `default.json`; editor saves write `_working.json`, and Save As creates a named preset. Preserve token references through this flow. Verify the project's configured data path rather than creating a second persistence mechanism.
|
|
171
|
-
|
|
172
|
-
For a first-party addition inside the Live Tokens package, follow the internal layout instead: runtime in `src/system/components`, editor in `src/editor/component-editor`, and an entry in `builtInRegistry` in `src/editor/component-editor/registry.ts`. Update the built-in id union and catalogue through the repository's existing conventions.
|
|
173
|
-
|
|
174
|
-
## Rendering integration
|
|
175
|
-
|
|
176
|
-
Read [references/sketch-mode.md](references/sketch-mode.md) and integrate the component's painted parts with Sketch mode. Custom components use the reserved sketch classes and bind the sketch properties to their own semantic properties. Choose a wrapper that accommodates the layer's positioning, overflow, and pseudo-element requirements. First-party components add the corresponding `PartSpec` entries.
|
|
177
|
-
|
|
178
|
-
For a fixed overlay, read [references/fixed-overlays.md](references/fixed-overlays.md) and portal the layer to `body`. Follow the existing container pattern when the component owns typography inside a content snippet.
|
|
179
|
-
|
|
180
|
-
## Verification
|
|
181
|
-
|
|
182
|
-
Run `npx live-tokens check-component <id> --strict --json`. Inside the package repository, use `node bin/cli.mjs check-component <id> --strict --json`. Resolve findings and rerun until the command passes. Run the project's Svelte checks and the build or tests that cover the new behavior.
|
|
183
|
-
|
|
184
|
-
Read [references/contract-tests.md](references/contract-tests.md) and verify the registry entry with `checkRegistryEntry`. The contract checks registration, unique schema variables, runtime declarations, seeded defaults, and alias round trips. Include intrinsic checks when the component declares intrinsics.
|
|
185
|
-
|
|
186
|
-
Open `/live-tokens/components` and verify:
|
|
187
|
-
|
|
188
|
-
- The component appears under Custom, or among system components for a first-party addition.
|
|
189
|
-
- Each property has the correct editor control and changes the matching runtime part.
|
|
190
|
-
- Preview variants and states match the controls; keyboard and pointer behavior work.
|
|
191
|
-
- Linked properties change together, and separate roles remain independent.
|
|
192
|
-
- Save and reload retain assignments. Reset restores runtime defaults.
|
|
193
|
-
- Theme changes reach every property that references a theme token.
|
|
194
|
-
- Sketch mode renders each painted part correctly and restores the normal appearance when switched off.
|
|
195
|
-
|
|
196
|
-
Report the files, component id, available props, and verification results. State any checks the environment prevented you from running.
|