@motion-proto/live-tokens 0.78.0 → 0.80.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 (172) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +49 -38
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +23 -28
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +61 -0
  4. package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +2 -2
  5. package/.claude/skills/live-tokens-create-component/references/token-naming.md +6 -15
  6. package/.claude/skills/live-tokens-create-page/SKILL.md +6 -4
  7. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
  8. package/.claude/skills/live-tokens-pick-component/SKILL.md +3 -3
  9. package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +1 -1
  10. package/.claude/skills/live-tokens-set-geometry/SKILL.md +1 -1
  11. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +1 -1
  12. package/CHANGELOG.md +271 -0
  13. package/README.md +10 -16
  14. package/bin/check-component.mjs +154 -551
  15. package/bin/check-page.mjs +57 -541
  16. package/bin/cli.mjs +93 -16
  17. package/bin/contractRunner.mjs +37 -91
  18. package/bin/lib/buildChecks.mjs +32 -0
  19. package/bin/lib/catalogue.mjs +161 -34
  20. package/bin/lib/componentSource.mjs +152 -0
  21. package/bin/lib/cssValues.mjs +9 -0
  22. package/bin/lib/dataDir.mjs +126 -0
  23. package/bin/lib/findings.mjs +103 -12
  24. package/bin/lib/fixers.mjs +64 -0
  25. package/bin/lib/geometry.mjs +92 -0
  26. package/bin/lib/pageSource.mjs +230 -0
  27. package/bin/lib/report.mjs +57 -59
  28. package/bin/lib/tokenVocabulary.mjs +104 -34
  29. package/bin/migrate-build-script.mjs +66 -0
  30. package/bin/migrate.mjs +5 -0
  31. package/bin/rules/componentStructure.mjs +344 -0
  32. package/bin/rules/componentUse.mjs +313 -0
  33. package/bin/rules/importsAndRoutes.mjs +136 -0
  34. package/bin/rules/testRuns.mjs +122 -0
  35. package/bin/rules/tokens.mjs +426 -0
  36. package/bin/setup-claude.mjs +1 -2
  37. package/dist-plugin/{chunk-PDNL4NC5.js → chunk-D4WRIKEZ.js} +7 -2
  38. package/dist-plugin/{chunk-SWXRVZKT.js → chunk-REBHE3ZM.js} +414 -1
  39. package/dist-plugin/index.cjs +470 -19
  40. package/dist-plugin/index.d.cts +1 -0
  41. package/dist-plugin/index.d.ts +1 -0
  42. package/dist-plugin/index.js +87 -55
  43. package/dist-plugin/migrateData/index.cjs +422 -4
  44. package/dist-plugin/migrateData/index.js +2 -2
  45. package/dist-plugin/setColors/index.cjs +414 -1
  46. package/dist-plugin/setColors/index.d.cts +1 -1
  47. package/dist-plugin/setColors/index.d.ts +1 -1
  48. package/dist-plugin/setColors/index.js +1 -1
  49. package/dist-plugin/setGeometry/index.cjs +423 -13
  50. package/dist-plugin/setGeometry/index.d.cts +3 -3
  51. package/dist-plugin/setGeometry/index.d.ts +3 -3
  52. package/dist-plugin/setGeometry/index.js +10 -13
  53. package/dist-plugin/setType/index.d.cts +1 -1
  54. package/dist-plugin/setType/index.d.ts +1 -1
  55. package/dist-plugin/{themeTypes-BxRtuN5V.d.cts → themeTypes-B8_Idrp4.d.cts} +2 -2
  56. package/dist-plugin/{themeTypes-BxRtuN5V.d.ts → themeTypes-B8_Idrp4.d.ts} +2 -2
  57. package/package.json +2 -2
  58. package/src/editor/component-editor/CalloutEditor.svelte +2 -2
  59. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +14 -15
  60. package/src/editor/component-editor/CornerBadgeEditor.svelte +14 -14
  61. package/src/editor/component-editor/DialogEditor.svelte +5 -5
  62. package/src/editor/component-editor/InlineEditActionsEditor.svelte +2 -2
  63. package/src/editor/component-editor/RadioButtonEditor.svelte +21 -21
  64. package/src/editor/component-editor/SectionDividerEditor.svelte +7 -7
  65. package/src/editor/component-editor/SegmentedControlEditor.svelte +5 -5
  66. package/src/editor/component-editor/SideNavigationEditor.svelte +25 -25
  67. package/src/editor/component-editor/TabBarEditor.svelte +6 -6
  68. package/src/editor/component-editor/TableEditor.svelte +6 -6
  69. package/src/editor/component-editor/ToggleEditor.svelte +2 -2
  70. package/src/editor/component-editor/index.ts +3 -0
  71. package/src/editor/component-editor/registry.ts +57 -1
  72. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +14 -14
  73. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +3 -0
  74. package/src/editor/component-editor/scaffolding/types.ts +15 -0
  75. package/src/editor/core/components/adjustAliases.ts +4 -4
  76. package/src/editor/core/components/aliasKinds.ts +20 -19
  77. package/src/editor/core/sketch/sketchLayer.ts +3 -3
  78. package/src/editor/core/themes/migrateComponentConfig.ts +14 -6
  79. package/src/editor/core/themes/migrations/2026-09-13-badge-brand.ts +42 -0
  80. package/src/editor/core/themes/migrations/2026-09-13-collapsiblesection-open.ts +33 -0
  81. package/src/editor/core/themes/migrations/2026-09-13-cornerbadge-prefix.ts +70 -0
  82. package/src/editor/core/themes/migrations/2026-09-13-hairline.ts +92 -0
  83. package/src/editor/core/themes/migrations/2026-09-13-indicator.ts +54 -0
  84. package/src/editor/core/themes/migrations/2026-09-13-sectiondivider-surface.ts +36 -0
  85. package/src/editor/core/themes/migrations/2026-09-13-selected-state.ts +124 -0
  86. package/src/editor/core/themes/migrations/2026-09-13-toggle-label.ts +31 -0
  87. package/src/editor/core/themes/migrations/index.ts +16 -0
  88. package/src/editor/core/themes/themeService.ts +3 -3
  89. package/src/editor/core/themes/themeTypes.ts +13 -15
  90. package/src/editor/docs/Docs.svelte +1 -1
  91. package/src/editor/docs/content/light-and-dark.md +3 -3
  92. package/src/editor/docs/content.generated.ts +1 -1
  93. package/src/editor/index.ts +1 -0
  94. package/src/editor/overlay/LiveEditorOverlay.svelte +15 -0
  95. package/src/editor/skill-atlas/SkillAtlas.svelte +4 -4
  96. package/src/editor/skill-atlas/TreeNodeCard.svelte +4 -4
  97. package/src/editor/skill-atlas/skillSources.generated.ts +11 -14
  98. package/src/editor/skill-atlas/skillTrees.ts +1 -3
  99. package/src/editor/skill-atlas/trees/check-compliance.ts +37 -149
  100. package/src/editor/skill-atlas/trees/create-component.ts +62 -64
  101. package/src/editor/skill-atlas/trees/create-page.ts +38 -17
  102. package/src/editor/skill-atlas/trees/pick-component.ts +3 -3
  103. package/src/editor/skill-atlas/trees/set-geometry.ts +1 -1
  104. package/src/editor/ui/UIPaletteSelector.svelte +11 -15
  105. package/src/editor/ui/variantScales.ts +8 -8
  106. package/src/live-tokens/data/themes/autumn.json +188 -188
  107. package/src/live-tokens/data/themes/halloween.json +188 -188
  108. package/src/live-tokens/data/themes/midnight-study.json +245 -245
  109. package/src/live-tokens/data/themes/ocean.json +188 -188
  110. package/src/live-tokens/data/themes/royal-velvet.json +188 -188
  111. package/src/live-tokens/data/themes/sketchy.json +188 -188
  112. package/src/live-tokens/data/themes/spring-meadow.json +188 -188
  113. package/src/live-tokens/data/themes/sunset.json +188 -188
  114. package/src/system/components/Badge.svelte +27 -23
  115. package/src/system/components/Button.svelte +13 -7
  116. package/src/system/components/Callout.svelte +16 -11
  117. package/src/system/components/Card.svelte +22 -15
  118. package/src/system/components/CodeSnippet.svelte +10 -6
  119. package/src/system/components/CollapsibleSection.svelte +81 -76
  120. package/src/system/components/CornerBadge.svelte +78 -72
  121. package/src/system/components/Dialog.svelte +21 -18
  122. package/src/system/components/IconButton.svelte +13 -9
  123. package/src/system/components/Image.svelte +14 -8
  124. package/src/system/components/ImageLightbox.svelte +10 -6
  125. package/src/system/components/InlineEditActions.svelte +17 -14
  126. package/src/system/components/Input.svelte +13 -7
  127. package/src/system/components/MenuSelect.svelte +13 -7
  128. package/src/system/components/Notification.svelte +17 -11
  129. package/src/system/components/Panel.svelte +13 -6
  130. package/src/system/components/ProgressBar.svelte +10 -5
  131. package/src/system/components/RadioButton.svelte +33 -30
  132. package/src/system/components/SectionDivider.svelte +28 -21
  133. package/src/system/components/SegmentedControl.svelte +27 -23
  134. package/src/system/components/SideNavigation.svelte +175 -171
  135. package/src/system/components/Slider.svelte +11 -7
  136. package/src/system/components/TabBar.svelte +53 -49
  137. package/src/system/components/Table.svelte +20 -15
  138. package/src/system/components/Toggle.svelte +14 -10
  139. package/src/system/components/Tooltip.svelte +10 -6
  140. package/src/system/styles/CONVENTIONS.md +3 -4
  141. package/src/testing-js/chunk-3UKGXCDL.js +48 -0
  142. package/src/testing-js/chunk-3UKGXCDL.js.map +1 -0
  143. package/src/testing-js/{chunk-FAFOAWYL.js → chunk-Q3YIAAG3.js} +17 -3
  144. package/src/testing-js/chunk-Q3YIAAG3.js.map +1 -0
  145. package/src/testing-js/{chunk-GNIUPIU2.js → chunk-U7OJE5DU.js} +18 -18
  146. package/src/testing-js/chunk-U7OJE5DU.js.map +1 -0
  147. package/src/testing-js/{chunk-4JQX6WWL.js → chunk-ZMZQZ33J.js} +474 -166
  148. package/src/testing-js/chunk-ZMZQZ33J.js.map +1 -0
  149. package/src/testing-js/component-behavior.contract.js +155 -0
  150. package/src/testing-js/component-behavior.contract.js.map +1 -0
  151. package/src/testing-js/component-editor.contract.js +6 -4
  152. package/src/testing-js/component-editor.contract.js.map +1 -1
  153. package/src/testing-js/component-render.contract.js +14 -10
  154. package/src/testing-js/component-render.contract.js.map +1 -1
  155. package/src/testing-js/index.d.ts +44 -4
  156. package/src/testing-js/index.js +13 -7
  157. package/src/testing-js/index.js.map +1 -1
  158. package/src/testing-js/page-compliance.contract.js +48 -10
  159. package/src/testing-js/page-compliance.contract.js.map +1 -1
  160. package/src/testing-js/registry.contract.js +5 -3
  161. package/src/testing-js/registry.contract.js.map +1 -1
  162. package/src/testing-js/{vitest-C-wNWcoA.d.ts → vitest-BMLIbDs2.d.ts} +8 -2
  163. package/src/testing-js/vitest.d.ts +1 -1
  164. package/src/testing-js/vitest.js +2 -1
  165. package/template/package.json +1 -2
  166. package/template/vite.config.ts +3 -2
  167. package/.claude/skills/live-tokens-create-component/SKILL copy.md +0 -196
  168. package/.claude/skills/live-tokens-fix-findings/SKILL.md +0 -105
  169. package/src/editor/skill-atlas/trees/fix-findings.ts +0 -504
  170. package/src/testing-js/chunk-4JQX6WWL.js.map +0 -1
  171. package/src/testing-js/chunk-FAFOAWYL.js.map +0 -1
  172. package/src/testing-js/chunk-GNIUPIU2.js.map +0 -1
