@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.
- 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 +103 -0
- package/README.md +3 -3
- package/bin/check-component.mjs +91 -19
- package/bin/check-page.mjs +102 -21
- package/bin/cli.mjs +78 -58
- 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/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 +2 -0
- package/template/package.json +2 -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
package/bin/check-page.mjs
CHANGED
|
@@ -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-
|
|
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
|
-
/**
|
|
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}
|
|
319
|
-
: `${name} is not a
|
|
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
|
|
399
|
+
add('color-literal', at(index), `${prop}: ${value}. Use a design token.`);
|
|
332
400
|
continue;
|
|
333
401
|
}
|
|
334
402
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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 [--
|
|
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
|
|
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,
|
|
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)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
tokens [--
|
|
59
|
-
List every
|
|
60
|
-
declares, by
|
|
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.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
against the live-tokens-create-component contract
|
|
69
|
-
|
|
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
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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('--
|
|
202
|
-
const
|
|
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 (
|
|
214
|
+
if (scale && !desc.scales.some((s) => s.scale === scale)) fail(formatTokens(desc, { scale }));
|
|
205
215
|
writeOut(
|
|
206
216
|
opts.json
|
|
207
|
-
? JSON.stringify(
|
|
208
|
-
: formatTokens(desc, {
|
|
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]}` :
|
|
245
|
-
|
|
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
|
|
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
|
|
444
|
+
console.log(`\n${installed} installed, ${skipped} skipped, in ${destSkills}`);
|
|
425
445
|
|
|
426
446
|
const SAMPLE_PROMPTS = {
|
|
427
|
-
'live-tokens-
|
|
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(`
|
|
465
|
+
console.log(` "${prompt}"\n ${skill}`);
|
|
446
466
|
}
|
|
447
467
|
}
|