@motion-proto/live-tokens 0.70.0 → 0.72.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.
Files changed (66) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +78 -0
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +2 -2
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +99 -0
  4. package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +1 -2
  5. package/.claude/skills/live-tokens-create-component/references/token-naming.md +6 -0
  6. package/.claude/skills/live-tokens-fix-findings/SKILL.md +5 -2
  7. package/CHANGELOG.md +100 -0
  8. package/README.md +9 -2
  9. package/bin/check-component.mjs +2 -11
  10. package/bin/check-page.mjs +4 -3
  11. package/bin/cli.mjs +29 -1
  12. package/bin/lib/catalogue.mjs +6 -2
  13. package/bin/lib/findings.mjs +17 -0
  14. package/bin/lib/report.mjs +158 -0
  15. package/bin/lib/tokenVocabulary.mjs +20 -0
  16. package/dist-plugin/adjust/index.cjs +33 -6
  17. package/dist-plugin/adjust/index.d.cts +2 -2
  18. package/dist-plugin/adjust/index.d.ts +2 -2
  19. package/dist-plugin/adjust/index.js +7 -6
  20. package/dist-plugin/{chunk-2YNERPXY.js → chunk-RIXO2E55.js} +1 -1
  21. package/dist-plugin/{chunk-RVE3MNKM.js → chunk-W6Y4BWFB.js} +3 -1
  22. package/dist-plugin/{chunk-GPIBU44G.js → chunk-YLCOIGQC.js} +25 -1
  23. package/dist-plugin/{dataPaths-bJTCEO4H.d.ts → dataPaths-BhWzd5cL.d.cts} +4 -0
  24. package/dist-plugin/{dataPaths-bJTCEO4H.d.cts → dataPaths-BhWzd5cL.d.ts} +4 -0
  25. package/dist-plugin/fontPairing/index.cjs +3 -1
  26. package/dist-plugin/fontPairing/index.d.cts +1 -1
  27. package/dist-plugin/fontPairing/index.d.ts +1 -1
  28. package/dist-plugin/fontPairing/index.js +1 -1
  29. package/dist-plugin/generateColorsAndType/index.cjs +28 -2
  30. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  31. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  32. package/dist-plugin/generateColorsAndType/index.js +2 -2
  33. package/dist-plugin/index.cjs +28 -2
  34. package/dist-plugin/index.d.cts +1 -1
  35. package/dist-plugin/index.d.ts +1 -1
  36. package/dist-plugin/index.js +3 -3
  37. package/dist-plugin/migrateData/index.cjs +28 -2
  38. package/dist-plugin/migrateData/index.d.cts +1 -1
  39. package/dist-plugin/migrateData/index.d.ts +1 -1
  40. package/dist-plugin/migrateData/index.js +3 -3
  41. package/dist-plugin/tokensCssMigrations/index.cjs +3 -1
  42. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  43. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  44. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  45. package/package.json +5 -1
  46. package/src/editor/component-editor/SectionDividerEditor.svelte +1 -11
  47. package/src/editor/component-editor/contract.ts +175 -0
  48. package/src/editor/component-editor/scaffolding/StateBlock.svelte +0 -8
  49. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +5 -0
  50. package/src/editor/component-editor/scaffolding/TypeEditor.svelte +0 -21
  51. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +2 -2
  52. package/src/editor/component-editor/scaffolding/types.ts +1 -7
  53. package/src/editor/core/components/aliasKinds.ts +6 -2
  54. package/src/editor/core/themes/migrations/2026-09-02-sectiondivider-drop-title-outline.ts +39 -0
  55. package/src/editor/core/themes/migrations/index.ts +2 -0
  56. package/src/live-tokens/data/themes/autumn.json +1 -7
  57. package/src/live-tokens/data/themes/halloween.json +1 -7
  58. package/src/live-tokens/data/themes/midnight-study.json +1 -7
  59. package/src/live-tokens/data/themes/ocean.json +1 -7
  60. package/src/live-tokens/data/themes/royal-velvet.json +1 -7
  61. package/src/live-tokens/data/themes/sketchy.json +1 -7
  62. package/src/live-tokens/data/themes/spring-meadow.json +1 -7
  63. package/src/live-tokens/data/themes/sunset.json +1 -7
  64. package/src/system/components/SectionDivider.svelte +3 -143
  65. package/src/system/components/FloatingTokenTags.css +0 -284
  66. package/src/system/components/FloatingTokenTags.svelte +0 -570
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: live-tokens-check-compliance
3
+ description: Check an existing @motion-proto/live-tokens project against its design system and report, without changing a file: which tokens each component reads, which page renders which component, what the two checkers find, and a list of recommended fixes handed to live-tokens-fix-findings. Use when the user asks to check, audit, validate, or review the project, a page, or a component against the design system; asks how compliant it is, what is off, or what it would take to make the build pass; or wants a look before an upgrade. Not for making the changes (live-tokens-fix-findings), and not for a single token (use the editor).
4
+ ---
5
+
6
+ # Checking a project against its design system
7
+
8
+ The answer to "check this project" is a report, and every fact in it comes
9
+ from one command. This skill runs it, reads it, and says what the facts mean
10
+ and what fixing them would involve. It edits nothing. When the user wants the
11
+ changes made, that is **live-tokens-fix-findings**, and the report is what it
12
+ starts from.
13
+
14
+ ## Run the report
15
+
16
+ ```sh
17
+ npx live-tokens report --json
18
+ ```
19
+
20
+ It always exits 0: it is a reading, not a gate. Unknown command means the
21
+ installed package predates it; upgrade `@motion-proto/live-tokens` first. The
22
+ sections, in the order the report gives them:
23
+
24
+ | Section | Fact | What it means when it is not clean |
25
+ | --- | --- | --- |
26
+ | `migrations` | Whether `tokens.css` is behind the installed package | A stale file shows up downstream as unknown tokens. This is the first fix, and it is one command: `npx live-tokens migrate --check`, then `--write` (`--tokens <path>` for a tokens.css in an unusual place). |
27
+ | `components[].unread` | Tokens a component declares that nothing in its file reads | An editor row that edits nothing. Each is a token to wire into the CSS or to remove. |
28
+ | `components[].registered` | A component file with no `bootLiveTokens` or `registerComponent` entry | It renders on the page but has no editor. |
29
+ | `components[].described` | Whether the runtime file has the header comment the picker reads | Without one, `live-tokens components` cannot say what it is for. |
30
+ | `usage.byPage` | Which catalogue component each page renders, and how many times | A page rendering none is either chrome or hand-rolled markup that a shipped component covers. |
31
+ | `usage.unusedShipped` | Shipped components no page renders | Information, not a finding. |
32
+ | `usage.customUnregistered`, `usage.customUnused` | The project's own components that are unregistered or unused | Dead or half-wired work. |
33
+ | `findings.pages`, `findings.components` | Both checkers' findings by rule, under the project's severities and again under `--strict` | The errors are what fails the build today; the strict count is what a fully tokenized project would fail. |
34
+
35
+ `npx live-tokens components <id>` and `npx live-tokens tokens --family <name>`
36
+ (both take `--json`) answer any question the report raises about one
37
+ component or one scale.
38
+
39
+ ## Read it
40
+
41
+ Facts are the report's; the reading is yours. For each rule with findings, say
42
+ in a line what the rule holds and which of two kinds the fix is:
43
+
44
+ - **Mechanical**: a spacing literal to its nearest `--space-*` step, a stroke
45
+ to `--border-width-*`, a hardcoded column count to `var(--columns-count)`,
46
+ `site.css` moved out of `main.ts`, a route given its `source`. Name any
47
+ visible shift, such as a `14px` margin becoming `16px`.
48
+ - **Judgement**: a colour literal mapped by the role it plays rather than its
49
+ hue, a raw type axis set from a text style, a prop the component does not
50
+ declare mapped or dropped. Say what the choice is, not what you would pick.
51
+
52
+ Where a finding looks deliberate, a translucent overlay on an app shell or a
53
+ layout size the project owns, say so and name the config entry that would
54
+ record the decision: `"checks": { "rules": { "<rule>": "warn" } }` in
55
+ `live-tokens.config.json`. Where a whole file is not a themed surface at all,
56
+ hand-tuned artwork or vendored CSS, the entry is
57
+ `"checks": { "exclude": ["src/art/hero.css"] }` — a project-relative path, a
58
+ directory covering what is under it, and naming the file on the command line
59
+ still checks it. Prefer the narrower one: an exclusion drops one file, a
60
+ severity change drops a rule everywhere. Recording either is the user's call,
61
+ not yours.
62
+
63
+ ## Report
64
+
65
+ In this order, each line carrying its count:
66
+
67
+ 1. Migrations pending, and the one command that clears them.
68
+ 2. What fails the build now: errors by rule, with the files.
69
+ 3. What `--strict` would add: warnings by rule.
70
+ 4. Components: unread tokens, unregistered, undescribed.
71
+ 5. Usage: what each page renders, and what is used nowhere.
72
+ 6. Recommended fixes, in the order **live-tokens-fix-findings** would take
73
+ them: migrations, then the largest group of errors, then the rest, then
74
+ warnings. Mark each as mechanical or judgement.
75
+
76
+ End with the hand-off: "Run live-tokens-fix-findings to apply these", or the
77
+ subset the user chooses. Do not start applying them here, even when the fix is
78
+ one line, because the user asked how things stand.
@@ -230,9 +230,9 @@ It *warns* (non-fatal) when a token-backed default still carries a px or rem ter
230
230
 