@@ -5,7 +5,7 @@ import {
5
5
  nextAvailableName,
6
6
  normalizeTheme,
7
7
  versionedFileResourceServer
8
- } from "./chunk-PDNL4NC5.js";
8
+ } from "./chunk-D4WRIKEZ.js";
9
9
  import {
10
10
  formatGradientValue,
11
11
  palettesToVars,
@@ -16,7 +16,7 @@ import {
16
16
  } from "./chunk-O6GQ6GBH.js";
17
17
  import {
18
18
  CURRENT_COMPONENT_SCHEMA_VERSION
19
- } from "./chunk-SWXRVZKT.js";
19
+ } from "./chunk-REBHE3ZM.js";
20
20
  import {
21
21
  TOKENS_CSS_MIGRATIONS,
22
22
  extractGlobalRootBody,
@@ -37,7 +37,7 @@ import {
37
37
 
38
38
  // vite-plugin/themeFileApi.ts
39
39
  import fs2 from "fs";
40
- import path2 from "path";
40
+ import path3 from "path";
41
41
 
42
42
  // src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts
43
43
  var round = (v, dp) => {
@@ -229,18 +229,40 @@ function dataTreeWatch(opts) {
229
229
  return { noteOwnWrite, subscribe };
230
230
  }
231
231
 
232
+ // vite-plugin/buildChecks.ts
233
+ import path2 from "path";
234
+ import { fileURLToPath, pathToFileURL } from "url";
235
+ var runPackageChecks = async (root) => {
236
+ const runner = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "..", "bin", "lib", "buildChecks.mjs");
237
+ const { runBuildChecks } = await import(pathToFileURL(runner).href);
238
+ return runBuildChecks({ root });
239
+ };
240
+ async function checkBeforeBuild({
241
+ root,
242
+ logger,
243
+ run = runPackageChecks
244
+ }) {
245
+ const result = await run(root);
246
+ if (result.errors > 0) {
247
+ logger.error(result.report);
248
+ return `live-tokens: the design checks found ${result.errors} error(s). Run the live-tokens-check-compliance skill, or \`npx live-tokens check-page\` and \`npx live-tokens check-component\`, to repair them.`;
249
+ }
250
+ (result.warnings > 0 ? logger.warn : logger.info).call(logger, result.report);
251
+ return null;
252
+ }
253
+
232
254
  // vite-plugin/themeFileApi.ts
233
- import { fileURLToPath } from "url";
255
+ import { fileURLToPath as fileURLToPath2 } from "url";
234
256
  var PKG_VERSION = (() => {
235
257
  try {
236
- let dir = path2.dirname(fileURLToPath(import.meta.url));
258
+ let dir = path3.dirname(fileURLToPath2(import.meta.url));
237
259
  for (let i = 0; i < 4; i++) {
238
- const p = path2.join(dir, "package.json");
260
+ const p = path3.join(dir, "package.json");
239
261
  if (fs2.existsSync(p)) {
240
262
  const json = JSON.parse(fs2.readFileSync(p, "utf-8"));
241
263
  if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
242
264
  }
243
- const up = path2.dirname(dir);
265
+ const up = path3.dirname(dir);
244
266
  if (up === dir) break;
245
267
  dir = up;
246
268
  }
@@ -284,37 +306,37 @@ function themeFileApi(opts) {
284
306
  const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
285
307
  const THEMES_DIR = dataDirs.themesDir;
286
308
  const SKETCH_STYLES_DIR = dataDirs.sketchStylesDir;
287
- const CSS_PATH = path2.resolve(opts.tokensCssPath);
309
+ const CSS_PATH = path3.resolve(opts.tokensCssPath);
288
310
  const ISOLATED_DATA_DIR = testDataDir();
289
- const GENERATED_CSS_PATH = ISOLATED_DATA_DIR ? path2.join(ISOLATED_DATA_DIR, "tokens.generated.css") : opts.tokensGeneratedCssPath ? path2.resolve(opts.tokensGeneratedCssPath) : path2.join(dataDirs.dataDir, "tokens.generated.css");
290
- const FONTS_CSS_PATH = ISOLATED_DATA_DIR ? path2.join(ISOLATED_DATA_DIR, "fonts.css") : opts.fontsCssPath ? path2.resolve(opts.fontsCssPath) : path2.join(path2.dirname(CSS_PATH), "fonts.css");
311
+ const GENERATED_CSS_PATH = ISOLATED_DATA_DIR ? path3.join(ISOLATED_DATA_DIR, "tokens.generated.css") : opts.tokensGeneratedCssPath ? path3.resolve(opts.tokensGeneratedCssPath) : path3.join(dataDirs.dataDir, "tokens.generated.css");
312
+ const FONTS_CSS_PATH = ISOLATED_DATA_DIR ? path3.join(ISOLATED_DATA_DIR, "fonts.css") : opts.fontsCssPath ? path3.resolve(opts.fontsCssPath) : path3.join(path3.dirname(CSS_PATH), "fonts.css");
291
313
  const API_BASE = opts.apiBase ?? "/api/live-tokens";
292
- const consumerComponentDirs = opts.componentsSrcDir ? [path2.resolve(opts.componentsSrcDir)] : [path2.resolve("src/components"), path2.resolve("src/system/components")];
293
- const packageComponentsDir = path2.resolve(
294
- path2.dirname(fileURLToPath(import.meta.url)),
314
+ const consumerComponentDirs = opts.componentsSrcDir ? [path3.resolve(opts.componentsSrcDir)] : [path3.resolve("src/components"), path3.resolve("src/system/components")];
315
+ const packageComponentsDir = path3.resolve(
316
+ path3.dirname(fileURLToPath2(import.meta.url)),
295
317
  "..",
296
318
  "src",
297
319
  "system",
298
320
  "components"
299
321
  );
300
- const packageDataDir = testPackageDataDir() ?? path2.resolve(
301
- path2.dirname(fileURLToPath(import.meta.url)),
322
+ const packageDataDir = testPackageDataDir() ?? path3.resolve(
323
+ path3.dirname(fileURLToPath2(import.meta.url)),
302
324
  "..",
303
325
  "src",
304
326
  "live-tokens",
305
327
  "data"
306
328
  );
307
- const packageColorsAndTypeDir = path2.join(packageDataDir, "colors-and-type");
308
- const packageThemesDir = path2.join(packageDataDir, "themes");
309
- const packageComponentConfigsDir = path2.join(packageDataDir, "component-configs");
310
- const packageSketchStylesDir = path2.join(packageDataDir, "sketch-styles");
329
+ const packageColorsAndTypeDir = path3.join(packageDataDir, "colors-and-type");
330
+ const packageThemesDir = path3.join(packageDataDir, "themes");
331
+ const packageComponentConfigsDir = path3.join(packageDataDir, "component-configs");
332
+ const packageSketchStylesDir = path3.join(packageDataDir, "sketch-styles");
311
333
  function shippedNames(dataDir, subdir) {
312
334
  try {
313
- const pkgRoot = path2.resolve(dataDir, "..", "..", "..");
314
- const pkg = JSON.parse(fs2.readFileSync(path2.join(pkgRoot, "package.json"), "utf-8"));
335
+ const pkgRoot = path3.resolve(dataDir, "..", "..", "..");
336
+ const pkg = JSON.parse(fs2.readFileSync(path3.join(pkgRoot, "package.json"), "utf-8"));
315
337
  const files = Array.isArray(pkg.files) ? pkg.files : [];
316
338
  const owned = new RegExp(`^src/live-tokens/data/${subdir}/[^/]+\\.json$`);
317
- return files.filter((f) => owned.test(f)).map((f) => path2.basename(f, ".json"));
339
+ return files.filter((f) => owned.test(f)).map((f) => path3.basename(f, ".json"));
318
340
  } catch {
319
341
  return [];
320
342
  }
@@ -323,7 +345,7 @@ function themeFileApi(opts) {
323
345
  if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs2.existsSync(packageComponentsDir)) {
324
346
  COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
325
347
  }
326
- const isLiveStateFile = (file) => file === path2.join(THEMES_DIR, "_active.json") || path2.basename(file) === "_working.json" && (file.startsWith(COLORS_AND_TYPE_DIR + path2.sep) || file.startsWith(COMPONENT_CONFIGS_DIR + path2.sep));
348
+ const isLiveStateFile = (file) => file === path3.join(THEMES_DIR, "_active.json") || path3.basename(file) === "_working.json" && (file.startsWith(COLORS_AND_TYPE_DIR + path3.sep) || file.startsWith(COMPONENT_CONFIGS_DIR + path3.sep));
327
349
  const liveWatch = dataTreeWatch({
328
350
  dirs: [COLORS_AND_TYPE_DIR, COMPONENT_CONFIGS_DIR, THEMES_DIR],
329
351
  isLive: isLiveStateFile
@@ -339,8 +361,8 @@ function themeFileApi(opts) {
339
361
  let r = componentResourceCache.get(comp);
340
362
  if (!r) {
341
363
  r = versionedFileResourceServer({
342
- dir: path2.join(COMPONENT_CONFIGS_DIR, comp),
343
- packageDir: path2.join(packageComponentConfigsDir, comp),
364
+ dir: path3.join(COMPONENT_CONFIGS_DIR, comp),
365
+ packageDir: path3.join(packageComponentConfigsDir, comp),
344
366
  onWrite: liveWatch.noteOwnWrite
345
367
  });
346
368
  componentResourceCache.set(comp, r);
@@ -431,7 +453,7 @@ function themeFileApi(opts) {
431
453
  const productionThemeName = themesResource.getProductionName();
432
454
  const productionTheme = readTheme(productionThemeName)?.theme ?? null;
433
455
  if (themesResource.existingPath(productionThemeName) !== null && !productionTheme?.colorsAndType) {
434
- const tail = `${path2.basename(GENERATED_CSS_PATH)} was left as it was.`;
456
+ const tail = `${path3.basename(GENERATED_CSS_PATH)} was left as it was.`;
435
457
  throw new Error(
436
458
  unreadableThemeReason(productionThemeName) === "colors-and-type" ? `[live-tokens] ${notAThemeError(productionThemeName)} ${tail}` : `[live-tokens] Production theme "${productionThemeName}" is unreadable, so ${tail} Repair the theme file or adopt another theme.`
437
459
  );
@@ -502,12 +524,12 @@ function themeFileApi(opts) {
502
524
  lines.push("");
503
525
  }
504
526
  }
505
- if (!fs2.existsSync(path2.dirname(GENERATED_CSS_PATH))) {
506
- fs2.mkdirSync(path2.dirname(GENERATED_CSS_PATH), { recursive: true });
527
+ if (!fs2.existsSync(path3.dirname(GENERATED_CSS_PATH))) {
528
+ fs2.mkdirSync(path3.dirname(GENERATED_CSS_PATH), { recursive: true });
507
529
  }
508
530
  fs2.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
509
531
  console.log(
510
- `[regenerateTokensCss] Wrote ${path2.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
532
+ `[regenerateTokensCss] Wrote ${path3.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
511
533
  );
512
534
  }
513
535
  function syncFontsToCss(colorsAndTypeData) {
@@ -535,14 +557,14 @@ function themeFileApi(opts) {
535
557
  lines.push("");
536
558
  }
537
559
  const content = lines.join("\n");
538
- if (!fs2.existsSync(path2.dirname(FONTS_CSS_PATH))) {
539
- fs2.mkdirSync(path2.dirname(FONTS_CSS_PATH), { recursive: true });
560
+ if (!fs2.existsSync(path3.dirname(FONTS_CSS_PATH))) {
561
+ fs2.mkdirSync(path3.dirname(FONTS_CSS_PATH), { recursive: true });
540
562
  }
541
563
  fs2.writeFileSync(FONTS_CSS_PATH, content);
542
- console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path2.basename(FONTS_CSS_PATH)}`);
564
+ console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path3.basename(FONTS_CSS_PATH)}`);
543
565
  }
544
566
  function componentNameFromFile(filePath) {
545
- return path2.basename(filePath, ".svelte").toLowerCase();
567
+ return path3.basename(filePath, ".svelte").toLowerCase();
546
568
  }
547
569
  function isThemeAwareComponent(filePath) {
548
570
  try {
@@ -558,7 +580,7 @@ function themeFileApi(opts) {
558
580
  if (!fs2.existsSync(dir)) continue;
559
581
  for (const f of fs2.readdirSync(dir)) {
560
582
  if (!f.endsWith(".svelte")) continue;
561
- const full = path2.join(dir, f);
583
+ const full = path3.join(dir, f);
562
584
  if (!isThemeAwareComponent(full)) continue;
563
585
  const name = componentNameFromFile(f);
564
586
  if (!byName.has(name)) byName.set(name, full);
@@ -591,7 +613,7 @@ function themeFileApi(opts) {
591
613
  (m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
592
614
  );
593
615
  log(
594
- `[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path2.relative(process.cwd(), CSS_PATH)}:
616
+ `[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path3.relative(process.cwd(), CSS_PATH)}:
595
617
  ${lines.join("\n")}
596
618
  These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
597
619
  );
@@ -607,7 +629,7 @@ ${lines.join("\n")}
607
629
  if (!changed) return;
608
630
  fs2.writeFileSync(CSS_PATH, css);
609
631
  log(
610
- `[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path2.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
632
+ `[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path3.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
611
633
  );
612
634
  }
613
635
  function generateDefaultConfig(comp, sourcePath) {
@@ -672,14 +694,14 @@ ${lines.join("\n")}
672
694
  const legacy = [];
673
695
  const check = (dir) => {
674
696
  for (const name of ["_active.json", "_production.json"]) {
675
- const p = path2.join(dir, name);
676
- if (fs2.existsSync(p)) legacy.push(path2.relative(process.cwd(), p));
697
+ const p = path3.join(dir, name);
698
+ if (fs2.existsSync(p)) legacy.push(path3.relative(process.cwd(), p));
677
699
  }
678
700
  };
679
701
  check(COLORS_AND_TYPE_DIR);
680
702
  if (fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
681
703
  for (const entry of fs2.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
682
- if (entry.isDirectory()) check(path2.join(COMPONENT_CONFIGS_DIR, entry.name));
704
+ if (entry.isDirectory()) check(path3.join(COMPONENT_CONFIGS_DIR, entry.name));
683
705
  }
684
706
  }
685
707
  return legacy;
@@ -687,19 +709,19 @@ ${lines.join("\n")}
687
709
  function warnOnLegacyPointers(legacy, bakeHeld, warn) {
688
710
  if (legacy.length === 0) return;
689
711
  warn(
690
- `[live-tokens] ${legacy.length} legacy pointer file(s) left from the pre-working-set data model, starting with ${legacy[0]}. They are no longer read. Run \`npx live-tokens migrate\` to heal the tree.` + (bakeHeld ? ` Until then this tree has named no production theme, so ${path2.basename(GENERATED_CSS_PATH)} is left exactly as it is rather than rebuilt from the default.` : "")
712
+ `[live-tokens] ${legacy.length} legacy pointer file(s) left from the pre-working-set data model, starting with ${legacy[0]}. They are no longer read. Run \`npx live-tokens migrate\` to heal the tree.` + (bakeHeld ? ` Until then this tree has named no production theme, so ${path3.basename(GENERATED_CSS_PATH)} is left exactly as it is rather than rebuilt from the default.` : "")
691
713
  );
692
714
  }
693
715
  function warnOnLegacyLayout(evidence, warn) {
694
716
  warn(
695
- `[live-tokens] This project still uses the data layout from 0.47 and earlier: ${evidence}. Since 0.48 the whole-theme theme files live in \`themes/\` and the colors and type live in \`colors-and-type/\`. Nothing in the data directory was written, the editor cannot save, and ${path2.basename(GENERATED_CSS_PATH)} is left exactly as it is. Run \`npx live-tokens migrate\` to move the directories and heal the tree, then restart the dev server.`
717
+ `[live-tokens] This project still uses the data layout from 0.47 and earlier: ${evidence}. Since 0.48 the whole-theme theme files live in \`themes/\` and the colors and type live in \`colors-and-type/\`. Nothing in the data directory was written, the editor cannot save, and ${path3.basename(GENERATED_CSS_PATH)} is left exactly as it is. Run \`npx live-tokens migrate\` to move the directories and heal the tree, then restart the dev server.`
696
718
  );
697
719
  }
698
720
  function warnOnUnmigratedSketchStyles(warn) {
699
- const legacyDir = path2.join(dataDirs.dataDir, "sketch-presets");
721
+ const legacyDir = path3.join(dataDirs.dataDir, "sketch-presets");
700
722
  if (!fs2.existsSync(legacyDir)) return;
701
723
  warn(
702
- `[live-tokens] Saved sketchstyles still live under the retired ${path2.basename(legacyDir)}/ directory name. Run \`npx live-tokens migrate\` to rename it to ${path2.basename(SKETCH_STYLES_DIR)}/; until then they will not show up in the Sketchstyle view.`
724
+ `[live-tokens] Saved sketchstyles still live under the retired ${path3.basename(legacyDir)}/ directory name. Run \`npx live-tokens migrate\` to rename it to ${path3.basename(SKETCH_STYLES_DIR)}/; until then they will not show up in the Sketchstyle view.`
703
725
  );
704
726
  }
705
727
  const diskThemeResolvers = {
@@ -731,7 +753,7 @@ ${lines.join("\n")}
731
753
  return "corrupt";
732
754
  }
733
755
  function notAThemeError(fileName) {
734
- return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path2.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme skill "create-theme".`;
756
+ return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path3.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme skill "create-theme".`;
735
757
  }
736
758
  function respondUnreadableTheme(res, fileName, missingError) {
737
759
  switch (unreadableThemeReason(fileName)) {
@@ -753,7 +775,7 @@ ${lines.join("\n")}
753
775
  fs2.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
754
776
  }
755
777
  function migrateLocalThemes(warn) {
756
- if (path2.resolve(THEMES_DIR) === path2.resolve(packageThemesDir)) return;
778
+ if (path3.resolve(THEMES_DIR) === path3.resolve(packageThemesDir)) return;
757
779
  if (!fs2.existsSync(THEMES_DIR)) return;
758
780
  for (const file of fs2.readdirSync(THEMES_DIR)) {
759
781
  if (!file.endsWith(".json") || file.startsWith("_")) continue;
@@ -761,7 +783,7 @@ ${lines.join("\n")}
761
783
  if (fileName === "default") continue;
762
784
  let raw;
763
785
  try {
764
- raw = JSON.parse(fs2.readFileSync(path2.join(THEMES_DIR, file), "utf-8"));
786
+ raw = JSON.parse(fs2.readFileSync(path3.join(THEMES_DIR, file), "utf-8"));
765
787
  } catch {
766
788
  continue;
767
789
  }
@@ -1432,15 +1454,14 @@ data: ${JSON.stringify(state)}
1432
1454
  // spelled "no override" that way (normalizeTheme.ts's pointer-
1433
1455
  // resolution loop treats it identically), so it always meant *this
1434
1456
  // install's* default, not something the bundle carries. The
1435
- // completeness fill (Wave 2 of docs/plans/theme-completeness.md) relies
1436
- // on that fall-through: without it, `readComponentConfig(comp,
1437
- // 'default')` answers undefined for every component (no bundle ever
1438
- // carries a `<comp>/default` entry), the fill loop no-ops 25 times, and
1439
- // an incomplete bundle stays incomplete forever — exactly the drift RJC
1440
- // 10 exists to prevent.
1457
+ // completeness fill relies on that fall-through: without it,
1458
+ // `readComponentConfig(comp, 'default')` answers undefined for every
1459
+ // component (no bundle ever carries a `<comp>/default` entry), the
1460
+ // fill loop no-ops 25 times, and an incomplete bundle stays incomplete
1461
+ // forever.
1441
1462
  readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`] ?? (name === "default" ? readComponentConfig(comp, "default") : void 0),
1442
1463
  // Installed components are a property of this install, not of the
1443
- // bundle being imported, so the completeness fill (Wave 2) uses the
1464
+ // bundle being imported, so the completeness fill uses the
1444
1465
  // same list the disk resolver does.
1445
1466
  listComponentNames,
1446
1467
  normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
@@ -1650,7 +1671,9 @@ data: ${JSON.stringify(state)}
1650
1671
  { method: "POST", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
1651
1672
  { method: "DELETE", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed }
1652
1673
  ];
1653
- const isEditorOwnedJson = (file) => file.endsWith(".json") && path2.resolve(file).startsWith(dataDirs.dataDir + path2.sep);
1674
+ const isEditorOwnedJson = (file) => file.endsWith(".json") && path3.resolve(file).startsWith(dataDirs.dataDir + path3.sep);
1675
+ let buildLogger = null;
1676
+ let buildChecked = false;
1654
1677
  return {
1655
1678
  name: "theme-file-api",
1656
1679
  config() {
@@ -1663,6 +1686,15 @@ data: ${JSON.stringify(state)}
1663
1686
  server: { watch: { ignored: [isEditorOwnedJson] } }
1664
1687
  };
1665
1688
  },
1689
+ configResolved(config) {
1690
+ buildLogger = config.command === "build" && opts.checks !== false ? config.logger : null;
1691
+ },
1692
+ async buildStart() {
1693
+ if (!buildLogger || buildChecked) return;
1694
+ buildChecked = true;
1695
+ const failure = await checkBeforeBuild({ root: process.cwd(), logger: buildLogger });
1696
+ if (failure) this.error(failure);
1697
+ },
1666
1698
  configureServer(server) {
1667
1699
  legacyLayout = detectLegacyLayout({
1668
1700
  dataDir: dataDirs.dataDir,
@@ -1704,7 +1736,7 @@ data: ${JSON.stringify(state)}
1704
1736
  },
1705
1737
  handleHotUpdate(ctx) {
1706
1738
  if (legacyLayout) return;
1707
- const normalized = path2.resolve(ctx.file);
1739
+ const normalized = path3.resolve(ctx.file);
1708
1740
  if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
1709
1741
  if (!normalized.endsWith(".svelte")) return;
1710
1742
  if (!isThemeAwareComponent(normalized)) return;