@motion-proto/live-tokens 0.74.0 → 0.76.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 +33 -38
- package/.claude/skills/live-tokens-create-component/SKILL copy.md +196 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +198 -146
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +95 -52
- package/.claude/skills/live-tokens-create-component/references/intrinsics.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +12 -12
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +2 -1
- package/.claude/skills/live-tokens-create-page/SKILL.md +187 -0
- package/.claude/skills/live-tokens-create-page/references/interaction-sources.md +66 -0
- package/.claude/skills/live-tokens-create-page/references/layout-sources.md +87 -0
- package/.claude/skills/live-tokens-create-theme/SKILL.md +52 -48
- package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +69 -60
- package/.claude/skills/live-tokens-pick-component/SKILL.md +64 -79
- package/.claude/skills/live-tokens-set-colors/SKILL.md +47 -38
- package/.claude/skills/live-tokens-set-geometry/SKILL.md +58 -35
- package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +5 -3
- package/.claude/skills/live-tokens-set-type/SKILL.md +30 -30
- package/CHANGELOG.md +138 -0
- package/README.md +17 -7
- package/bin/check-component.mjs +91 -19
- package/bin/check-page.mjs +102 -21
- package/bin/cli.mjs +87 -113
- package/bin/contractRunner.mjs +945 -0
- package/bin/create.mjs +1 -1
- package/bin/lib/catalogue.mjs +37 -30
- package/bin/lib/findings.mjs +46 -15
- package/bin/lib/report.mjs +3 -3
- package/bin/lib/tokenVocabulary.mjs +4 -4
- package/bin/migrate-routes.mjs +5 -5
- package/bin/migrate.mjs +4 -4
- package/bin/save-theme.mjs +8 -9
- package/bin/set-colors.mjs +9 -11
- package/bin/set-geometry.mjs +7 -7
- package/bin/set-type.mjs +5 -7
- package/bin/setup-claude.mjs +110 -0
- package/dist-plugin/{chunk-W6Y4BWFB.js → chunk-6WGFOJXO.js} +22 -0
- package/dist-plugin/{chunk-7VRTBGJT.js → chunk-PDNL4NC5.js} +9 -2
- package/dist-plugin/{chunk-V3YF6CGT.js → chunk-SWXRVZKT.js} +43 -1
- package/dist-plugin/{dataPaths-BhWzd5cL.d.cts → dataPaths-BpIK_Obx.d.cts} +1 -0
- package/dist-plugin/{dataPaths-BhWzd5cL.d.ts → dataPaths-BpIK_Obx.d.ts} +1 -0
- package/dist-plugin/index.cjs +310 -147
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +218 -122
- package/dist-plugin/migrateData/index.cjs +66 -2
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +3 -3
- package/dist-plugin/setColors/index.cjs +61 -4
- package/dist-plugin/setColors/index.d.cts +1 -1
- package/dist-plugin/setColors/index.d.ts +1 -1
- package/dist-plugin/setColors/index.js +5 -5
- package/dist-plugin/setGeometry/index.cjs +107 -44
- package/dist-plugin/setGeometry/index.d.cts +5 -5
- package/dist-plugin/setGeometry/index.d.ts +5 -5
- package/dist-plugin/setGeometry/index.js +51 -45
- package/dist-plugin/setType/index.cjs +15 -0
- package/dist-plugin/setType/index.d.cts +1 -1
- package/dist-plugin/setType/index.d.ts +1 -1
- package/dist-plugin/setType/index.js +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +35 -8
- package/src/app/site.css +19 -9
- package/src/editor/bootstrap.ts +2 -0
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +4 -4
- package/src/editor/component-editor/DialogEditor.svelte +4 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +4 -1
- package/src/editor/component-editor/SegmentedControlEditor.svelte +6 -1
- package/src/editor/component-editor/TabBarEditor.svelte +1 -1
- package/src/editor/component-editor/TableEditor.svelte +2 -2
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +6 -3
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +41 -20
- package/src/editor/core/components/adjustAliases.ts +59 -45
- package/src/editor/core/components/aliasKinds.ts +9 -5
- package/src/editor/core/preview/themePreview.ts +9 -2
- package/src/editor/core/sketch/sketchLayer.ts +22 -0
- package/src/editor/core/store/editorStore.ts +10 -1
- package/src/editor/core/themes/buildColors.ts +3 -3
- package/src/editor/core/themes/liveStateStream.ts +26 -0
- package/src/editor/core/themes/migrations/2026-09-07-stroke-role-renames.ts +52 -0
- package/src/editor/core/themes/migrations/index.ts +2 -0
- package/src/editor/core/themes/themeDocumentSync.ts +22 -11
- package/src/editor/core/themes/themeService.ts +9 -2
- package/src/editor/pages/ComponentEditorPage.svelte +17 -1
- package/src/editor/pages/liveTokensEditorHandle.ts +23 -0
- package/src/editor/skill-atlas/SkillAtlas.svelte +75 -573
- package/src/editor/skill-atlas/TreeCanvas.svelte +263 -0
- package/src/editor/skill-atlas/TreeNodeCard.svelte +136 -53
- package/src/editor/skill-atlas/edges.ts +31 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +20 -19
- package/src/editor/skill-atlas/skillTrees.ts +19 -3840
- package/src/editor/skill-atlas/trees/check-compliance.ts +183 -0
- package/src/editor/skill-atlas/trees/create-component.ts +275 -0
- package/src/editor/skill-atlas/trees/create-page.ts +320 -0
- package/src/editor/skill-atlas/trees/create-theme.ts +255 -0
- package/src/editor/skill-atlas/trees/fix-findings.ts +469 -0
- package/src/editor/skill-atlas/trees/pick-component.ts +300 -0
- package/src/editor/skill-atlas/trees/set-colors.ts +148 -0
- package/src/editor/skill-atlas/trees/set-geometry.ts +136 -0
- package/src/editor/skill-atlas/trees/set-type.ts +142 -0
- package/src/editor/skill-atlas/types.ts +3 -4
- package/src/editor/skill-atlas/wireLayout.ts +287 -0
- package/src/live-tokens/data/themes/autumn.json +15 -15
- package/src/live-tokens/data/themes/halloween.json +15 -15
- package/src/live-tokens/data/themes/midnight-study.json +15 -15
- package/src/live-tokens/data/themes/ocean.json +15 -15
- package/src/live-tokens/data/themes/royal-velvet.json +15 -15
- package/src/live-tokens/data/themes/sketchy.json +15 -15
- package/src/live-tokens/data/themes/spring-meadow.json +15 -15
- package/src/live-tokens/data/themes/sunset.json +15 -15
- package/src/system/components/Badge.svelte +7 -0
- package/src/system/components/Button.svelte +7 -0
- package/src/system/components/Callout.svelte +10 -6
- package/src/system/components/Card.svelte +23 -4
- package/src/system/components/CodeSnippet.svelte +4 -3
- package/src/system/components/CollapsibleSection.svelte +23 -8
- package/src/system/components/CornerBadge.svelte +6 -0
- package/src/system/components/Dialog.svelte +13 -6
- package/src/system/components/IconButton.svelte +9 -0
- package/src/system/components/Image.svelte +8 -0
- package/src/system/components/ImageLightbox.svelte +6 -0
- package/src/system/components/InlineEditActions.svelte +7 -0
- package/src/system/components/Input.svelte +7 -0
- package/src/system/components/MenuSelect.svelte +7 -0
- package/src/system/components/Notification.svelte +7 -0
- package/src/system/components/Panel.svelte +6 -0
- package/src/system/components/ProgressBar.svelte +5 -0
- package/src/system/components/RadioButton.svelte +11 -5
- package/src/system/components/SectionDivider.svelte +8 -0
- package/src/system/components/SegmentedControl.svelte +6 -0
- package/src/system/components/SideNavigation.svelte +6 -0
- package/src/system/components/Slider.svelte +7 -4
- package/src/system/components/TabBar.svelte +15 -9
- package/src/system/components/Table.svelte +8 -3
- package/src/system/components/Toggle.svelte +4 -4
- package/src/system/components/Tooltip.svelte +6 -0
- package/src/testing-js/chunk-AO7EZHYV.js +776 -0
- package/src/testing-js/chunk-AO7EZHYV.js.map +1 -0
- package/src/testing-js/chunk-FAFOAWYL.js +39 -0
- package/src/testing-js/chunk-FAFOAWYL.js.map +1 -0
- package/src/testing-js/chunk-L73N4NSO.js +23 -0
- package/src/testing-js/chunk-L73N4NSO.js.map +1 -0
- package/src/testing-js/chunk-LXR3MN6N.js +3063 -0
- package/src/testing-js/chunk-LXR3MN6N.js.map +1 -0
- package/src/testing-js/chunk-ZMSX6CXR.js +53 -0
- package/src/testing-js/chunk-ZMSX6CXR.js.map +1 -0
- package/src/testing-js/component-alias.contract.js +81 -0
- package/src/testing-js/component-alias.contract.js.map +1 -0
- package/src/testing-js/component-editor.contract.js +62 -0
- package/src/testing-js/component-editor.contract.js.map +1 -0
- package/src/testing-js/component-render.contract.js +568 -0
- package/src/testing-js/component-render.contract.js.map +1 -0
- package/src/testing-js/index.d.ts +293 -0
- package/src/testing-js/index.js +222 -0
- package/src/testing-js/index.js.map +1 -0
- package/src/testing-js/registry.contract.js +39 -0
- package/src/testing-js/registry.contract.js.map +1 -0
- package/src/testing-js/vitest-BE6uGF31.d.ts +73 -0
- package/src/testing-js/vitest.d.ts +3 -0
- package/src/testing-js/vitest.js +13 -0
- package/src/testing-js/vitest.js.map +1 -0
- package/template/README.md +13 -0
- package/template/_gitignore +6 -0
- package/template/package.json +3 -1
- package/template/src/pages/Home.svelte +4 -18
- package/.claude/skills/live-tokens-build-page/SKILL.md +0 -103
- package/.claude/skills/live-tokens-build-page/references/layout-sources.md +0 -48
|
@@ -1,189 +1,241 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: live-tokens-create-component
|
|
3
|
-
description:
|
|
3
|
+
description: Create an editable component for a @motion-proto/live-tokens project. A component is a runtime Svelte file, an editor Svelte file, and one registration. The runtime file declares one semantic property per editable CSS property and assigns each an existing design token. The property names are semantic, based on function, and reuse the names of the existing components. Use when live-tokens-pick-component finds no suitable component, or the user asks for a new component. Use when the user asks to make an existing Svelte component editable in the live-tokens editor. For page integration, read live-tokens-create-page.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
The end state is a runtime Svelte file, an editor Svelte file, one registration, and an entry on `/live-tokens/components` under the **CUSTOM** group with full token editing, linked-block sharing, and persistence.
|
|
9
|
-
|
|
10
|
-
## Worked examples ship inside the package
|
|
11
|
-
|
|
12
|
-
Read a shipped component's source from the consumer's `node_modules` rather than from memory, because the files are the contract and this skill is not:
|
|
13
|
-
|
|
14
|
-
- Runtime files: `node_modules/@motion-proto/live-tokens/src/system/components/<Name>.svelte`.
|
|
15
|
-
- Simplest reads (no state, no linked-block): `Card` (single variant with parts), `Badge` and `Callout` (multi-variant).
|
|
16
|
-
- Multi-state (hover, disabled, focus): `Button`, `Input`.
|
|
17
|
-
- Multi-part (overlay / header / body / footer): `Dialog`.
|
|
18
|
-
- Multi-variant with linked siblings (`canBeLinked` + `groupKey`): `SegmentedControl`, `TabBar`. Composes another shipped component: `CodeSnippet`.
|
|
19
|
-
- Every rule below in the fewest lines: `Toggle`. Component states name themselves in the token (`--toggle-on-*`, `--toggle-disabled-*`), interaction states layer on top (`--toggle-hover-*`, `--toggle-on-hover-*`), disabled is terminal (no `--toggle-disabled-hover-*`), and each `:hover` selector has a `.force-hover` sibling so the editor's preview can paint hover tokens without a pointer.
|
|
20
|
-
- Editor files: `node_modules/@motion-proto/live-tokens/src/editor/component-editor/<Name>Editor.svelte`. `ToggleEditor` has no `groupKey` and no `canBeLinked`; for components that share base properties across variants, read `references/linked-siblings.md`.
|
|
21
|
-
|
|
22
|
-
Shipped editors live in `src/editor/component-editor/` because they are library-internal. For *your* component, co-locate both files in `src/system/components/`. Read the shipped files for pattern, ignore their location.
|
|
23
|
-
|
|
24
|
-
## The recipe
|
|
25
|
-
|
|
26
|
-
1. **Runtime file**, `src/system/components/MyWidget.svelte`. Declare every editable slot as a CSS custom property inside `:global(:root)`, defaulting to a theme token (never a raw value). The plugin parses `:global(:root)` to seed `component-configs/<id>/default.json`; variables declared anywhere else cannot be edited.
|
|
27
|
-
2. **Editor file**, `src/system/components/MyWidgetEditor.svelte`. In a `<script module>` block, declare `const component = 'mywidget'`, build a `states: Record<string, Token[]>` for each VariantGroup, and export the flat union as `allTokens: Token[]`. Components with linked siblings also build a `linkableContexts: Map<string, string>` (read `references/linked-siblings.md`). Components with structural or display controls that are not token values (alignment, element visibility, layout position) also export an `intrinsics: IntrinsicSpec[]` (read `references/intrinsics.md`). In the runtime `<script>` block, mount `ComponentEditorBase` with one `VariantGroup` per variant.
|
|
28
|
-
3. **Register** by passing the component to `bootLiveTokens` in `src/main.ts`, the boot the scaffold generates:
|
|
29
|
-
```ts
|
|
30
|
-
import { bootLiveTokens } from '@motion-proto/live-tokens';
|
|
31
|
-
import App from './App.svelte';
|
|
32
|
-
import MyWidgetEditor, { allTokens as myWidgetTokens } from './system/components/MyWidgetEditor.svelte';
|
|
33
|
-
|
|
34
|
-
bootLiveTokens(App, '#app', {
|
|
35
|
-
components: [{
|
|
36
|
-
id: 'mywidget',
|
|
37
|
-
label: 'My Widget',
|
|
38
|
-
icon: 'fas fa-magic',
|
|
39
|
-
sourceFile: 'src/system/components/MyWidget.svelte',
|
|
40
|
-
editorComponent: MyWidgetEditor,
|
|
41
|
-
schema: myWidgetTokens,
|
|
42
|
-
}],
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
`bootLiveTokens` calls `registerComponent` for you after its editor init hooks and before it seeds configs, so a standalone `registerComponent(...)` placed *before* `bootLiveTokens` lands in the wrong window and can leave editor changes disconnected from the live page. Call `registerComponent` directly only when the app mounts manually, and then before `mount(App, ...)`. Registering against a built-in id wins with a console warning; the right call is a unique id.
|
|
46
|
-
4. **Say what it is for.** The runtime file's leading HTML comment is the component's description. `npx live-tokens components` prints it beside the id with the variants and props read from `interface Props` (`--json` for data), which is how **live-tokens-pick-component** weighs a project's own component against the shipped set: no skill file is edited, and nothing is lost when `setup-claude` refreshes the skills. Name the job it does and what it is not for. A directory other than `src/system/components` goes in `"componentDirs"` in `live-tokens.config.json`. A first-party component is also added to the picker's **Catalogue** line, which `check:skills` holds.
|
|
47
|
-
5. **Join the sketch layer.** The effect draws a fixed set of parts, so a new component stays crisp while the page around it goes hand-drawn until it opts in. A consumer component carries one of four reserved classes on its root and names the five `--sketch-*` values it is drawn with; a first-party component adds a `PartSpec` row instead. The layer also takes `background`, `border-color`, `box-shadow`, `overflow`, `position` and both pseudo-elements away from the element it draws, which constrains where the class can go. Read `references/sketch-mode.md`.
|
|
48
|
-
6. **Gate on the checker.** Run it, fix every error, and run it again. Do not call the component done while it reports one:
|
|
49
|
-
```bash
|
|
50
|
-
npx live-tokens check-component <id> --strict --json
|
|
51
|
-
```
|
|
52
|
-
`--json` gives each finding a stable `rule` id and a line number, so work one rule at a time. `--strict` fails on warnings too, the right setting for a new component: every warning is a naming or token decision that is cheaper to make now than to migrate later. `--off=<rule>` silences a rule for one run, which a component still being authored has no use for: the finding is a decision to make. Exit code 0 is the gate. With no id it checks every component under `src/system/components`; a project scaffolded by `create` runs that as `npm run check:design` before every `vite build`.
|
|
53
|
-
|
|
54
|
-
If it rejects a suffix, do not invent a new name for the role. Find a shipped component that paints the same thing and use the name it uses: every shipped component passes this same check, so the catalogue is the worked reference.
|
|
55
|
-
7. **Verify** with the checklist at the bottom of this file, then place the component on a page with **live-tokens-build-page**.
|
|
56
|
-
|
|
57
|
-
## Token discipline
|
|
58
|
-
|
|
59
|
-
### Naming scheme
|
|
6
|
+
# Creating a component for a live-tokens project
|
|
60
7
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
8
|
+
Create a component whose structure and behavior serve the user's purpose. Give each editable visual property a semantic name. Assign its default from the existing design tokens. Deliver the runtime file, the editor file, and the registration together.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
1. Read the project: `package.json`, `live-tokens.config.json`, `src/main.ts`, the catalogue, the token scales the component will use, a shipped runtime and editor pair, and the property suffixes.
|
|
13
|
+
2. Design the properties: separate the component's parts, variants, and states, then write one row per editable role with its token and the CSS it controls, named the way the shipped components name the same role.
|
|
14
|
+
3. Write the runtime file: the usage comment and the `:global(:root)` block. A structural choice is an intrinsic. Every component joins the sketch layer, and a fixed overlay portals to `<body>`.
|
|
15
|
+
4. Write the editor file: the schema, the preview props, and the markup. Variants that share a value are linked.
|
|
16
|
+
5. Register the component in the module `src/main.ts` and `live-tokens.testing.ts` both name.
|
|
17
|
+
6. Run **live-tokens-check-compliance**, then `npx live-tokens check-component <id> --tests --strict --json` until exit 0 with complete applicable coverage, then the Svelte check and the build.
|
|
18
|
+
7. Reply with the files, the id, the props, and each check's result. Then place the component on a page with **live-tokens-create-page**.
|
|
19
|
+
|
|
20
|
+
## Design model
|
|
21
|
+
|
|
22
|
+
A live-tokens project has two layers.
|
|
23
|
+
|
|
24
|
+
| Layer | Responsibility | Example |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Design tokens | Name the available colors, typography, geometry, and motion values. A theme sets the values. | `--space-16`, `--radius-md`, `--surface-neutral` |
|
|
27
|
+
| Semantic properties | Name the visual roles within a component and reference tokens. A component config records these assignments. | `--statcard-padding: var(--space-16)` |
|
|
28
|
+
|
|
29
|
+
A token is assigned to a property, and a CSS declaration reads the property. The editor changes the assignment; the runtime reads the property. Keep the assignment a token reference, so a theme change reaches the component.
|
|
30
|
+
|
|
31
|
+
A property describes its purpose: `--statcard-value`, `--statcard-radius`, `--statcard-label-font-size`. Its name stays stable when its assigned color or size changes. Use role names such as `surface` and `text`. Use full words for component ids and parts.
|
|
32
|
+
|
|
33
|
+
A component is distinct in its anatomy, its proportions, its content hierarchy, and its behavior. Its appearance comes from the existing tokens. Create only the variants and states the task requires. The tokens stay as they are; a new token is a separate change to the design system.
|
|
34
|
+
|
|
35
|
+
Props carry content and behavior: a value, a label, a callback. Properties carry the editable appearance. When a variant is a choice the page makes, expose it as a prop.
|
|
64
36
|
|
|
65
|
-
|
|
66
|
-
- `part` or `variant`: the sub-region (`bar`, `option`, `track`, `header`, `body`, `footer`, `overlay`, `value`, `label`), or, on a component whose variants differ in more than one property, the variant name: `--badge-accent-surface`, `--callout-danger-border`. A component with both stacks them outer to inner, so the bar in its small size is `--segmentedcontrol-bar-small-padding`.
|
|
67
|
-
- `state` (optional): interaction or component state (`hover`, `disabled`, `selected`, `focus`). **Always before the property.**
|
|
68
|
-
- `element` (optional): sub-element inside the part (`dot`, `icon`, `label`, `text`).
|
|
69
|
-
- `property`: theme role or CSS property. Always last.
|
|
37
|
+
## Source inspection
|
|
70
38
|
|
|
71
|
-
|
|
39
|
+
Before writing a file:
|
|
72
40
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`-hint`, `-error`, `-placeholder`, `-value`. Geometry: `-radius`,
|
|
79
|
-
`-border-width`, `-accent-width`, `-hairline-thickness`, `-thickness`,
|
|
80
|
-
`-width`, `-height`, `-size`, `-padding`, `-margin`, `-gap`, `-inset`,
|
|
81
|
-
`-divider-width`, `-divider-thickness`, `-divider-height`, `-divider-inset`,
|
|
82
|
-
`-track-height`, `-dot-size`, `-thumb-size`, `-icon-size`, `-scale`, `-blur`.
|
|
83
|
-
Motion: `-duration`, `-easing`. Typography: `-font-family`, `-font-weight`,
|
|
84
|
-
`-font-size`, `-line-height`, `-letter-spacing`.
|
|
41
|
+
1. Read the project's `package.json`, `live-tokens.config.json`, and `src/main.ts`.
|
|
42
|
+
2. Run `npx live-tokens components`. The list holds every component the project has, with its variants and usage comment. `npx live-tokens components <id>` prints one component's props.
|
|
43
|
+
3. Run `npx live-tokens tokens --scale <name>` for each token scale the component will use. Those names are the tokens a property can reference.
|
|
44
|
+
4. Read a shipped runtime and editor pair: `Toggle` for interaction states, `Badge` for variants and linked values, `Card` for text and container parts.
|
|
45
|
+
5. Read `references/token-naming.md` for the suffixes that select editor controls.
|
|
85
46
|
|
|
86
|
-
|
|
87
|
-
from this list. Declare it in the editor's `intrinsics` instead, which is what
|
|
88
|
-
exempts it, and never end its name in a state word, which reads as
|
|
89
|
-
state-after-property and fails.
|
|
47
|
+
The shipped sources are in `node_modules/@motion-proto/live-tokens/src/`: the runtime at `system/components/<Name>.svelte`, the editor at `editor/component-editor/<Name>Editor.svelte`. Inside the live-tokens repository, read them from the repository root. The source is the contract.
|
|
90
48
|
|
|
91
|
-
|
|
92
|
-
compete. A suffix outside that list fails `check-component`. The list lives in
|
|
93
|
-
`KIND_RULES` in the editor's `aliasKinds.ts`, which the picker, the
|
|
94
|
-
`set-geometry` CLI, and `check-component` all read, so a name accepted here
|
|
95
|
-
always has a control behind it.
|
|
49
|
+
## Variants and states
|
|
96
50
|
|
|
97
|
-
|
|
51
|
+
A component has three kinds of division. Keep them apart in the props, the names, and the editor.
|
|
98
52
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- **Typography `groupKey` on multi-slot components must include the slot prefix.** `groupKey: 'value-font-family'` and `groupKey: 'label-font-family'` stay distinct; a bare `groupKey: 'font-family'` silently merges the slots into one link tree. Single-slot components can use a bare typography `groupKey`; add the slot prefix the moment a second slot appears. The same trap applies to type-group colours (two slots ending in `-text` collapsing to one `text` key).
|
|
105
|
-
- **Let the type-group helpers derive slot-scoped keys.** When you build typography tokens with `buildTypeGroupColorTokens` / `buildTypeGroupTokens` / `buildTypeGroupFontTokens`, pass `{ component, variants }` so each slot gets a distinct, structural `groupKey`:
|
|
53
|
+
| Kind | Meaning | Example |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| Part | Regions present at once | Dialog's overlay, header, body, footer |
|
|
56
|
+
| Variant | Alternative presentations the page chooses | Badge's primary, danger |
|
|
57
|
+
| State | A runtime condition | Toggle's on, hover, disabled |
|
|
106
58
|
|
|
107
|
-
|
|
108
|
-
// variants = the variant/state segment strings as they appear in the variable name
|
|
109
|
-
const VARIANTS = ['default', 'hover'] as const;
|
|
110
|
-
...buildTypeGroupColorTokens(typeGroups, { component, variants: [...VARIANTS] }),
|
|
111
|
-
...buildTypeGroupFontTokens(typeGroups, { component, variants: [...VARIANTS] }),
|
|
112
|
-
```
|
|
59
|
+
States have two axes. A component state is one of a set that excludes the others: default, selected (or on), disabled. An interaction state layers on a component state: default, hover, and later focus or active. `disabled` is terminal: no other state layers on it, in the names or in the editor.
|
|
113
60
|
|
|
114
|
-
|
|
61
|
+
The default state carries the shared geometry and typography. A state adds properties only for the values that change: `--toggle-on-track-surface`, `--toggle-on-hover-track-surface`.
|
|
115
62
|
|
|
116
|
-
|
|
63
|
+
The preview renders the state being edited. Pair each `:hover` selector with a `.force-hover` selector and expose a `class` prop, so the editor shows hover without a pointer. Keep native disabled behavior, keyboard operation, and visible focus on an interactive control.
|
|
117
64
|
|
|
118
|
-
|
|
65
|
+
A component supplies its variants. The page chooses the one primary action.
|
|
119
66
|
|
|
120
|
-
|
|
67
|
+
## Property design
|
|
121
68
|
|
|
122
|
-
|
|
123
|
-
- **Interaction states** are a select *inside* each component-state fieldset: `default`, `hover`. Add `focus` or `active` later if needed.
|
|
69
|
+
Before writing a file, identify the component's parts, text roles, variants, and states. Then write a property map: one row per editable role, with the token it is assigned and the CSS property it controls. Keep separate roles independent even when they start with the same value.
|
|
124
70
|
|
|
125
|
-
|
|
71
|
+
| Property | Assigned token | CSS use |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `--statcard-surface` | `--surface-neutral` | `background` |
|
|
74
|
+
| `--statcard-border` | `--border-neutral` | `border-color` |
|
|
75
|
+
| `--statcard-border-width` | `--border-width-1` | `border-width` |
|
|
76
|
+
| `--statcard-radius` | `--radius-md` | `border-radius` |
|
|
77
|
+
| `--statcard-padding` | `--space-16` | `padding` |
|
|
78
|
+
| `--statcard-value` | `--text-primary` | `color` of the value |
|
|
79
|
+
| `--statcard-value-font-size` | `--font-size-2xl` | `font-size` of the value |
|
|
80
|
+
| `--statcard-label` | `--text-secondary` | `color` of the label |
|
|
126
81
|
|
|
127
|
-
|
|
128
|
-
- **`selected-disabled` is impossible.** Do not author tokens or fieldsets for it.
|
|
129
|
-
- **Parts are not states.** Dialog's `overlay | header | body | footer` are *parts* (all present at once), not states. The VariantGroup tab strip defaults its label to "Element" (neutral). If you label tabs anywhere, use **part** for structure and **state** for runtime conditions. Never call a footer a state.
|
|
130
|
-
- **Do not call interaction states "option states" or "selected states"** in the UI. `selected` is a *component* state.
|
|
82
|
+
Assign from the tokens the project has. Match the token scale to the role: `--surface-*` for a fill, `--border-*` for an outline, `--text-*` for text, and the space, radius, border-width, and icon-size scales for geometry. Give each text role five properties: `-font-family`, `-font-size`, `-font-weight`, `-line-height`, and `-letter-spacing`.
|
|
131
83
|
|
|
132
|
-
|
|
84
|
+
A property name starts with the component id and ends with the property suffix. Use this shape for part-specific states:
|
|
133
85
|
|
|
86
|
+
```text
|
|
87
|
+
--<componentId>[-<variant>][-<part>][-<state>]-<property>
|
|
134
88
|
```
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
89
|
+
|
|
90
|
+
- `componentId` is the runtime file name in lowercase with no dashes: `StatCard.svelte` is `statcard`.
|
|
91
|
+
- `variant` is present when the component has more than one: `--card-default-surface`, `--card-bare-surface`. A component with one variant has no variant segment: `--toggle-track-surface`.
|
|
92
|
+
- `part` names a region inside the component: `header`, `body`, `track`, `thumb`. The editor's `element` tag groups rows in the panel and is never a name segment.
|
|
93
|
+
- `state` comes before the property: `--card-hover-border`. `disabled` is terminal, so no name pairs `disabled` with another state.
|
|
94
|
+
- `property` is the suffix, and the suffix selects the editor control: `-surface` for a fill, `-border` for a border color, `-border-width` for a stroke, `-radius` for corners, `-padding` and `-gap` for spacing, and the five typography suffixes. `references/token-naming.md` lists every suffix.
|
|
95
|
+
|
|
96
|
+
For a state that affects several parts, follow Toggle: `--toggle-on-hover-track-surface`. State segments precede the affected part.
|
|
97
|
+
|
|
98
|
+
Name a role as the shipped component that paints the same thing names it. A fill is `-surface` in every shipped component. A knob is `-thumb`. A text role's color sits on the role's own name, `-title`, `-body`, `-label`, `-value`, and its typography hangs off that name: `--card-default-title-font-size`. A component with one text role uses `-text`: `--badge-primary-text`.
|
|
99
|
+
|
|
100
|
+
## Runtime component
|
|
101
|
+
|
|
102
|
+
Create `src/system/components/StatCard.svelte`. `check-component` finds a runtime there only. A component in another directory is listed by `components` and `report` when that directory is named in `"componentDirs"` in `live-tokens.config.json`, and `check-component` does not check it. Use Svelte 5 props and snippets, semantic HTML, and the behavior the task requires.
|
|
103
|
+
|
|
104
|
+
Open the file with an HTML comment in the shape every shipped component carries. `npx live-tokens components` prints the comment beside the id, and `components <id>` prints the props.
|
|
105
|
+
|
|
106
|
+
```svelte
|
|
107
|
+
<!--
|
|
108
|
+
StatCard.svelte. A figure with its label.
|
|
109
|
+
Use for: one number the reader takes in at a glance.
|
|
110
|
+
Not for: a set of records (Table); a titled block of content (Card).
|
|
111
|
+
-->
|
|
140
112
|
```
|
|
141
113
|
|
|
142
|
-
|
|
114
|
+
Declare every editable property in a literal `:global(:root)` block, each assigned a token. The plugin parses the Svelte source to seed `component-configs/<id>/default.json`, so the block holds plain declarations with no SCSS loop or interpolation.
|
|
115
|
+
|
|
116
|
+
```svelte
|
|
117
|
+
<style>
|
|
118
|
+
:global(:root) {
|
|
119
|
+
--statcard-surface: var(--surface-neutral);
|
|
120
|
+
--statcard-border: var(--border-neutral);
|
|
121
|
+
--statcard-border-width: var(--border-width-1);
|
|
122
|
+
--statcard-radius: var(--radius-md);
|
|
123
|
+
--statcard-padding: var(--space-16);
|
|
124
|
+
--statcard-value: var(--text-primary);
|
|
125
|
+
--statcard-value-font-size: var(--font-size-2xl);
|
|
126
|
+
--statcard-label: var(--text-secondary);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.statcard {
|
|
130
|
+
display: grid;
|
|
131
|
+
background: var(--statcard-surface);
|
|
132
|
+
border: var(--statcard-border-width) solid var(--statcard-border);
|
|
133
|
+
border-radius: var(--statcard-radius);
|
|
134
|
+
padding: var(--statcard-padding);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.value { color: var(--statcard-value); font-size: var(--statcard-value-font-size); }
|
|
138
|
+
.label { color: var(--statcard-label); }
|
|
139
|
+
</style>
|
|
140
|
+
```
|
|
143
141
|
|
|
144
|
-
|
|
142
|
+
The excerpt shows the chain for part of the property map. Every editable value reads a property. Structural CSS (`display: grid`, `width: 100%`, `align-items: center`) stays in the layout rules. A value beyond a scale is a token expression: `calc(var(--space-64) * 4)`. A property that carries a structural choice, an alignment or a visibility, is an intrinsic: read `references/intrinsics.md`.
|
|
143
|
+
|
|
144
|
+
## Component editor
|
|
145
|
+
|
|
146
|
+
Create `src/system/components/StatCardEditor.svelte` beside the runtime file. The editor has three parts.
|
|
147
|
+
|
|
148
|
+
1. A `<script module>` block exports `component`, the id, and `allTokens`, one row per property in the map. A row is `{ label, variable, element? }`; `element` groups rows in the panel by part, and `label` names the property in the row.
|
|
149
|
+
2. The instance script imports the runtime component and the editor primitives from the package's public paths, and maps the state being edited to preview props.
|
|
150
|
+
3. The markup mounts `ComponentEditorBase` with one `VariantGroup` per variant, each rendering a preview.
|
|
151
|
+
|
|
152
|
+
```svelte
|
|
153
|
+
<script module lang="ts">
|
|
154
|
+
import type { Token } from '@motion-proto/live-tokens/component-editor';
|
|
155
|
+
|
|
156
|
+
export const component = 'statcard';
|
|
157
|
+
const states: Record<string, Token[]> = {
|
|
158
|
+
default: [
|
|
159
|
+
{ label: 'surface', element: 'frame', variable: '--statcard-surface' },
|
|
160
|
+
{ label: 'border', element: 'frame', variable: '--statcard-border' },
|
|
161
|
+
{ label: 'border width', element: 'frame', variable: '--statcard-border-width' },
|
|
162
|
+
{ label: 'radius', element: 'frame', variable: '--statcard-radius' },
|
|
163
|
+
{ label: 'padding', element: 'frame', variable: '--statcard-padding' },
|
|
164
|
+
{ label: 'text', element: 'value', variable: '--statcard-value' },
|
|
165
|
+
{ label: 'font size', element: 'value', variable: '--statcard-value-font-size' },
|
|
166
|
+
{ label: 'text', element: 'label', variable: '--statcard-label' },
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
export const allTokens: Token[] = Object.values(states).flat();
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<script lang="ts">
|
|
173
|
+
import { ComponentEditorBase, VariantGroup } from '@motion-proto/live-tokens/component-editor';
|
|
174
|
+
import StatCard from './StatCard.svelte';
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<ComponentEditorBase {component} title="Stat Card" tokens={allTokens}>
|
|
178
|
+
<VariantGroup name="statcard" title="Stat Card" {states} {component}>
|
|
179
|
+
<StatCard value="1,204" label="Sessions" />
|
|
180
|
+
</VariantGroup>
|
|
181
|
+
</ComponentEditorBase>
|
|
182
|
+
```
|
|
145
183
|
|
|
146
|
-
|
|
184
|
+
The shipped editor for the closest component gives the preview snippet for a component with states. Custom chrome in an editor takes `--ui-*` tokens and no accent color; its copy uses periods and commas, never em-dashes.
|
|
147
185
|
|
|
148
|
-
|
|
186
|
+
When variants share a value, read `references/linked-siblings.md`. A `groupKey` is scoped to the text role: `value-font-size` and `label-font-size` stay separate keys. A `buildTypeGroup*` helper takes `{ component, variants }` so it derives one key per role.
|
|
149
187
|
|
|
150
|
-
|
|
188
|
+
## Registration
|
|
189
|
+
|
|
190
|
+
Register the component in `src/registerComponents.ts`, a registration-only module, beside any registration already there. The id is unique; a registration that repeats a shipped id replaces that component.
|
|
151
191
|
|
|
152
192
|
```ts
|
|
153
|
-
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
193
|
+
// src/registerComponents.ts
|
|
194
|
+
import { registerComponent } from '@motion-proto/live-tokens';
|
|
195
|
+
import StatCardEditor, { allTokens as statCardTokens } from './system/components/StatCardEditor.svelte';
|
|
196
|
+
|
|
197
|
+
registerComponent({
|
|
198
|
+
id: 'statcard',
|
|
199
|
+
label: 'Stat Card',
|
|
200
|
+
icon: 'fas fa-chart-simple',
|
|
201
|
+
sourceFile: 'src/system/components/StatCard.svelte',
|
|
202
|
+
editorComponent: StatCardEditor,
|
|
203
|
+
schema: statCardTokens,
|
|
204
|
+
});
|
|
159
205
|
```
|
|
160
206
|
|
|
161
|
-
|
|
207
|
+
Import the module from `src/main.ts`, before `bootLiveTokens` or `mount`, and name it as `registrySetup` in `live-tokens.testing.ts`. One module then serves the running app and `check-component --tests`, which imports it to see the registration without mounting the app: read `references/contract-tests.md`. A component that declares intrinsics adds `intrinsics` to the entry. `check-component` finds the registration by the id literal inside the call.
|
|
208
|
+
|
|
209
|
+
At boot the plugin reads the `:global(:root)` block and writes `component-configs/<id>/default.json`, one token per property. An edit in the editor writes `_working.json`; Save As writes a named config. The assignments stay token references through that flow.
|
|
162
210
|
|
|
163
|
-
|
|
211
|
+
Inside the live-tokens repository, a first-party component keeps its editor in `src/editor/component-editor/` and takes an entry in `builtInRegistry` in `src/editor/component-editor/registry.ts`.
|
|
164
212
|
|
|
165
|
-
##
|
|
213
|
+
## Sketch mode and overlays
|
|
166
214
|
|
|
167
|
-
|
|
215
|
+
Every component joins the sketch layer: read `references/sketch-mode.md`. A suitable root or inner wrapper carries a reserved class and names five `--sketch-*` values from its own properties. Preserve positioning, clipping, and pseudo-elements as the reference specifies. A first-party component adds a `PartSpec` row instead.
|
|
168
216
|
|
|
169
|
-
|
|
170
|
-
- `references/intrinsics.md`: structural or display choices that are not token values (an alignment, an element's visibility), where the runtime default and the editor's read-back must agree.
|
|
171
|
-
- `references/sketch-mode.md`: joining the sketch layer. **Every component needs this.** One class on the root, the five `--sketch-*` values the layer draws with, and the list of what it takes over from the element. Skip it and the component stays crisp while the page around it goes hand-drawn.
|
|
217
|
+
A fixed overlay portals to `<body>`: read `references/fixed-overlays.md`. A container that owns the typography of its content follows `Card` and its `prose` prop.
|
|
172
218
|
|
|
173
|
-
## Verification
|
|
219
|
+
## Verification
|
|
174
220
|
|
|
175
|
-
|
|
221
|
+
1. Run **live-tokens-check-compliance** and address its findings with **live-tokens-fix-findings**. Then run `npx live-tokens check-component <id> --tests --strict --json`. Inside the live-tokens repository, run `node bin/cli.mjs check-component <id> --tests --strict --json`. Each finding carries a rule id and a line; `--off=<rule>` silences a rule for one run. `--tests` runs the registry contract and the component contract suites, and its JSON reports coverage by rule. A `tests-not-installed` finding names the missing package; install `@playwright/test`, `vitest`, and `happy-dom` as devDependencies, then `npx playwright install chromium`. Fix every finding and rerun until exit 0 with complete applicable coverage and no disabled checks.
|
|
222
|
+
2. Run the project's Svelte check and its build.
|
|
223
|
+
3. Reply with the files, the component id, the props, and the results of steps 1 and 2, naming any check the environment prevented.
|
|
176
224
|
|
|
177
|
-
|
|
225
|
+
Every finding carries a `fix` slug naming the section that fixes it.
|
|
178
226
|
|
|
179
|
-
|
|
227
|
+
| `fix` | Section |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `property-name` | Property design, the name |
|
|
230
|
+
| `property-token` | Property design, the assigned token |
|
|
231
|
+
| `runtime` | Runtime component |
|
|
232
|
+
| `runtime-defaults` | Runtime component, the `:global(:root)` defaults |
|
|
233
|
+
| `editor` | Component editor |
|
|
234
|
+
| `registration` | Registration |
|
|
235
|
+
| `sketch` | Sketch mode and overlays |
|
|
236
|
+
| `tooling` | The message names the missing tool or the broken path; fix it and rerun |
|
|
237
|
+
| `coverage` | Add the missing contract, or complete the run the message names |
|
|
180
238
|
|
|
181
|
-
|
|
239
|
+
`--tests` covers every line a reviewer once checked by eye: the component's listing, its controls and preview, persistence and reset, theme projection, linked properties, and Sketch mode.
|
|
182
240
|
|
|
183
|
-
|
|
184
|
-
- [ ] Token rows render. Color pickers, radius selectors, font selectors all work.
|
|
185
|
-
- [ ] Linked-block (if your component has linked siblings): shared rows appear with the link toggle. Changing the linked value broadcasts across every variant.
|
|
186
|
-
- [ ] `component-configs/<id>/default.json` is derived from the `:global(:root)` block at boot. Save writes `_working.json`, the unsaved buffer the open theme captures; Save As also writes a named preset.
|
|
187
|
-
- [ ] Reset returns each variable to its `:global(:root)` default.
|
|
188
|
-
- [ ] Boot validation is clean (no warnings about the component being missing from the server scan, or about disk-vs-registry drift).
|
|
189
|
-
- [ ] Switch Sketch mode on in the editor and walk the checklist at the end of `references/sketch-mode.md`. The component is drawn in every variant and on hover, in its own colours, not crisp and not wearing another part's palette. Switch it off again and the component is unchanged.
|
|
241
|
+
Then place the component on a page with **live-tokens-create-page**.
|
|
@@ -1,17 +1,45 @@
|
|
|
1
|
-
# The registry contract as a test in
|
|
1
|
+
# The registry contract as a test in a consumer project
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
one registry entry and returns a violation line per failure; an
|
|
6
|
-
the pass.
|
|
3
|
+
The package ships the contract as a test file. `checkRegistryEntry` is the
|
|
4
|
+
assertion behind it, exported so a project can write its own file instead. The
|
|
5
|
+
contract takes one registry entry and returns a violation line per failure; an
|
|
6
|
+
empty array is the pass.
|
|
7
|
+
|
|
8
|
+
## The shipped path
|
|
9
|
+
|
|
10
|
+
`npx live-tokens check-component <id> --tests` runs the shipped file for you,
|
|
11
|
+
under vitest, alongside the Playwright component contract suites, and maps
|
|
12
|
+
every failure to a finding with a rule id and a line. The compiled file is
|
|
13
|
+
`src/testing-js/registry.contract.js`; the `.ts` source it compiles from is not
|
|
14
|
+
in the tarball.
|
|
15
|
+
|
|
16
|
+
It resolves a shipped component's `sourceFile` against the package and yours
|
|
17
|
+
against your project, and reports a component that exists as files and never
|
|
18
|
+
reached a registration.
|
|
19
|
+
|
|
20
|
+
Add `@playwright/test`, `vitest`, and `happy-dom` as devDependencies, then
|
|
21
|
+
`npx playwright install chromium`. A missing one is a `tests-not-installed`
|
|
22
|
+
finding naming the install command.
|
|
23
|
+
|
|
24
|
+
Name the module that registers your components, in `live-tokens.testing.ts` at
|
|
25
|
+
the project root, as a plain quoted string:
|
|
7
26
|
|
|
8
27
|
```ts
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
// live-tokens.testing.ts
|
|
29
|
+
import { defineTestingConfig } from '@motion-proto/live-tokens/testing/vitest';
|
|
30
|
+
|
|
31
|
+
export default defineTestingConfig({
|
|
32
|
+
registrySetup: 'src/registerComponents.ts',
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The setup module registers and stops there, exactly as the Registration
|
|
37
|
+
section of live-tokens-create-component wires it up:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// src/registerComponents.ts
|
|
41
|
+
import { registerComponent } from '@motion-proto/live-tokens';
|
|
42
|
+
import MyWidgetEditor, { allTokens } from './system/components/MyWidgetEditor.svelte';
|
|
15
43
|
|
|
16
44
|
registerComponent({
|
|
17
45
|
id: 'mywidget',
|
|
@@ -21,53 +49,68 @@ registerComponent({
|
|
|
21
49
|
editorComponent: MyWidgetEditor,
|
|
22
50
|
schema: allTokens,
|
|
23
51
|
});
|
|
52
|
+
```
|
|
24
53
|
|
|
25
|
-
|
|
54
|
+
Import the same module from `src/main.ts`, so one list of registrations serves
|
|
55
|
+
the app and the tests. Importing an editor registers nothing, and importing
|
|
56
|
+
`main.ts` would mount the app, which is why the registrations live in a module
|
|
57
|
+
of their own.
|
|
26
58
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```
|
|
59
|
+
`LIVE_TOKENS_COMPONENT=<id>` is what `check-component <id> --tests` sets for
|
|
60
|
+
you; it narrows the run to one component and fails when no component is
|
|
61
|
+
registered under that id.
|
|
62
|
+
|
|
63
|
+
## Running vitest yourself
|
|
33
64
|
|
|
34
|
-
|
|
65
|
+
`check-component --tests` covers the shipped path. Run vitest directly only
|
|
66
|
+
when you need to drive it outside the CLI. `createVitestConfig` lives at
|
|
67
|
+
`@motion-proto/live-tokens/testing/vitest`, which never imports
|
|
68
|
+
`@playwright/test`, so a project holding only `vitest` and `happy-dom` can
|
|
69
|
+
still build this config:
|
|
35
70
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
yours. Without the filter every built-in fails on a path that does not exist
|
|
42
|
-
in your project.
|
|
71
|
+
```ts
|
|
72
|
+
// vitest.contract.config.ts
|
|
73
|
+
import { createVitestConfig } from '@motion-proto/live-tokens/testing/vitest';
|
|
74
|
+
import viteConfig from './vite.config';
|
|
75
|
+
import settings from './live-tokens.testing';
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
export default createVitestConfig(viteConfig, { registrySetup: settings.registrySetup });
|
|
78
|
+
```
|
|
45
79
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
its own subpath.
|
|
80
|
+
```bash
|
|
81
|
+
npx vitest run --config vitest.contract.config.ts
|
|
82
|
+
```
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`
|
|
54
|
-
|
|
84
|
+
## Writing your own file
|
|
85
|
+
|
|
86
|
+
`checkRegistryEntry` is exported at
|
|
87
|
+
`@motion-proto/live-tokens/component-editor/contract`, so a project that wants
|
|
88
|
+
its own suite writes two lines against its own registrations:
|
|
55
89
|
|
|
56
90
|
```ts
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
import
|
|
91
|
+
// tests/registryContract.test.ts
|
|
92
|
+
// @vitest-environment happy-dom
|
|
93
|
+
import { describe, it, expect } from 'vitest';
|
|
94
|
+
import { getComponentRegistryEntries } from '@motion-proto/live-tokens';
|
|
95
|
+
import { checkRegistryEntry } from '@motion-proto/live-tokens/component-editor/contract';
|
|
96
|
+
import '../src/registerComponents';
|
|
97
|
+
|
|
98
|
+
const mine = getComponentRegistryEntries().filter((e) => e.origin === 'custom');
|
|
60
99
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
100
|
+
describe.each(mine.map((e) => [e.id, e] as const))('%s', (_id, entry) => {
|
|
101
|
+
it('meets the registry contract', () => {
|
|
102
|
+
expect(checkRegistryEntry(entry)).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
69
105
|
```
|
|
70
106
|
|
|
107
|
+
Filter on `origin`. The registry always carries the shipped components too, and
|
|
108
|
+
their `sourceFile` paths are relative to the package root. Without the filter
|
|
109
|
+
every built-in fails on a path that does not exist in your project.
|
|
110
|
+
|
|
111
|
+
The helper reads the runtime file and `default.json` off disk, which is why it
|
|
112
|
+
is node-only and has its own subpath.
|
|
113
|
+
|
|
71
114
|
## Paths
|
|
72
115
|
|
|
73
116
|
Paths resolve against `process.cwd()` and `src/live-tokens/data/component-configs`.
|
|
@@ -81,17 +124,17 @@ checkRegistryEntry(entry, { projectRoot, componentConfigsDir });
|
|
|
81
124
|
|
|
82
125
|
## What it holds
|
|
83
126
|
|
|
84
|
-
1. **Registration
|
|
85
|
-
2. **Uniqueness
|
|
86
|
-
3. **Editor to runtime
|
|
127
|
+
1. **Registration**: `sourceFile` resolves to a real file, the schema is non-empty.
|
|
128
|
+
2. **Uniqueness**: no schema variable is declared twice.
|
|
129
|
+
3. **Editor to runtime**: every editable token's CSS var is declared in the
|
|
87
130
|
runtime's `<style>` block, so an edit has something to repaint.
|
|
88
|
-
4. **Editor to default config
|
|
131
|
+
4. **Editor to default config**: every editable token has a seed alias in
|
|
89
132
|
`component-configs/<id>/default.json`, so the component adopts with full
|
|
90
133
|
defaults. A component with no `default.json` is editor-only; this check and
|
|
91
134
|
the next one skip it.
|
|
92
|
-
5. **Opacity floors
|
|
135
|
+
5. **Opacity floors**: a token declaring `minOpacity` ships a default at or
|
|
93
136
|
above it, so a floating panel starts out legible over page content.
|
|
94
|
-
6. **Round-trip
|
|
137
|
+
6. **Round-trip**: `setComponentAlias` persists into the slice under the same key.
|
|
95
138
|
|
|
96
139
|
Checks 3 and 4 exclude `hidden: true` tokens, `kind: 'gradient'` tokens (stored
|
|
97
140
|
as gradient objects, not vars), and `-padding-(top|right|bottom|left)` suffixes
|