231
231
  Exit code 0 means the static contract is met. Resolve warnings before shipping, or run with `--strict` to make them fail. `--json` prints findings with a stable `rule` id, so you can work through one rule at a time and re-run.
232
232
 
233
- **Then run the registry contract test.** If you're authoring inside the package itself, `src/editor/component-editor/registryContract.test.ts` runs `describe.each(getComponentRegistryEntries())` and verifies, per component, that the registration resolves to a real `sourceFile` and a non-empty schema, that schema variables are unique, that every editable token (excluding `hidden: true`, `kind: 'gradient'`, and padding-side suffixes) is declared in the runtime `<style>` block and seeded in `src/live-tokens/data/component-configs/<id>/default.json`, and that `setComponentAlias` round-trips the alias through the slice.
233
+ **Then run the registry contract test.** `checkRegistryEntry`, from `@motion-proto/live-tokens/component-editor/contract`, takes one registry entry and returns a violation line per failure, so a suite over your own components is a `describe.each` and one call. Per component it verifies that the registration resolves to a real `sourceFile` and a non-empty schema, that schema variables are unique, that every editable token is declared in the runtime `<style>` block and seeded in `component-configs/<id>/default.json`, that a token declaring `minOpacity` seeds at or above its floor, and that `setComponentAlias` round-trips the alias through the slice. The test file, the two lines in it that are load-bearing, and the path options are in `references/contract-tests.md`.
234
234
 
