@motion-proto/live-tokens 0.74.0 → 0.75.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 (170) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +33 -38
  2. package/.claude/skills/live-tokens-create-component/SKILL copy.md +196 -0
  3. package/.claude/skills/live-tokens-create-component/SKILL.md +198 -146
  4. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +95 -52
  5. package/.claude/skills/live-tokens-create-component/references/intrinsics.md +2 -2
  6. package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +2 -2
  7. package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +12 -12
  8. package/.claude/skills/live-tokens-create-component/references/token-naming.md +2 -1
  9. package/.claude/skills/live-tokens-create-page/SKILL.md +187 -0
  10. package/.claude/skills/live-tokens-create-page/references/interaction-sources.md +66 -0
  11. package/.claude/skills/live-tokens-create-page/references/layout-sources.md +87 -0
  12. package/.claude/skills/live-tokens-create-theme/SKILL.md +52 -48
  13. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
  14. package/.claude/skills/live-tokens-fix-findings/SKILL.md +69 -60
  15. package/.claude/skills/live-tokens-pick-component/SKILL.md +64 -79
  16. package/.claude/skills/live-tokens-set-colors/SKILL.md +47 -38
  17. package/.claude/skills/live-tokens-set-geometry/SKILL.md +58 -35
  18. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +5 -3
  19. package/.claude/skills/live-tokens-set-type/SKILL.md +30 -30
  20. package/CHANGELOG.md +103 -0
  21. package/README.md +3 -3
  22. package/bin/check-component.mjs +91 -19
  23. package/bin/check-page.mjs +102 -21
  24. package/bin/cli.mjs +78 -58
  25. package/bin/contractRunner.mjs +945 -0
  26. package/bin/create.mjs +1 -1
  27. package/bin/lib/catalogue.mjs +37 -30
  28. package/bin/lib/findings.mjs +46 -15
  29. package/bin/lib/report.mjs +3 -3
  30. package/bin/lib/tokenVocabulary.mjs +4 -4
  31. package/bin/migrate-routes.mjs +5 -5
  32. package/bin/migrate.mjs +4 -4
  33. package/bin/save-theme.mjs +8 -9
  34. package/bin/set-colors.mjs +9 -11
  35. package/bin/set-geometry.mjs +7 -7
  36. package/bin/set-type.mjs +5 -7
  37. package/dist-plugin/{chunk-W6Y4BWFB.js → chunk-6WGFOJXO.js} +22 -0
  38. package/dist-plugin/{chunk-7VRTBGJT.js → chunk-PDNL4NC5.js} +9 -2
  39. package/dist-plugin/{chunk-V3YF6CGT.js → chunk-SWXRVZKT.js} +43 -1
  40. package/dist-plugin/{dataPaths-BhWzd5cL.d.cts → dataPaths-BpIK_Obx.d.cts} +1 -0
  41. package/dist-plugin/{dataPaths-BhWzd5cL.d.ts → dataPaths-BpIK_Obx.d.ts} +1 -0
  42. package/dist-plugin/index.cjs +310 -147
  43. package/dist-plugin/index.d.cts +1 -1
  44. package/dist-plugin/index.d.ts +1 -1
  45. package/dist-plugin/index.js +218 -122
  46. package/dist-plugin/migrateData/index.cjs +66 -2
  47. package/dist-plugin/migrateData/index.d.cts +1 -1
  48. package/dist-plugin/migrateData/index.d.ts +1 -1
  49. package/dist-plugin/migrateData/index.js +3 -3
  50. package/dist-plugin/setColors/index.cjs +61 -4
  51. package/dist-plugin/setColors/index.d.cts +1 -1
  52. package/dist-plugin/setColors/index.d.ts +1 -1
  53. package/dist-plugin/setColors/index.js +5 -5
  54. package/dist-plugin/setGeometry/index.cjs +107 -44
  55. package/dist-plugin/setGeometry/index.d.cts +5 -5
  56. package/dist-plugin/setGeometry/index.d.ts +5 -5
  57. package/dist-plugin/setGeometry/index.js +51 -45
  58. package/dist-plugin/setType/index.cjs +15 -0
  59. package/dist-plugin/setType/index.d.cts +1 -1
  60. package/dist-plugin/setType/index.d.ts +1 -1
  61. package/dist-plugin/setType/index.js +1 -1
  62. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  63. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  64. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  65. package/package.json +35 -8
  66. package/src/app/site.css +19 -9
  67. package/src/editor/bootstrap.ts +2 -0
  68. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +4 -4
  69. package/src/editor/component-editor/DialogEditor.svelte +4 -4
  70. package/src/editor/component-editor/MenuSelectEditor.svelte +4 -1
  71. package/src/editor/component-editor/SegmentedControlEditor.svelte +6 -1
  72. package/src/editor/component-editor/TabBarEditor.svelte +1 -1
  73. package/src/editor/component-editor/TableEditor.svelte +2 -2
  74. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +6 -3
  75. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +41 -20
  76. package/src/editor/core/components/adjustAliases.ts +59 -45
  77. package/src/editor/core/components/aliasKinds.ts +9 -5
  78. package/src/editor/core/preview/themePreview.ts +9 -2
  79. package/src/editor/core/sketch/sketchLayer.ts +22 -0
  80. package/src/editor/core/store/editorStore.ts +10 -1
  81. package/src/editor/core/themes/buildColors.ts +3 -3
  82. package/src/editor/core/themes/liveStateStream.ts +26 -0
  83. package/src/editor/core/themes/migrations/2026-09-07-stroke-role-renames.ts +52 -0
  84. package/src/editor/core/themes/migrations/index.ts +2 -0
  85. package/src/editor/core/themes/themeDocumentSync.ts +22 -11
  86. package/src/editor/core/themes/themeService.ts +9 -2
  87. package/src/editor/pages/ComponentEditorPage.svelte +17 -1
  88. package/src/editor/pages/liveTokensEditorHandle.ts +23 -0
  89. package/src/editor/skill-atlas/SkillAtlas.svelte +75 -573
  90. package/src/editor/skill-atlas/TreeCanvas.svelte +263 -0
  91. package/src/editor/skill-atlas/TreeNodeCard.svelte +136 -53
  92. package/src/editor/skill-atlas/edges.ts +31 -0
  93. package/src/editor/skill-atlas/skillSources.generated.ts +20 -19
  94. package/src/editor/skill-atlas/skillTrees.ts +19 -3840
  95. package/src/editor/skill-atlas/trees/check-compliance.ts +183 -0
  96. package/src/editor/skill-atlas/trees/create-component.ts +275 -0
  97. package/src/editor/skill-atlas/trees/create-page.ts +320 -0
  98. package/src/editor/skill-atlas/trees/create-theme.ts +255 -0
  99. package/src/editor/skill-atlas/trees/fix-findings.ts +469 -0
  100. package/src/editor/skill-atlas/trees/pick-component.ts +300 -0
  101. package/src/editor/skill-atlas/trees/set-colors.ts +148 -0
  102. package/src/editor/skill-atlas/trees/set-geometry.ts +136 -0
  103. package/src/editor/skill-atlas/trees/set-type.ts +142 -0
  104. package/src/editor/skill-atlas/types.ts +3 -4
  105. package/src/editor/skill-atlas/wireLayout.ts +287 -0
  106. package/src/live-tokens/data/themes/autumn.json +15 -15
  107. package/src/live-tokens/data/themes/halloween.json +15 -15
  108. package/src/live-tokens/data/themes/midnight-study.json +15 -15
  109. package/src/live-tokens/data/themes/ocean.json +15 -15
  110. package/src/live-tokens/data/themes/royal-velvet.json +15 -15
  111. package/src/live-tokens/data/themes/sketchy.json +15 -15
  112. package/src/live-tokens/data/themes/spring-meadow.json +15 -15
  113. package/src/live-tokens/data/themes/sunset.json +15 -15
  114. package/src/system/components/Badge.svelte +7 -0
  115. package/src/system/components/Button.svelte +7 -0
  116. package/src/system/components/Callout.svelte +10 -6
  117. package/src/system/components/Card.svelte +23 -4
  118. package/src/system/components/CodeSnippet.svelte +4 -3
  119. package/src/system/components/CollapsibleSection.svelte +23 -8
  120. package/src/system/components/CornerBadge.svelte +6 -0
  121. package/src/system/components/Dialog.svelte +13 -6
  122. package/src/system/components/IconButton.svelte +9 -0
  123. package/src/system/components/Image.svelte +8 -0
  124. package/src/system/components/ImageLightbox.svelte +6 -0
  125. package/src/system/components/InlineEditActions.svelte +7 -0
  126. package/src/system/components/Input.svelte +7 -0
  127. package/src/system/components/MenuSelect.svelte +7 -0
  128. package/src/system/components/Notification.svelte +7 -0
  129. package/src/system/components/Panel.svelte +6 -0
  130. package/src/system/components/ProgressBar.svelte +5 -0
  131. package/src/system/components/RadioButton.svelte +11 -5
  132. package/src/system/components/SectionDivider.svelte +8 -0
  133. package/src/system/components/SegmentedControl.svelte +6 -0
  134. package/src/system/components/SideNavigation.svelte +6 -0
  135. package/src/system/components/Slider.svelte +7 -4
  136. package/src/system/components/TabBar.svelte +15 -9
  137. package/src/system/components/Table.svelte +8 -3
  138. package/src/system/components/Toggle.svelte +4 -4
  139. package/src/system/components/Tooltip.svelte +6 -0
  140. package/src/testing-js/chunk-AO7EZHYV.js +776 -0
  141. package/src/testing-js/chunk-AO7EZHYV.js.map +1 -0
  142. package/src/testing-js/chunk-FAFOAWYL.js +39 -0
  143. package/src/testing-js/chunk-FAFOAWYL.js.map +1 -0
  144. package/src/testing-js/chunk-L73N4NSO.js +23 -0
  145. package/src/testing-js/chunk-L73N4NSO.js.map +1 -0
  146. package/src/testing-js/chunk-LXR3MN6N.js +3063 -0
  147. package/src/testing-js/chunk-LXR3MN6N.js.map +1 -0
  148. package/src/testing-js/chunk-ZMSX6CXR.js +53 -0
  149. package/src/testing-js/chunk-ZMSX6CXR.js.map +1 -0
  150. package/src/testing-js/component-alias.contract.js +81 -0
  151. package/src/testing-js/component-alias.contract.js.map +1 -0
  152. package/src/testing-js/component-editor.contract.js +62 -0
  153. package/src/testing-js/component-editor.contract.js.map +1 -0
  154. package/src/testing-js/component-render.contract.js +568 -0
  155. package/src/testing-js/component-render.contract.js.map +1 -0
  156. package/src/testing-js/index.d.ts +293 -0
  157. package/src/testing-js/index.js +222 -0
  158. package/src/testing-js/index.js.map +1 -0
  159. package/src/testing-js/registry.contract.js +39 -0
  160. package/src/testing-js/registry.contract.js.map +1 -0
  161. package/src/testing-js/vitest-BE6uGF31.d.ts +73 -0
  162. package/src/testing-js/vitest.d.ts +3 -0
  163. package/src/testing-js/vitest.js +13 -0
  164. package/src/testing-js/vitest.js.map +1 -0
  165. package/template/README.md +13 -0
  166. package/template/_gitignore +2 -0
  167. package/template/package.json +2 -1
  168. package/template/src/pages/Home.svelte +4 -18
  169. package/.claude/skills/live-tokens-build-page/SKILL.md +0 -103
  170. package/.claude/skills/live-tokens-build-page/references/layout-sources.md +0 -48