235
- A new first-party component is auto-covered the moment it lands in `builtInRegistry` — `npm test` will fail if any of the five checks miss. For a consumer-authored component, mirror this pattern in your own test suite if you want the same drift protection: `getComponentRegistryEntries` is exported from `@motion-proto/live-tokens` and returns every registration, shipped and custom, once your `main.ts` has run.
235
+ Inside the package, `src/editor/component-editor/registryContract.test.ts` is that same file over `builtInRegistry`, so a new first-party component is auto-covered the moment it lands there — `npm test` fails if any check misses.
236
236
 
237
237
  **If your component declares `intrinsics`, the intrinsics contract test covers it too.** `src/editor/component-editor/intrinsicsContract.test.ts` iterates every entry with an `intrinsics` array and asserts, per (intrinsic, variant), that the runtime `:global(:root)` declares a default, the default is one of the spec's `values`, and the editor's `default` equals the runtime default. This is what would have caught a getter defaulting to `center` while `:global(:root)` says `start`. Same auto-coverage rule: declare `intrinsics` on the registry entry and the test picks it up.
238
238
 
@@ -0,0 +1,99 @@
1
+ # The registry contract as a test in your own project
2
+
3
+ `checkRegistryEntry` is the contract the package holds its own 26 components
4
+ to, exported so a project outside the package can run it over its own. It takes
5
+ one registry entry and returns a violation line per failure; an empty array is
6
+ the pass.
7
+
8
+ ```ts
9
+ // tests/registryContract.test.ts
10
+ // @vitest-environment happy-dom
11
+ import { describe, it, expect } from 'vitest';
12
+ import { getComponentRegistryEntries, registerComponent } from '@motion-proto/live-tokens';
13
+ import { checkRegistryEntry } from '@motion-proto/live-tokens/component-editor/contract';
14
+ import MyWidgetEditor, { allTokens } from '../src/system/components/MyWidgetEditor.svelte';
15
+
16
+ registerComponent({
17
+ id: 'mywidget',
18
+ label: 'My Widget',
19
+ icon: 'fas fa-magic',
20
+ sourceFile: 'src/system/components/MyWidget.svelte',
21
+ editorComponent: MyWidgetEditor,
22
+ schema: allTokens,
23
+ });
24
+
25
+ const mine = getComponentRegistryEntries().filter((e) => e.origin === 'custom');
26
+
27
+ describe.each(mine.map((e) => [e.id, e] as const))('%s', (_id, entry) => {
28
+ it('meets the registry contract', () => {
29
+ expect(checkRegistryEntry(entry)).toEqual([]);
30
+ });
31
+ });
32
+ ```
33
+
34
+ Two lines there are load-bearing.
35
+
36
+ - **Register at the top of the test file**, rather than importing `main.ts`.
37
+ The entries have to exist before `describe.each` reads them, and
38
+ `bootLiveTokens` would mount the app.
39
+ - **Filter on `origin`.** The registry always carries the shipped components
40
+ too, and their `sourceFile` paths are relative to the package root, not
41
+ yours. Without the filter every built-in fails on a path that does not exist
42
+ in your project.
43
+
44
+ ## Setup
45
+
46
+ `vitest` and `happy-dom` as devDependencies, and the svelte plugin already in
47
+ `vite.config.ts` so the editor `.svelte` import resolves. The helper reads the
48
+ runtime file and `default.json` off disk, which is why it is node-only and has
49
+ its own subpath.
50
+
51
+ The package ships Svelte and TypeScript source, and `bootLiveTokens` imports the
52
+ FontAwesome stylesheet. Left external, Node meets that `.css` and stops with
53
+ `Unknown file extension ".css"`, before a single test runs. Inline both so Vite
54
+ transforms them:
55
+
56
+ ```ts
57
+ // vitest.config.ts
58
+ import { defineConfig, mergeConfig } from 'vitest/config';
59
+ import viteConfig from './vite.config';
60
+
61
+ export default mergeConfig(
62
+ viteConfig,
63
+ defineConfig({
64
+ test: {
65
+ server: { deps: { inline: [/@motion-proto\/live-tokens/, /@fortawesome/] } },
66
+ },
67
+ }),
68
+ );
69
+ ```
70
+
71
+ ## Paths
72
+
73
+ Paths resolve against `process.cwd()` and `src/live-tokens/data/component-configs`.
74
+ A project that moved either passes them:
75
+
76
+ ```ts
77
+ checkRegistryEntry(entry, { projectRoot, componentConfigsDir });
78
+ ```
79
+
80
+ `componentConfigsDir` is the same directory `live-tokens.config.json` names.
81
+
82
+ ## What it holds
83
+
84
+ 1. **Registration** — `sourceFile` resolves to a real file, the schema is non-empty.
85
+ 2. **Uniqueness** — no schema variable is declared twice.
86
+ 3. **Editor to runtime** — every editable token's CSS var is declared in the
87
+ runtime's `<style>` block, so an edit has something to repaint.
88
+ 4. **Editor to default config** — every editable token has a seed alias in
89
+ `component-configs/<id>/default.json`, so the component adopts with full
90
+ defaults. A component with no `default.json` is editor-only; this check and
91
+ the next one skip it.
92
+ 5. **Opacity floors** — a token declaring `minOpacity` ships a default at or
93
+ above it, so a floating panel starts out legible over page content.
94
+ 6. **Round-trip** — `setComponentAlias` persists into the slice under the same key.
95
+
96
+ Checks 3 and 4 exclude `hidden: true` tokens, `kind: 'gradient'` tokens (stored
97
+ as gradient objects, not vars), and `-padding-(top|right|bottom|left)` suffixes
98
+ (written on demand by the split-padding UI and read through the `themed-padding`
99
+ mixin's fallback chain, so they exist as neither `:root` declarations nor seeds).
@@ -176,8 +176,7 @@ inherits, so one declaration covers everything under it:
176
176
 
177
177
  Travel is stated in px against a glyph whose size the layer cannot know, so the
178
178
  dial that suits a card's worth of artwork tears a 16px icon apart. Reach for the
179
- soft bank before reaching for `none`. The shipped `SectionDivider` is the worked
180
- example.
179
+ soft bank before reaching for `none`.
181
180
 
182
181
  ## First-party components
183
182
 
@@ -64,6 +64,12 @@ to that list, so the two cannot drift apart.
64
64
  | `-padding` | Internal spacing |
65
65
  | `-gap` | Spacing between sibling elements |
66
66
 
67
+ `-width`, `-height` and `-size` are the fall-through: any dimension with no
68
+ more specific name behind it. They read the `--space-*` scale through the same
69
+ picker `-gap` uses, and they match last, so `-border-width`, `-divider-height`,
70
+ `-icon-size` and the rest claim their token first. Reach for the specific name
71
+ when one fits; a stroke is `-border-width` even where the CSS says `outline:`.
72
+
67
73
  ## Typography
68
74
 
69
75
  | Suffix | Meaning |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: live-tokens-fix-findings
3
- description: Bring an existing @motion-proto/live-tokens project into line with its design system by running check-page and check-component, reading the findings, and fixing each by rule until both exit 0. Use when the user asks to make the build pass, fix the design-system errors or warnings, clean up the literals, replace hex or pixel values with tokens, make a page or component themeable, audit the site or a file against the design system, or asks why check:design fails. Not for building a new page (live-tokens-build-page) or a new component (live-tokens-create-component), which run the same gate as their last step, and not for a single token edit (use the editor).
3
+ description: Bring an existing @motion-proto/live-tokens project into line with its design system by running check-page and check-component, reading the findings, and fixing each by rule until both exit 0. Use when the user asks to make the build pass, fix the design-system errors or warnings, clean up the literals, replace hex or pixel values with tokens, make a page or component themeable, or apply what a check reported. Not for the check itself (live-tokens-check-compliance reports and edits nothing), not for building a new page (live-tokens-build-page) or a new component (live-tokens-create-component), which run the same gate as their last step, and not for a single token edit (use the editor).
4
4
  ---
5
5
 
6
6
  # Fixing what the checkers report
@@ -14,7 +14,10 @@ passes repaints when the theme changes. One that does not has opted out of the
14
14
  system silently, and these findings are where.
15
15
 
16
16
  This skill is the loop for code that already exists. Run the checker, fix one
17
- rule at a time, run it again, and stop only when both exit 0.
17
+ rule at a time, run it again, and stop only when both exit 0. When the user has
18
+ not seen the state of the project yet, `npx live-tokens report --json` is the
19
+ reading to start from, and **live-tokens-check-compliance** is the skill that
20
+ presents it without editing; this one edits.
18
21
 
19
22
  ## Reach the checkers
20
23
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.72.0 — The contract a consumer can run
4
+
5
+ ### Added
6
+
7
+ - **The registry contract is a function a consumer can run.** The six
8
+ per-component checks that hold this package's own components — registration
9
+ resolves, schema variables are unique, every editable token is declared in
10
+ the runtime `<style>` and seeded in `default.json`, a declared `minOpacity`
11
+ is honoured, `setComponentAlias` round-trips — are now `checkRegistryEntry`,
12
+ exported from `@motion-proto/live-tokens/component-editor/contract`. It takes
13
+ one registry entry and returns a violation line per failure, so a project
14
+ outside this package holds its own components to the same contract with a
15
+ `describe.each` and one call. Paths resolve against `process.cwd()` and
16
+ `src/live-tokens/data/component-configs`, or the `projectRoot` and
17
+ `componentConfigsDir` passed in. Node-only, hence its own subpath.
18
+ `live-tokens-create-component` carries the test file in
19
+ `references/contract-tests.md`; filtering the registry on
20
+ `origin === 'custom'` is the line that matters, since the shipped entries
21
+ name paths relative to the package root.
22
+
23
+ - **`"checks": { "exclude": [...] }` in `live-tokens.config.json`.** Paths the
24
+ checkers skip when they discover their own targets: a project-relative path,
25
+ a directory covering what is under it. For a file that is not a themed
26
+ surface at all — hand-tuned artwork, vendored CSS — where the only other way
27
+ out was downgrading a rule for the whole project. Naming the file on the
28
+ command line still checks it, so the escape hatch cannot hide a file from
29
+ someone looking straight at it. The dev plugin now also recognises `checks`
30
+ and `componentDirs`, which the CLI has read for some time while the plugin
31
+ warned they were unknown keys.
32
+
33
+ ### Changed (breaking)
34
+
35
+ - **`FloatingTokenTags` is demo artwork, not a shipped component.** It moved to
36
+ `src/demo/`, so it leaves the published package, the component query and the
37
+ count, which now agree with the registry at 26. **Breaking for anyone
38
+ importing `@motion-proto/live-tokens/components/FloatingTokenTags.svelte`;**
39
+ it was only ever the hero animation of the demo, and it never had a
40
+ `:global(:root)` block, an editor, or a registry entry.
41
+
42
+ - **The SectionDivider title outline is removed.** It was the only reason the title was an
43
+ SVG `<text>` behind a `feMorphology` filter rather than an element, and that
44
+ SVG carried a `getBBox()` viewBox, a per-instance `MutationObserver` on the
45
+ document's inline style (filter primitives cannot read a CSS var, so the
46
+ resolved width and colour had to be read back and pushed onto them by hand),
47
+ font-load listeners to re-measure, and a title that was neither selectable nor
48
+ findable. It shipped transparent in the default and in all eight presets, and
49
+ the trap-out it was built for is done by the layout: the `through-label`
50
+ hairlines flank the title in a flex row, so no rule ever runs behind the
51
+ glyphs. The title is now a span that inherits typography like every other
52
+ component's. `--sectiondivider-{lg,md,sm}-title-outline-width` and
53
+ `-title-outline-color` are dropped by a component-config migration; the
54
+ `outlineWidthVariable` / `outlineColorVariable` rows on `TypeGroupConfig` go
55
+ with them, since nothing else declared one.
56
+
57
+ ### Changed
58
+
59
+ - **`registered` is read from the registry for shipped components too.** The
60
+ query assumed a component under `src/system/components` was registered, so
61
+ `live-tokens components` could name one the editor cannot open and
62
+ pick-component does not list. It now reads the package's own
63
+ `builtInRegistry`, the same parse `check-component` uses, which the two now
64
+ share. A component that is discovered but registered nowhere reports
65
+ `registered: false`, prints `(NOT registered)`, and is counted out of the
66
+ catalogue in the summary line.
67
+
68
+ ### Fixed
69
+
70
+ - **A bare `-width`, `-height` or `-size` no longer renders a colour picker.**
71
+ The three sat in `KIND_RULES` under `surface`, so `--widget-panel-width` drew
72
+ a palette while the naming vocabulary documented it as geometry. They are now
73
+ a `length` kind, matched last among the geometry rules so `-border-width`,
74
+ `-divider-height`, `-icon-size` and the rest still claim their token first,
75
+ and drawn with the `--space-*` picker `-gap` uses. The suffix vocabulary is
76
+ unchanged, so no token is renamed and no project has to migrate. `adjust`
77
+ does not take the kind: its ladders are density and shape, and a panel width
78
+ is neither.
79
+
80
+ ## 0.71.0 — Check this project
81
+
82
+ ### Added
83
+
84
+ - **`npx live-tokens report` is the project as facts.** Pending `tokens.css`
85
+ migrations, the tokens each component declares and how many its own CSS
86
+ reads (a read counts a `var()`, a `style:` directive, a padding mixin's
87
+ string, or an SCSS-interpolated pattern), whether a component is registered
88
+ and carries the description comment the picker reads, which page renders
89
+ which component and how many times, the shipped and custom components used
90
+ nowhere, and both checkers' findings by rule under the project's severities
91
+ and again under `--strict`. It always exits 0: a reading, not a gate. `--json`
92
+ for data.
93
+
94
+ - **`live-tokens-check-compliance`, the eighth skill.** "Check this project"
95
+ runs the report and presents it without editing a file: what fails the
96
+ build now, what `--strict` would add, the components and usage facts, and a
97
+ list of recommended fixes marked mechanical or judgement with any visible
98
+ shift named, handed to `live-tokens-fix-findings`. A finding that looks
99
+ deliberate is flagged with the config entry that would record the decision,
100
+ which stays the user's call. `live-tokens-fix-findings` no longer claims the
101
+ audit wording, and starts from the report when the user has not seen it.
102
+
3
103
  ## 0.70.0 — The registry is a query
4
104
 
5
105
  ### Added
package/README.md CHANGED
@@ -11,7 +11,7 @@ The editor is dev-only. Production builds get plain CSS variables and the compon
11
11
  ## Features
12
12
 
13
13
  - **Live token editing.** Colors, typography, spacing, radii, shadows, motion, palettes, and gradients. Every input writes a CSS variable, so the page repaints with no reload and no build step.
14
- - **Live component editing.** 26 shipped Svelte components (Button, IconButton, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, Panel, InlineEditActions) declare their design-token aliases in a `:global(:root)` block. Rewire an alias from the component's editor and it updates everywhere that component is used, on your real pages.
14
+ - **Live component editing.** 26 shipped Svelte components (Button, IconButton, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, Panel, InlineEditActions, Slider) declare their design-token aliases in a `:global(:root)` block. Rewire an alias from the component's editor and it updates everywhere that component is used, on your real pages.
15
15
  - **Four dev-only routes.** `/live-tokens/editor` for tokens, `/live-tokens/colors` for palettes, `/live-tokens/components` for per-component aliases, `/live-tokens/docs` for the user guide.
16
16
  - **Editor overlay.** Pins to the top right of every dev page and opens the editor in a side panel or floating window, so you edit on the page you are styling. Its "Page Source" button opens the current page's `.svelte` file in VS Code.