@@ -1,7 +1,7 @@
1
1
  // Static validator for a live-tokens page.
2
2
  //
3
3
  // Asserts that a page satisfies the contract described in the
4
- // live-tokens-build-page skill: it is assembled from catalogue components, and
4
+ // live-tokens-create-page skill: it is assembled from catalogue components, and
5
5
  // every value in its CSS is a theme token rather than a literal. The rules and
6
6
  // their default severities are in PAGE_RULES; each is overridable per project
7
7
  // (live-tokens.config.json) or per run (--off/--warn/--error/--strict), because
@@ -26,10 +26,13 @@ export const PAGE_RULES = {
26
26
  'color-literal': 'error',
27
27
  'reserved-route': 'error',
28
28
  'site-css-in-main': 'error',
29
+ 'raw-text-axis': 'error',
29
30
  'dimension-literal': 'warn',
30
31
  'hardcoded-columns': 'warn',
31
- 'raw-text-axis': 'warn',
32
32
  'missing-source': 'warn',
33
+ 'control-size': 'warn',
34
+ 'multiple-primary': 'warn',
35
+ 'danger-without-dialog': 'warn',
33
36
  };
34
37
 
35
38
  // Directories that hold the system, not pages built on it.
@@ -45,6 +48,13 @@ const DEEP_IMPORT_PATTERNS = [
45
48
 
46
49
  const TEXT_AXES = ['font-size', 'font-family', 'font-weight', 'line-height', 'letter-spacing'];
47
50
 
51
+ // The single-axis scales in tokens.css. A text style bundle carries its axis
52
+ // as a suffix (--body-md-font-size, --code-font-family), so no bundle name
53
+ // matches, and neither does a custom property the page declares itself. A
54
+ // weight alone cannot move the scale or the fonts, so --font-weight-* is not one.
55
+ const SINGLE_AXIS_TOKEN =
56
+ /^--(?:font-size|line-height|letter-spacing)-|^--font-(?:sans|serif|mono|display|editorial)$/;
57
+
48
58
  // The geometry the theme owns: spacing, stroke, radius, and shadow all have a
49
59
  // token scale, and `set-geometry` moves them. Sizing (a hero's height, a
50
60
  // column's minimum width, a max content width) is layout, has no scale, and
@@ -184,7 +194,10 @@ function tagAttributes(code, start) {
184
194
  return { attrs, end: tagEnd };
185
195
  }
186
196
 
187
- /** Props a page passes that the component does not declare, or values outside a prop's union. */
197
+ /**
198
+ * Props a page passes that the component does not declare, values outside a
199
+ * prop's union, and a shipped component the page sizes itself.
200
+ */
188
201
  function checkComponentUsage(code, imports, add) {
189
202
  for (const [local, entry] of imports) {
190
203
  const props = entry.props;
@@ -203,11 +216,64 @@ function checkComponentUsage(code, imports, add) {
203
216
  if (allowed && value !== null && !allowed.has(value)) {
204
217
  add('unknown-prop-value', index, `${entry.name} ${name}="${value}" is not one of ${[...allowed].join(', ')}`);
205
218
  }
219
+ if (name === 'size' && entry.origin === 'shipped') {
220
+ add(
221
+ 'control-size',
222
+ index,
223
+ `${entry.name} ${value === null ? 'is sized here' : `size="${value}"`}. Drop it for the shipped default, or retune ${entry.name} for the whole project in /live-tokens/components.`,
224
+ );
225
+ }
206
226
  }
207
227
  }
208
228
  }
209
229
  }