17
17
  - **Themes.** A theme is a whole look in one file: colors and type plus a config for every component, stored by value. Loading one changes a single pointer file, and nothing your site ships changes until you Adopt. Export a theme and import it into another project to restore the look in one step.
@@ -325,6 +325,7 @@ npx @motion-proto/live-tokens <command>
325
325
  | `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`. |
326
326
  | `components [id] [--json]` | List every component the project has, shipped and its own, with the props each takes; with an id, its props, variants, tokens, and defaults. |
327
327
  | `tokens [--family <name>] [--json]` | List every theme token the project's `tokens.css` declares, by family, with its value. |
328
+ | `report [--json]` | The project as facts: pending migrations, tokens each component reads, which page renders which component, and both checkers' findings by rule. Always exits 0. |
328
329
  | `check-component [id]` | Validate a component's runtime, editor, and registration against the authoring contract; with no id, every component authored under `src/system/components`. |
329
330
  | `check-page [paths...]` | Validate pages against the build-page contract: catalogue components and their props, theme tokens over literals, route wiring. |
330
331
  | `generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]` | Build a full theme from a 10-seed OKLCH brief, enforce AA contrast, write `themes/<slug>.json`, and open it. |
@@ -336,7 +337,7 @@ Once installed in a project, the same commands are available as `npx live-tokens
336
337
 
337
338
  ## Claude Code skills
338
339
 
339
- The package bundles seven Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, what a valid theme looks like in OKLCH, how two typefaces sit together, how geometry moves along the token scales, and how an existing page or component is brought back into line with all of that. Each triggers from an ordinary request, so there are no slash commands to learn.
340
+ The package bundles eight Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, what a valid theme looks like in OKLCH, how two typefaces sit together, how geometry moves along the token scales, how a project is checked against all of that, and how an existing page or component is brought back into line. Each triggers from an ordinary request, so there are no slash commands to learn.
340
341
 
341
342
  ### Install
342
343
 
@@ -400,6 +401,12 @@ Ask for something the catalogue lacks: "author a Rating component", "make my Chi
400
401
 
401
402
  The skill covers the recipe: the runtime `.svelte` file with its `:global(:root)` token block, the editor `.svelte` file exporting `allTokens` and its variant groups, the `registerComponent()` call, and the catalogue entry that keeps `live-tokens-pick-component` current. It carries the naming scheme, the token suffix vocabulary, the state model (component states such as selected and disabled are separate from interaction states such as hover), and the public-imports rule, and points at the shipped `Toggle` in `node_modules` as the worked example. Linked siblings, intrinsics, and the fixed-overlay portal rule sit in reference files the skill reads only when a component needs them.
402
403
 
404
+ ### `live-tokens-check-compliance`
405
+
406
+ Ask how things stand: "check this project against the design system", "audit the pricing page", "what would it take to make the build pass?", "review this before I upgrade".
407
+
408
+ The skill runs `npx live-tokens report --json`, which is the project as facts: pending `tokens.css` migrations, the tokens each component declares and reads, which page renders which component, and both checkers' findings by rule under the project's severities and under `--strict`. It presents the report, says what each rule holds, marks each recommended fix as mechanical or a judgement call, names any visible shift, and flags a finding that looks deliberate together with the config entry that would record the decision. It edits nothing and ends by handing the list to `live-tokens-fix-findings`.
409
+
403
410
  ### `live-tokens-fix-findings`
404
411
 
405
412
  Ask for the existing code to catch up: "make check:design pass", "fix the design-system warnings", "replace the hex and pixel values with tokens", "why is check-page failing on the pricing page?".
@@ -23,7 +23,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
23
23
  import { extname, join, relative } from 'node:path';
24
24
  import { hasColorLiteral, hasDimensionLiteral, stripVarFallbacks } from './lib/cssValues.mjs';
25
25
  import { lineOf } from './lib/findings.mjs';
26
- import { PKG_ROOT, declaredCustomProperties, extractGlobalRootBlocks, isContractToken, loadVocabulary } from './lib/tokenVocabulary.mjs';
26
+ import { PKG_ROOT, builtInIds, declaredCustomProperties, extractGlobalRootBlocks, isContractToken, loadVocabulary } from './lib/tokenVocabulary.mjs';
27
27
 
28
28
  export const COMPONENT_RULES = {
29
29
  'invalid-id': 'error',
@@ -71,18 +71,9 @@ function readKnownSuffixes(root) {
71
71
  return [];
72
72
  }
73
73
 
74
- const BUILT_IN_REGISTRY = 'src/editor/component-editor/registry.ts';
75
-
76
74
  /** True when `id` is one of the package's own components. */
77
75
  function isBuiltIn(id) {
78
- for (const base of [PKG_ROOT, process.cwd()]) {
79
- const path = join(base, BUILT_IN_REGISTRY);
80
- if (!existsSync(path)) continue;
81
- const src = readFileSync(path, 'utf8');
82
- const block = src.match(/builtInRegistry[^=]*=\s*Object\.freeze\(\{([\s\S]*?)\n\}\);/);
83
- if (block && new RegExp(`\\bid:\\s*'${id}'`).test(block[1])) return true;
84
- }
85
- return false;
76
+ return builtInIds(process.cwd(), PKG_ROOT).has(id);
86
77
  }
87
78
 
88
79
  // Per-side padding names (`--card-body-padding-top`) are written by the padding
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { existsSync, readFileSync, statSync } from 'node:fs';
14
14
  import { join, relative, resolve, basename } from 'node:path';
15
- import { lineOf } from './lib/findings.mjs';
15
+ import { isExcluded, lineOf } from './lib/findings.mjs';
16
16
  import { blankStrings, hasColorLiteral, hasDimensionLiteral, stripVarFallbacks } from './lib/cssValues.mjs';
17
17
  import { isContractToken, loadVocabulary, walk } from './lib/tokenVocabulary.mjs';
18
18
  import { resolveTokensCssPath } from './migrate.mjs';
@@ -35,7 +35,7 @@ export const PAGE_RULES = {
35
35
  // Directories that hold the system, not pages built on it.
36
36
  const NOT_PAGES = ['src/system', 'src/editor', 'src/lib', 'src/live-tokens'];
37
37
 
38
- const COMPONENT_IMPORT =
38
+ export const COMPONENT_IMPORT =
39
39
  /(?:@motion-proto\/live-tokens\/components|[./][^'"]*\/system\/components)\/([A-Za-z0-9]+)\.svelte$/;
40
40
 
41
41
  const DEEP_IMPORT_PATTERNS = [
@@ -382,6 +382,7 @@ export function discoverPages(root) {
382
382
  return walk(src, ['.svelte', '.css', '.ts', '.js']).filter((f) => {
383
383
  const rel = relative(root, f);
384
384
  if (NOT_PAGES.some((d) => rel.startsWith(`${d}/`))) return false;
385
+ if (isExcluded(rel, root)) return false;
385
386
  if (f === tokensCss || TOKEN_SOURCES.includes(basename(f))) return false;
386
387
  if (/\.(test|spec)\.[tj]s$/.test(rel)) return false;
387
388
  if (rel.endsWith('.ts') || rel.endsWith('.js')) return /main\.(ts|js)$/.test(rel);
@@ -396,7 +397,7 @@ export function checkPages(targets, { root = process.cwd(), vocabulary } = {}) {
396
397
  const full = resolve(root, t);
397
398
  if (!existsSync(full)) continue;
398
399
  if (statSync(full).isDirectory()) {
399
- files.push(...walk(full, ['.svelte', '.css']));
400
+ files.push(...walk(full, ['.svelte', '.css']).filter((f) => !isExcluded(relative(root, f), root)));
400
401
  } else {
401
402
  files.push(full);
402
403
  }
package/bin/cli.mjs CHANGED
@@ -5,6 +5,7 @@
5
5
  // setup-claude [--force] Copy bundled Claude Code skills into ./.claude/skills/.
6
6
  // components [id] List every component the project has, shipped and its own, with props and tokens.
7
7
  // tokens [--family <name>] List every theme token by family, with its value.
8
+ // report The project as facts: tokens read, components used, findings by rule. Always exits 0.
8
9
  // check-component [id] Validate a component (or every authored one) against the create-component skill contract.
9
10
  // check-page [paths...] Validate pages against the build-page skill contract.
10
11
  // generate-theme <brief> Build a theme from a 10-seed OKLCH brief and open it.
@@ -19,6 +20,7 @@ import process from 'node:process';
19
20
  import { COMPONENT_RULES, checkComponent, discoverComponents, formatReport } from './check-component.mjs';
20
21
  import { PAGE_RULES, checkPages, discoverPages } from './check-page.mjs';
21
22
  import { describeComponents, describeTokens, formatComponents, formatTokens } from './lib/catalogue.mjs';
23
+ import { buildReport, formatReport as formatProjectReport } from './lib/report.mjs';
22
24
  import { loadVocabulary } from './lib/tokenVocabulary.mjs';
23
25
  import {
24
26
  applySeverity,
@@ -54,6 +56,12 @@ Commands:
54
56
  tokens [--family <name>] [--json]
55
57
  List every theme token the project's tokens.css
56
58
  declares, by family, with its value
59
+ report [--json] The project as facts: pending migrations, tokens
60
+ each component declares and reads, which page
61
+ renders which component, and both checkers'
62
+ findings by rule under the project's severities
63
+ and under --strict. A reading, not a gate: always
64
+ exits 0
57
65
  check-component [id] Validate <id>'s runtime, editor, and registration
58
66
  against the live-tokens-create-component contract
59
67
  check-page [paths...] Validate pages against the live-tokens-build-page
@@ -67,7 +75,8 @@ Both check commands accept:
67
75
  --strict Treat warnings as errors
68
76
  --off=<rule,...> Silence rules; --warn=/--error= change severity
69
77
  (or set "checks": { "rules": {...} } in
70
- live-tokens.config.json)
78
+ live-tokens.config.json; "checks": { "exclude":
79
+ [...] } drops paths from discovery entirely)
71
80
  generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]
72
81
  Build a full theme from a 10-seed OKLCH brief
73
82
  (see the live-tokens-generate-theme skill),
@@ -190,6 +199,24 @@ if (command === 'tokens') {
190
199
  process.exit(0);
191
200
  }
192
201
 
202
+ if (command === 'report') {
203
+ const opts = parseCheckFlags(rest);
204
+ const report = buildReport(loadVocabulary());
205
+ try {
206
+ const plan = await runMigrate({ check: true });
207
+ report.migrations =
208
+ plan.status === 'no-path'
209
+ ? { status: 'no tokens.css' }
210
+ : plan.status === 'would-change'
211
+ ? { status: 'pending', pending: plan.applied ?? plan.migrations ?? [] }
212
+ : { status: 'none pending' };
213
+ } catch {
214
+ report.migrations = { status: 'unavailable (compiled engine not built)' };
215
+ }
216
+ writeOut(opts.json ? JSON.stringify(report, null, 2) : formatProjectReport(report));
217
+ process.exit(0);
218
+ }
219
+
193
220
  if (command === 'check-component') {
194
221
  const opts = parseCheckFlags(rest);
195
222
  const ids = opts.rest.length > 0 ? [opts.rest[0]] : discoverComponents();
@@ -368,6 +395,7 @@ const SAMPLE_PROMPTS = {
368
395
  'live-tokens-adjust-geometry': 'make the buttons pill shaped',
369
396
  'live-tokens-pair-fonts': 'pair some fonts for this theme',
370
397
  'live-tokens-fix-findings': 'make check:design pass',
398
+ 'live-tokens-check-compliance': 'check this project against the design system',
371
399
  };
372
400
 
373
401
  const installedSamples = skills
@@ -44,7 +44,7 @@ export function describeComponents(vocab, { root = process.cwd() } = {}) {
44
44
  name: entry.name,
45
45
  origin: entry.origin,
46
46
  file: relative(root, entry.file),
47
- registered: entry.origin === 'shipped' || vocab.registered.has(entry.id),
47
+ registered: vocab.builtIn.has(entry.id) || vocab.registered.has(entry.id),
48
48
  description: descriptionOf(source),
49
49
  variants: entry.props?.enums.get('variant') ? [...entry.props.enums.get('variant')] : [],
50
50
  props,
@@ -99,7 +99,11 @@ export function formatComponents(list, { id } = {}) {
99
99
  if (c.description) lines.push(`${''.padEnd(29)} ${c.description}`);
100
100
  }
101
101
  lines.push('');
102
- lines.push(`${list.length} component(s). \`live-tokens components <id>\` prints one with its props and tokens.`);
102
+ const unregistered = list.filter((c) => !c.registered).length;
103
+ lines.push(
104
+ `${list.length} component(s)${unregistered ? `, ${unregistered} not registered and so not in the catalogue` : ''}. ` +
105
+ '`live-tokens components <id>` prints one with its props and tokens.',
106
+ );
103
107
  return lines.join('\n');
104
108
  }
105
109
 
@@ -30,6 +30,23 @@ export function readChecksConfig(root) {
30
30
  }
31
31
  }
32
32
 
33
+ /**
34
+ * `checks.exclude` in live-tokens.config.json: paths the checkers do not read
35
+ * when they discover their own targets. Each entry is a project-relative path;
36
+ * a directory entry covers everything under it. For a file that is not a themed
37
+ * surface — hand-tuned artwork, vendored CSS — where the alternative is
38
+ * downgrading a rule for the whole project. Naming the file explicitly on the
39
+ * command line still checks it.
40
+ */
41
+ export function isExcluded(relPath, root) {
42
+ const list = readChecksConfig(root).exclude;
43
+ if (!Array.isArray(list)) return false;
44
+ return list.some((entry) => {
45
+ const e = String(entry).replace(/\/+$/, '');
46
+ return relPath === e || relPath.startsWith(`${e}/`);
47
+ });
48
+ }
49
+
33
50
  /**
34
51
  * Parse `--off=a,b --warn=c --error=d --strict --json` out of argv.
35
52
  * Unrecognised flags are returned in `rest` for the caller to handle.