210
230
 
231
+ /**
232
+ * One finding when a page holds more than one primary Button, at the second of
233
+ * them. Emphasis is what the variant carries, so a second primary leaves the
234
+ * page with no single most important action. A Button with no variant is
235
+ * primary, the component's default.
236
+ */
237
+ function checkPrimaryActions(code, imports, add) {
238
+ const primaries = [];
239
+ for (const [local, entry] of imports) {
240
+ if (entry.id !== 'button') continue;
241
+ for (const m of code.matchAll(new RegExp(`<${local}(?=[\\s/>])`, 'g'))) {
242
+ const tag = tagAttributes(code, m.index);
243
+ const variant = tag?.attrs.find((a) => a.name === 'variant');
244
+ if (tag && (variant === undefined || variant.value === 'primary')) primaries.push(m.index);
245
+ }
246
+ }
247
+ if (primaries.length < 2) return;
248
+ primaries.sort((a, b) => a - b);
249
+ add(
250
+ 'multiple-primary',
251
+ primaries[1],
252
+ `${primaries.length} primary Buttons in this page. Keep the most important action primary and make the other ${primaries.length - 1} secondary.`,
253
+ );
254
+ }
255
+
256
+ /**
257
+ * One finding when a page holds a danger Button and imports no Dialog. A
258
+ * danger action destroys saved work, and the page has nothing to confirm it.
259
+ */
260
+ function checkDestructiveActions(code, imports, add) {
261
+ if ([...imports.values()].some((entry) => entry.id === 'dialog')) return;
262
+ for (const [local, entry] of imports) {
263
+ if (entry.id !== 'button' && entry.id !== 'iconbutton') continue;
264
+ for (const m of code.matchAll(new RegExp(`<${local}(?=[\\s/>])`, 'g'))) {
265
+ const tag = tagAttributes(code, m.index);
266
+ if (!tag?.attrs.some((a) => a.name === 'variant' && a.value === 'danger')) continue;
267
+ add(
268
+ 'danger-without-dialog',
269
+ m.index,
270
+ `${entry.name} variant="danger" with no Dialog in this page. A destructive action confirms in a Dialog before it runs.`,
271
+ );
272
+ return;
273
+ }
274
+ }
275
+ }
276
+
211
277
  /** The object literal enclosing `index`, found by balancing braces outward. */
212
278
  function enclosingObject(text, index) {
213
279
  let depth = 0;
@@ -267,6 +333,8 @@ function checkFile(file, text, vocab, root) {
267
333
  if (local) imports.set(local, entry);
268
334
  }
269
335
  checkComponentUsage(code, imports, add);
336
+ checkPrimaryActions(code, imports, add);
337
+ checkDestructiveActions(code, imports, add);
270
338
 
271
339
  for (const m of code.matchAll(/['"](\/live-tokens[^'"]*)['"]\s*:/g)) {
272
340
  add('reserved-route', m.index, `route '${m[1]}' is inside the reserved /live-tokens/* namespace`);
@@ -315,8 +383,8 @@ function checkFile(file, text, vocab, root) {
315
383
  'unknown-token',
316
384
  at(m.index),
317
385
  isContractToken(name)
318
- ? `${name} looks like a theme token but no longer exists; check tokens.css for a rename`
319
- : `${name} is not a theme token, a component token, or declared in this file`,
386
+ ? `${name} has the shape of a design token but no longer exists. Check tokens.css for a rename`
387
+ : `${name} is not a design token, a semantic property, or declared in this file`,
320
388
  );
321
389
  }
322
390
 
@@ -328,25 +396,38 @@ function checkFile(file, text, vocab, root) {
328
396
  // inside one is not the page's value.
329
397
  const painted = stripVarFallbacks(value);
330
398
  if (!TEXT_AXES.includes(prop) && hasColorLiteral(painted)) {
331
- add('color-literal', at(index), `${prop}: ${value}. Use a theme token, not a colour literal.`);
399
+ add('color-literal', at(index), `${prop}: ${value}. Use a design token.`);
332
400
  continue;
333
401
  }
334
402
 
335
- // Only absolute type values are a finding. `em`, `%`, and a unitless
336
- // line-height are relative to the inherited type, so they ride whatever
337
- // the theme sets rather than overriding it.
338
- if (
339
- (TEXT_AXES.includes(prop) || prop === 'font') &&
340
- !value.includes('var(') &&
341
- !/^(inherit|initial|unset|normal)$/.test(value) &&
342
- /\d(px|rem|pt)\b|^[a-z"']/i.test(value)
343
- ) {
344
- add(
345
- 'raw-text-axis',
346
- at(index),
347
- `${prop}: ${value}. Set type from a text style bundle (--heading-*, --body-*, --editorial-*).`,
348
- );
349
- continue;
403
+ if (TEXT_AXES.includes(prop) || prop === 'font') {
404
+ const axis = [...painted.matchAll(/var\(\s*(--[a-z0-9-]+)/g)]
405
+ .map((m) => m[1])
406
+ .find((name) => SINGLE_AXIS_TOKEN.test(name));
407
+ if (axis) {
408
+ add(
409
+ 'raw-text-axis',
410
+ at(index),
411
+ `${prop}: ${value}. ${axis} is one axis. Set every axis from one text style bundle (--heading-*, --body-*, --editorial-*, --code-*).`,
412
+ );
413
+ continue;
414
+ }
415
+
416
+ // Of the literals only absolute type values are a finding. `em`, `%`,
417
+ // and a unitless line-height are relative to the inherited type, so
418
+ // they ride whatever the theme sets rather than overriding it.
419
+ if (
420
+ !value.includes('var(') &&
421
+ !/^(inherit|initial|unset|normal)$/.test(value) &&
422
+ /\d(px|rem|pt)\b|^[a-z"']/i.test(value)
423
+ ) {
424
+ add(
425
+ 'raw-text-axis',
426
+ at(index),
427
+ `${prop}: ${value}. Set type from a text style bundle (--heading-*, --body-*, --editorial-*, --code-*).`,
428
+ );
429
+ continue;
430
+ }
350
431
  }
351
432
 
352
433
  if (THEMED_GEOMETRY.test(prop) && hasDimensionLiteral(painted)) {
package/bin/cli.mjs CHANGED
@@ -4,10 +4,10 @@
4
4
  // create <dir> Scaffold a new app that depends on this package.
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
- // tokens [--family <name>] List every theme token by family, with its value.
7
+ // tokens [--scale <name>] List every design token by scale, with its value.
8
8
  // report The project as facts: tokens read, components used, findings by rule. Always exits 0.
9
9
  // check-component [id] Validate a component (or every authored one) against the create-component skill contract.
10
- // check-page [paths...] Validate pages against the build-page skill contract.
10
+ // check-page [paths...] Validate pages against the create-page skill contract.
11
11
  // set-colors <colors> Build the color identity from 10 OKLCH base colors, into the open buffer.
12
12
  // set-geometry <ops> Apply radius/padding/gap/border-width ops to the open buffer.
13
13
  // set-type <pairing> Bind Google Fonts families to the theme's font stacks.
@@ -18,12 +18,13 @@ import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeSync } from
18
18
  import { dirname, join, resolve } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
20
  import process from 'node:process';
21
- import { COMPONENT_RULES, checkComponent, discoverComponents, formatReport } from './check-component.mjs';
21
+ import { COMPONENT_RULES, COMPONENT_RULE_FIX, checkComponent, discoverComponents, formatReport } from './check-component.mjs';
22
22
  import { PAGE_RULES, checkPages, discoverPages } from './check-page.mjs';
23
23
  import { describeComponents, describeTokens, formatComponents, formatTokens } from './lib/catalogue.mjs';
24
24
  import { buildReport, formatReport as formatProjectReport } from './lib/report.mjs';
25
25
  import { loadVocabulary } from './lib/tokenVocabulary.mjs';
26
26
  import {
27
+ applyCoverageSeverity,
27
28
  applySeverity,
28
29
  countBySeverity,
29
30
  formatFindings,
@@ -48,27 +49,33 @@ const USAGE = `Usage: npx @motion-proto/live-tokens <command> [options]
48
49
 
49
50
  Commands:
50
51
  create <dir> [--force] Scaffold a new Svelte + Vite app wired up with
51
- live-tokens (editor, components, theme tokens)
52
+ live-tokens (editor, components, design tokens)
52
53
  setup-claude [--force] Install bundled Claude Code skills into ./.claude/skills/
53
54
  components [id] [--json] List every component the project has, shipped and
54
55
  its own (src/system/components plus any
55
- "componentDirs" in live-tokens.config.json), with
56
- the props each takes; with an id, that component's
57
- props, variants, tokens, and defaults
58
- tokens [--family <name>] [--json]
59
- List every theme token the project's tokens.css
60
- declares, by family, with its value
56
+ "componentDirs" in live-tokens.config.json). With
57
+ an id, that component's props, variants, tokens,
58
+ and defaults
59
+ tokens [--scale <name>] [--json]
60
+ List every design token the project's tokens.css
61
+ declares, by scale, with its value
61
62
  report [--json] The project as facts: pending migrations, tokens
62
63
  each component declares and reads, which page
63
64
  renders which component, and both checkers'
64
65
  findings by rule under the project's severities
65
- and under --strict. A reading, not a gate: always
66
- exits 0
67
- check-component [id] Validate <id>'s runtime, editor, and registration
68
- against the live-tokens-create-component contract
69
- check-page [paths...] Validate pages against the live-tokens-build-page
66
+ and under --strict. Always exits 0
67
+ check-component [id] [--tests]
68
+ Validate <id>'s runtime, editor, and registration
69
+ against the live-tokens-create-component contract.
70
+ --tests also runs the registry contract under
71
+ vitest and the component contract suites under
72
+ Playwright, reporting coverage by rule. Needs
73
+ @playwright/test, vitest, and happy-dom; a
74
+ missing one is a tests-not-installed finding
75
+ naming the install command
76
+ check-page [paths...] Validate pages against the live-tokens-create-page
70
77
  contract: catalogue components only, and every CSS
71
- value a theme token. Checks every page under src/
78
+ value a design token. Checks every page under src/
72
79
  when given no paths.
73
80
 
74
81
  check-component and check-page also accept:
@@ -84,44 +91,42 @@ check-component and check-page also accept:
84
91
  OKLCH base colors (see the live-tokens-set-colors
85
92
  skill) and enforce AA contrast on the derived text
86
93
  tokens. Reads the live colors and type and writes
87
- the result to the unsaved colors-and-type buffer,
88
- so save the open theme in the editor or run
89
- save-theme to keep it. Fonts and every override no
90
- palette owns carry forward. --dry-run prints the
91
- contrast report without writing.
94
+ the result to the colors-and-type buffer. Run
95
+ save-theme to keep it as a theme. Fonts and every
96
+ override no palette owns carry forward. --dry-run
97
+ prints the contrast report without writing.
92
98
  set-geometry <ops.json> [--dry-run]
93
99
  Move radius, padding, gap, and border-width
94
100
  aliases along their token scales (see the
95
101
  live-tokens-set-geometry skill). Reads each
96
102
  component's live config and writes the result to
97
- that component's unsaved buffer, so save the open
98
- theme in the editor to keep it. --dry-run prints
99
- the report without writing.
103
+ that component's buffer. Run save-theme to keep it
104
+ as a theme. --dry-run prints the report without
105
+ writing.
100
106
  set-type <pairing.json> [--dry-run] [--no-verify]
101
107
  Bind Google Fonts families to --font-display,
102
108
  --font-sans, --font-serif, --font-mono and
103
109
  --font-editorial (see
104
110
  the live-tokens-set-type skill). Each family is
105
111
  verified against the Google Fonts API and the URL
106
- is negotiated from the weights it actually has.
107
- Writes the result to the unsaved colors-and-type
108
- buffer, so save the open theme in the editor to
109
- keep it. --dry-run prints the report without
110
- writing; --no-verify skips the network and
111
- requires an explicit URL per family.
112
+ is negotiated from the weights it has. Writes the
113
+ result to the colors-and-type buffer. Run
114
+ save-theme to keep it as a theme. --dry-run prints
115
+ the report without writing. --no-verify skips the
116
+ network and requires an explicit URL per family.
112
117
  save-theme <name> [--no-activate] [--dry-run]
113
- Compose the live state (the unsaved buffers, the
114
- open theme under them, the shipped defaults under
115
- that) into themes/<slug>.json and open it, which
116
- clears the buffers. With no unsaved edits it saves
117
- a copy of the open theme under the new name.
118
- Opening never changes what your site ships; Adopt
119
- in the editor does that. --no-activate writes the
120
- theme and leaves the live state alone, so a set of
121
- themes comes off one starting look; --dry-run
122
- prints the report without writing.
118
+ Compose the live state (the buffers, the open
119
+ theme under them, the shipped defaults under that)
120
+ into themes/<slug>.json and load it, which clears
121
+ the buffers. With no buffer it saves a copy of the
122
+ open theme under the new name. Loading never
123
+ changes what the site ships. Adopt in the editor
124
+ does that. --no-activate writes the theme and does
125
+ not load it, so a set of themes comes off one
126
+ starting theme. --dry-run prints the report
127
+ without writing.
123
128
  migrate [--check] [--write] [--tokens <path>]
124
- Reconcile your project with the installed package:
129
+ Reconcile the project with the installed package:
125
130
  applies additive tokens.css migrations, moves a
126
131
  pre-0.48 data tree onto the current directory
127
132
  names, heals what the retired pointer files named,
@@ -177,14 +182,19 @@ if (command === 'create') {
177
182
  }
178
183
  }
179
184
 
180
- function reportChecks(label, findings, checked, rules, opts) {
181
- const resolved = applySeverity(findings, rules, opts, readChecksConfig(process.cwd()));
185
+ function reportChecks(label, findings, checked, rules, opts, { coverage, hardFailure, fixes } = {}) {
186
+ const checksConfig = readChecksConfig(process.cwd());
187
+ const resolved = applySeverity(findings, rules, opts, checksConfig, fixes ?? {});
188
+ const resolvedCoverage = coverage ? applyCoverageSeverity(coverage, rules, opts, checksConfig) : coverage;
182
189
  console.log(
183
190
  opts.json
184
- ? toJson(resolved, { label, checked })
191
+ ? toJson(resolved, { label, checked, coverage: resolvedCoverage })
185
192
  : formatFindings(resolved, { label, checked }),
186
193
  );
187
- process.exit(countBySeverity(resolved).errors === 0 ? 0 : 1);
194
+ // Decision 2: a missing tool or a setup failure under --tests is an error
195
+ // even if a project silenced its rule id, since silencing it would read as
196
+ // "the tests passed" rather than "the tests did not run".
197
+ process.exit(countBySeverity(resolved).errors === 0 && !hardFailure ? 0 : 1);
188
198
  }
189
199
 
190
200
  if (command === 'components') {
@@ -198,14 +208,14 @@ if (command === 'components') {
198
208
 
199
209
  if (command === 'tokens') {
200
210
  const opts = parseCheckFlags(rest);
201
- const at = opts.rest.indexOf('--family');
202
- const family = at >= 0 ? opts.rest[at + 1] : undefined;
211
+ const at = opts.rest.indexOf('--scale');
212
+ const scale = at >= 0 ? opts.rest[at + 1] : undefined;
203
213
  const desc = describeTokens(loadVocabulary());
204
- if (family && !desc.families.some((f) => f.family === family)) fail(formatTokens(desc, { family }));
214
+ if (scale && !desc.scales.some((s) => s.scale === scale)) fail(formatTokens(desc, { scale }));
205
215
  writeOut(
206
216
  opts.json
207
- ? JSON.stringify(family ? desc.families.find((f) => f.family === family) : desc, null, 2)
208
- : formatTokens(desc, { family }),
217
+ ? JSON.stringify(scale ? desc.scales.find((s) => s.scale === scale) : desc, null, 2)
218
+ : formatTokens(desc, { scale }),
209
219
  );
210
220
  process.exit(0);
211
221
  }
@@ -231,18 +241,28 @@ if (command === 'report') {
231
241
  if (command === 'check-component') {
232
242
  const opts = parseCheckFlags(rest);
233
243
  const ids = opts.rest.length > 0 ? [opts.rest[0]] : discoverComponents();
234
- if (ids.length === 0) {
244
+ if (ids.length === 0 && !opts.tests) {
235
245
  console.log('✓ check-component: no component authored under src/system/components yet.');
236
246
  process.exit(0);
237
247
  }
238
248
  const results = ids.map((id) => [id, checkComponent(id)]);
239
- if (ids.length === 1 && !opts.json && !opts.strict && opts.off.length + opts.warn.length + opts.error.length === 0) {
249
+ if (!opts.tests && ids.length === 1 && !opts.json && !opts.strict && opts.off.length + opts.warn.length + opts.error.length === 0) {
240
250
  const [id, result] = results[0];
241
251
  console.log(formatReport(id, result));
242
252
  process.exit(result.errors.length === 0 ? 0 : 1);
243
253
  }
244
- const label = ids.length === 1 ? `check-component ${ids[0]}` : 'check-component';
245
- reportChecks(label, results.flatMap(([, r]) => r.findings), ids.length, COMPONENT_RULES, opts);
254
+ const label = ids.length === 1 ? `check-component ${ids[0]}${opts.tests ? ' --tests' : ''}` : `check-component${opts.tests ? ' --tests' : ''}`;
255
+ if (!opts.tests) {
256
+ reportChecks(label, results.flatMap(([, r]) => r.findings), ids.length, COMPONENT_RULES, opts, { fixes: COMPONENT_RULE_FIX });
257
+ }
258
+ const { hasHardFailure, runContractTests } = await import('./contractRunner.mjs');
259
+ const testOutcome = await runContractTests(opts.rest[0], { root: process.cwd() });
260
+ const findings = [...results.flatMap(([, r]) => r.findings), ...testOutcome.findings];
261
+ reportChecks(label, findings, Math.max(ids.length, 1), COMPONENT_RULES, opts, {
262
+ fixes: COMPONENT_RULE_FIX,
263
+ coverage: testOutcome.coverage,
264
+ hardFailure: hasHardFailure(testOutcome.findings),
265
+ });
246
266
  }
247
267
 
248
268
  if (command === 'check-page') {
@@ -265,7 +285,7 @@ if (command === 'set-colors') {
265
285
  }
266
286
  if (rest.includes('--carry-from')) {
267
287
  fail(
268
- `set-colors has no --carry-from: it reads the live look and edits it in place, so a second theme ` +
288
+ `set-colors has no --carry-from: it reads the live theme and edits it in place, so a second theme ` +
269
289
  `already starts from the first. Run save-theme --no-activate between themes.`,
270
290
  );
271
291
  }
@@ -421,10 +441,10 @@ for (const skill of skills) {
421
441
  installed++;
422
442
  }
423
443
 
424
- console.log(`\n${installed} installed, ${skipped} skipped → ${destSkills}`);
444
+ console.log(`\n${installed} installed, ${skipped} skipped, in ${destSkills}`);
425
445
 
426
446
  const SAMPLE_PROMPTS = {
427
- 'live-tokens-build-page': 'build a pricing page using live-tokens components',
447
+ 'live-tokens-create-page': 'build a pricing page using live-tokens components',
428
448
  'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
429
449
  'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
430
450
  'live-tokens-create-theme': 'make me a bright and cheerful theme',
@@ -442,6 +462,6 @@ const installedSamples = skills
442
462
  if (installedSamples.length > 0) {
443
463
  console.log(`\nIn Claude Code, prompts like these auto-trigger the matching skill:`);
444
464
  for (const [skill, prompt] of installedSamples) {
445
- console.log(` • "${prompt}"\n → ${skill}`);
465
+ console.log(` "${prompt}"\n ${skill}`);
446
466
  }
447
467
  }