@motion-proto/live-tokens 0.52.1 → 0.53.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 (60) hide show
  1. package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +7 -1
  2. package/CHANGELOG.md +63 -0
  3. package/bin/adjust.mjs +16 -39
  4. package/bin/generate-theme.mjs +20 -6
  5. package/dist-plugin/adjust/index.cjs +781 -21
  6. package/dist-plugin/adjust/index.d.cts +2 -1
  7. package/dist-plugin/adjust/index.d.ts +2 -1
  8. package/dist-plugin/adjust/index.js +43 -21
  9. package/dist-plugin/{chunk-6OZFXIQI.js → chunk-232GZGQU.js} +207 -12
  10. package/dist-plugin/chunk-NE6N66EE.js +753 -0
  11. package/dist-plugin/{chunk-JIGIIE5P.js → chunk-PB2JTK2H.js} +123 -0
  12. package/dist-plugin/generateColorsAndType/index.cjs +48 -32
  13. package/dist-plugin/generateColorsAndType/index.d.cts +2 -1
  14. package/dist-plugin/generateColorsAndType/index.d.ts +2 -1
  15. package/dist-plugin/generateColorsAndType/index.js +7 -773
  16. package/dist-plugin/{themeTypes-Dky03HL9.d.cts → index-DpTIRZ2H.d.cts} +49 -21
  17. package/dist-plugin/{themeTypes-Dky03HL9.d.ts → index-DpTIRZ2H.d.ts} +49 -21
  18. package/dist-plugin/index.cjs +1159 -85
  19. package/dist-plugin/index.js +102 -80
  20. package/dist-plugin/migrateData/index.cjs +956 -14
  21. package/dist-plugin/migrateData/index.js +10 -3
  22. package/package.json +6 -4
  23. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +9 -23
  24. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +8 -8
  25. package/src/editor/core/components/adjustAliases.ts +49 -22
  26. package/src/editor/core/components/aliasKinds.ts +7 -2
  27. package/src/editor/core/components/componentConfigService.ts +1 -1
  28. package/src/editor/core/store/editorStore.ts +7 -197
  29. package/src/editor/core/store/editorTypes.ts +9 -30
  30. package/src/editor/core/themes/lookSummary.ts +3 -25
  31. package/src/editor/core/themes/migrateComponentConfig.ts +205 -0
  32. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +1 -1
  33. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +1 -1
  34. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +1 -1
  35. package/src/editor/core/themes/parsers/gradient.ts +211 -0
  36. package/src/editor/core/themes/slices/gradients.ts +5 -58
  37. package/src/editor/core/themes/themeService.ts +22 -9
  38. package/src/editor/core/themes/themeTypes.ts +71 -21
  39. package/src/editor/docs/content/themes-workflow.md +9 -11
  40. package/src/editor/docs/content/where-themes-live.md +2 -2
  41. package/src/editor/docs/content.generated.ts +2 -2
  42. package/src/editor/index.ts +1 -0
  43. package/src/editor/styles/ui-editor.css +5 -2
  44. package/src/editor/ui/PaletteEditor.svelte +30 -15
  45. package/src/editor/ui/ThemePanel.svelte +24 -15
  46. package/src/editor/ui/Toggle.svelte +22 -22
  47. package/src/editor/ui/UIPillButton.svelte +1 -1
  48. package/src/editor/ui/UIReveal.svelte +47 -0
  49. package/src/editor/ui/UISquareButton.svelte +1 -1
  50. package/src/editor/ui/colors/ColorsTab.svelte +4 -5
  51. package/src/editor/ui/palette/OverridesPanel.svelte +4 -3
  52. package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -3
  53. package/src/editor/ui/palette/ScaleCurveEditor.svelte +3 -2
  54. package/src/live-tokens/data/themes/autumn.json +77 -41
  55. package/src/live-tokens/data/themes/halloween.json +53 -52
  56. package/src/live-tokens/data/themes/midnight-study.json +136 -68
  57. package/src/live-tokens/data/themes/ocean.json +77 -41
  58. package/src/live-tokens/data/themes/royal-velvet.json +43 -42
  59. package/src/live-tokens/data/themes/spring-meadow.json +86 -52
  60. package/src/live-tokens/data/themes/sunset.json +101 -40
@@ -1,8 +1,11 @@
1
1
  import {
2
+ formatGradientValue,
2
3
  palettesToVars,
4
+ parseColorOpacity,
5
+ parseGradientValue,
3
6
  reconcilePalettesFromCssVars,
4
7
  sanitizeFileName
5
- } from "./chunk-JIGIIE5P.js";
8
+ } from "./chunk-PB2JTK2H.js";
6
9
  import {
7
10
  THEME_SCHEMA_VERSION,
8
11
  detectLegacyLayout,
@@ -10,7 +13,10 @@ import {
10
13
  nextAvailableName,
11
14
  normalizeTheme,
12
15
  versionedFileResourceServer
13
- } from "./chunk-6OZFXIQI.js";
16
+ } from "./chunk-232GZGQU.js";
17
+ import {
18
+ CURRENT_COMPONENT_SCHEMA_VERSION
19
+ } from "./chunk-NE6N66EE.js";
14
20
  import {
15
21
  TOKENS_CSS_MIGRATIONS,
16
22
  extractGlobalRootBody,
@@ -29,14 +35,6 @@ import {
29
35
  import fs from "fs";
30
36
  import path from "path";
31
37
 
32
- // src/editor/core/themes/parsers/colorOpacity.ts
33
- var COLOR_OPACITY_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+)%,\s*transparent\)$/i;
34
- function parseColorOpacity(value) {
35
- const m = value.trim().match(COLOR_OPACITY_RE);
36
- if (!m) return null;
37
- return { name: m[1], opacity: parseInt(m[2], 10) };
38
- }
39
-
40
38
  // src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts
41
39
  var round = (v, dp) => {
42
40
  const f = 10 ** dp;
@@ -106,8 +104,29 @@ var PKG_VERSION = (() => {
106
104
  })();
107
105
  var BUNDLE_KIND_BY_VERSION = {
108
106
  1: "manifest-bundle",
107
+ 3: "theme-bundle",
109
108
  [THEME_SCHEMA_VERSION]: "theme-bundle"
110
109
  };
110
+ function extractAliasDeclarations(body) {
111
+ const aliases = {};
112
+ const re = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
113
+ let m;
114
+ while ((m = re.exec(body)) !== null) {
115
+ const value = m[2].trim();
116
+ const plain = value.match(/^var\((--[a-z0-9-]+)\)$/i);
117
+ if (plain) {
118
+ aliases[m[1]] = plain[1];
119
+ continue;
120
+ }
121
+ if (parseColorOpacity(value)) {
122
+ aliases[m[1]] = value;
123
+ continue;
124
+ }
125
+ const gradient = parseGradientValue(value);
126
+ if (gradient) aliases[m[1]] = { kind: "gradient", value: gradient };
127
+ }
128
+ return aliases;
129
+ }
111
130
  function themeFileApi(opts) {
112
131
  const dataDirs = resolveDataDirs({
113
132
  dataDir: opts.dataDir,
@@ -284,6 +303,7 @@ function themeFileApi(opts) {
284
303
  const overrides = [];
285
304
  const defaultAliases = defaultCfg.aliases ?? {};
286
305
  for (const [varName, semanticValue] of Object.entries(prodCfg.aliases ?? {})) {
306
+ if (!(varName in defaultAliases)) continue;
287
307
  if (!aliasValuesEqual(defaultAliases[varName], semanticValue)) {
288
308
  overrides.push([varName, semanticValue]);
289
309
  }
@@ -346,18 +366,6 @@ function themeFileApi(opts) {
346
366
  fs.writeFileSync(FONTS_CSS_PATH, content);
347
367
  console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path.basename(FONTS_CSS_PATH)}`);
348
368
  }
349
- function extractAliasDeclarations(body) {
350
- const aliases = {};
351
- const re = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
352
- let m;
353
- while ((m = re.exec(body)) !== null) {
354
- const value = m[2].trim();
355
- const plain = value.match(/^var\((--[a-z0-9-]+)\)$/i);
356
- if (plain) aliases[m[1]] = plain[1];
357
- else if (parseColorOpacity(value)) aliases[m[1]] = value;
358
- }
359
- return aliases;
360
- }
361
369
  function componentNameFromFile(filePath) {
362
370
  return path.basename(filePath, ".svelte").toLowerCase();
363
371
  }
@@ -433,17 +441,10 @@ ${lines.join("\n")}
433
441
  r.ensureDir();
434
442
  const defaultPath = r.filePath("default");
435
443
  const sourceStat = fs.statSync(sourcePath);
436
- if (fs.existsSync(defaultPath)) {
437
- const defaultStat = fs.statSync(defaultPath);
438
- if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
439
- }
440
- const source = fs.readFileSync(sourcePath, "utf-8");
441
- const body = extractGlobalRootBody(source);
442
- const aliases = extractAliasDeclarations(body);
443
- const now = (/* @__PURE__ */ new Date()).toISOString();
444
- let createdAt = now;
444
+ let createdAt = (/* @__PURE__ */ new Date()).toISOString();
445
445
  let existingUpdatedAt;
446
446
  let existingAliases;
447
+ let existingSchemaVersion;
447
448
  if (fs.existsSync(defaultPath)) {
448
449
  try {
449
450
  const existing = JSON.parse(fs.readFileSync(defaultPath, "utf-8"));
@@ -452,25 +453,29 @@ ${lines.join("\n")}
452
453
  if (existing.aliases && typeof existing.aliases === "object") {
453
454
  existingAliases = existing.aliases;
454
455
  }
456
+ if (typeof existing.schemaVersion === "number") existingSchemaVersion = existing.schemaVersion;
455
457
  } catch {
456
458
  }
457
- }
458
- if (existingAliases) {
459
- for (const [token, val] of Object.entries(existingAliases)) {
460
- if (val !== null && typeof val === "object" && !(token in aliases)) {
461
- aliases[token] = val;
462
- }
459
+ const defaultStat = fs.statSync(defaultPath);
460
+ if (defaultStat.mtimeMs >= sourceStat.mtimeMs && existingSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION) {
461
+ return;
463
462
  }
464
463
  }
464
+ const now = (/* @__PURE__ */ new Date()).toISOString();
465
+ const source = fs.readFileSync(sourcePath, "utf-8");
466
+ const body = extractGlobalRootBody(source);
467
+ const aliases = extractAliasDeclarations(body);
465
468
  const aliasesUnchanged = existingAliases !== void 0 && JSON.stringify(existingAliases) === JSON.stringify(aliases);
469
+ const schemaStampCurrent = existingSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION;
466
470
  const defaultConfig = {
467
471
  name: "default",
468
472
  component: comp,
469
473
  createdAt,
470
474
  updatedAt: aliasesUnchanged && existingUpdatedAt ? existingUpdatedAt : now,
471
- aliases
475
+ aliases,
476
+ schemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION
472
477
  };
473
- if (aliasesUnchanged && existingUpdatedAt) {
478
+ if (aliasesUnchanged && schemaStampCurrent && existingUpdatedAt) {
474
479
  const t = /* @__PURE__ */ new Date();
475
480
  fs.utimesSync(defaultPath, t, t);
476
481
  return;
@@ -518,6 +523,7 @@ ${lines.join("\n")}
518
523
  const diskThemeResolvers = {
519
524
  readColorsAndType: (name) => colorsAndTypeResource.readJson(sanitizeFileName(name)),
520
525
  readComponentConfig: (comp, name) => readComponentConfig(comp, sanitizeFileName(name)),
526
+ listComponentNames,
521
527
  normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
522
528
  };
523
529
  function readTheme(fileName) {
@@ -543,7 +549,7 @@ ${lines.join("\n")}
543
549
  return "corrupt";
544
550
  }
545
551
  function notAThemeError(fileName) {
546
- return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path.basename(COLORS_AND_TYPE_DIR)}/ or delete it.`;
552
+ return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme generator skill "generate-theme".`;
547
553
  }
548
554
  function respondUnreadableTheme(res, fileName, missingError) {
549
555
  switch (unreadableThemeReason(fileName)) {
@@ -605,19 +611,26 @@ ${lines.join("\n")}
605
611
  const componentConfigs = {};
606
612
  for (const comp of listComponentNames()) {
607
613
  const cfg = readComponentConfig(comp, "default");
608
- if (cfg) componentConfigs[comp] = cfg;
614
+ if (cfg) {
615
+ const { schemaVersion: _standaloneStamp, ...rest } = cfg;
616
+ componentConfigs[comp] = rest;
617
+ }
609
618
  }
610
- if (existing?.schemaVersion === THEME_SCHEMA_VERSION && JSON.stringify(existing.colorsAndType) === JSON.stringify(colorsAndType) && JSON.stringify(existing.componentConfigs) === JSON.stringify(componentConfigs)) {
619
+ const contentUnchanged = JSON.stringify(existing?.colorsAndType) === JSON.stringify(colorsAndType) && JSON.stringify(existing?.componentConfigs) === JSON.stringify(componentConfigs);
620
+ if (existing?.schemaVersion === THEME_SCHEMA_VERSION && existing?.componentSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION && contentUnchanged) {
611
621
  return;
612
622
  }
613
623
  const now = (/* @__PURE__ */ new Date()).toISOString();
614
624
  writeTheme("default", {
615
625
  name: "Motion Proto",
616
626
  createdAt: typeof existing?.createdAt === "string" ? existing.createdAt : now,
617
- updatedAt: now,
627
+ // A stamp-only rewrite (componentSchemaVersion catching up) is not a
628
+ // content change, so it doesn't bump this the way a real edit would.
629
+ updatedAt: contentUnchanged && typeof existing?.updatedAt === "string" ? existing.updatedAt : now,
618
630
  schemaVersion: THEME_SCHEMA_VERSION,
619
631
  colorsAndType,
620
- componentConfigs
632
+ componentConfigs,
633
+ componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION
621
634
  });
622
635
  }
623
636
  function ensureThemesDir(warn) {
@@ -649,14 +662,24 @@ ${lines.join("\n")}
649
662
  if (!shipped) return null;
650
663
  return { data: normalizeColorsAndType(shipped), source: "default" };
651
664
  }
665
+ const loggedMissingThemeEntries = /* @__PURE__ */ new Set();
666
+ function resolveComponentConfigOrDefault(comp, theme) {
667
+ const embedded = theme?.componentConfigs[comp];
668
+ if (embedded) return { ...embedded, component: comp };
669
+ if (!loggedMissingThemeEntries.has(comp)) {
670
+ loggedMissingThemeEntries.add(comp);
671
+ console.warn(
672
+ `[live-tokens] "${comp}" has no entry in the open theme; resolving from its shipped default.`
673
+ );
674
+ }
675
+ return readComponentConfig(comp, "default");
676
+ }
652
677
  function resolveLiveComponentConfig(comp, theme) {
653
678
  const working = componentResource(comp).readWorking();
654
679
  if (working !== null) return { data: working, source: "working" };
655
- const embedded = theme?.componentConfigs[comp];
656
- if (embedded) return { data: { ...embedded, component: comp }, source: "theme" };
657
- const cfg = readComponentConfig(comp, "default");
680
+ const cfg = resolveComponentConfigOrDefault(comp, theme);
658
681
  if (!cfg) return null;
659
- return { data: cfg, source: "default" };
682
+ return { data: cfg, source: "theme" };
660
683
  }
661
684
  function resolveSavedColorsAndType(theme) {
662
685
  if (theme?.colorsAndType) return theme.colorsAndType;
@@ -664,9 +687,7 @@ ${lines.join("\n")}
664
687
  return shipped ? normalizeColorsAndType(shipped) : null;
665
688
  }
666
689
  function resolveSavedComponentConfig(comp, theme) {
667
- const embedded = theme?.componentConfigs[comp];
668
- if (embedded) return { ...embedded, component: comp };
669
- return readComponentConfig(comp, "default");
690
+ return resolveComponentConfigOrDefault(comp, theme);
670
691
  }
671
692
  function sameJsonValue(a, b) {
672
693
  if (a === null || b === null) return a === b;
@@ -702,26 +723,9 @@ ${lines.join("\n")}
702
723
  if (entry.isDirectory()) componentResource(entry.name).clearWorking();
703
724
  }
704
725
  }
705
- function formatAliasGradient(v) {
706
- const stopColor = (s) => {
707
- const base = s.color.startsWith("--") ? `var(${s.color})` : s.color;
708
- const opacity = s.opacity ?? 100;
709
- return opacity >= 100 ? base : `color-mix(in srgb, ${base} ${opacity}%, transparent)`;
710
- };
711
- if (v.type === "none") return "transparent";
712
- if (v.type === "solid") {
713
- const first = v.stops[0];
714
- if (!first) return "transparent";
715
- return stopColor(first);
716
- }
717
- const stops = v.stops.map((s) => `${stopColor(s)} ${s.position}%`).join(", ");
718
- if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
719
- const radial = v.radius && v.radius > 0 ? `circle ${v.radius}px at center` : "circle";
720
- return `radial-gradient(${radial}, ${stops})`;
721
- }
722
726
  function aliasValueToCss(v) {
723
727
  if (typeof v === "string") return v.startsWith("--") ? `var(${v})` : v;
724
- return formatAliasGradient(v.value);
728
+ return formatGradientValue(v.value);
725
729
  }
726
730
  function aliasValuesEqual(a, b) {
727
731
  if (a === void 0 || b === void 0) return a === b;
@@ -871,13 +875,9 @@ ${lines.join("\n")}
871
875
  }
872
876
  }
873
877
  async function handleListComponents({ res }) {
874
- const theme = activeTheme();
875
878
  const components = listComponentNames().map((comp) => ({
876
879
  name: comp,
877
- // Presence, not a parse: one corrupt buffer must not take the listing
878
- // down, and the door only has to say where each component's live config
879
- // comes from.
880
- source: componentResource(comp).hasWorking() ? "working" : theme?.componentConfigs[comp] ? "theme" : "default"
880
+ source: componentResource(comp).hasWorking() ? "working" : "theme"
881
881
  }));
882
882
  jsonResponse(res, 200, { components });
883
883
  }
@@ -888,6 +888,7 @@ ${lines.join("\n")}
888
888
  }
889
889
  async function handleGetComponentActive({ params, res }) {
890
890
  const [comp] = params;
891
+ if (rejectUnknownComponent(res, comp)) return;
891
892
  const resolved = resolveLiveComponentConfig(comp, activeTheme());
892
893
  if (!resolved) {
893
894
  jsonResponse(res, 404, { error: "Active config not found" });
@@ -1134,9 +1135,11 @@ ${lines.join("\n")}
1134
1135
  respondUnreadableTheme(res, fileName, "Theme not found");
1135
1136
  return;
1136
1137
  }
1137
- const { theme } = read;
1138
+ const { theme, filled } = read;
1138
1139
  if (!theme.colorsAndType) {
1139
- jsonResponse(res, 422, { error: "This theme carries no colors and type" });
1140
+ jsonResponse(res, 422, {
1141
+ error: 'This theme carries no colors and type. Build one with the theme generator skill "generate-theme".'
1142
+ });
1140
1143
  return;
1141
1144
  }
1142
1145
  clearAllWorking();
@@ -1160,7 +1163,8 @@ ${lines.join("\n")}
1160
1163
  _source: resolvedColorsAndType.source
1161
1164
  },
1162
1165
  componentConfigs: resolvedConfigs,
1163
- skippedComponents
1166
+ skippedComponents,
1167
+ filled
1164
1168
  });
1165
1169
  }
1166
1170
  async function handleExportTheme({ params, res }) {
@@ -1215,10 +1219,27 @@ ${lines.join("\n")}
1215
1219
  }
1216
1220
  const bundleResolvers = {
1217
1221
  readColorsAndType: () => bundle.theme,
1218
- readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`],
1222
+ // A named ref resolves inside the bundle only — a v1 bundle's
1223
+ // `${comp}/${name}` map is itself the sender's data, and a stale name
1224
+ // must still land in `dropped` rather than quietly picking up this
1225
+ // install's copy. But `'default'` never named a bundled entry; v1
1226
+ // spelled "no override" that way (normalizeTheme.ts's pointer-
1227
+ // resolution loop treats it identically), so it always meant *this
1228
+ // install's* default, not something the bundle carries. The
1229
+ // completeness fill (Wave 2 of docs/plans/theme-completeness.md) relies
1230
+ // on that fall-through: without it, `readComponentConfig(comp,
1231
+ // 'default')` answers undefined for every component (no bundle ever
1232
+ // carries a `<comp>/default` entry), the fill loop no-ops 25 times, and
1233
+ // an incomplete bundle stays incomplete forever — exactly the drift RJC
1234
+ // 10 exists to prevent.
1235
+ readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`] ?? (name === "default" ? readComponentConfig(comp, "default") : void 0),
1236
+ // Installed components are a property of this install, not of the
1237
+ // bundle being imported, so the completeness fill (Wave 2) uses the
1238
+ // same list the disk resolver does.
1239
+ listComponentNames,
1219
1240
  normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
1220
1241
  };
1221
- const { theme, dropped } = normalizeTheme(bundle.manifest, bundleResolvers);
1242
+ const { theme, dropped, filled } = normalizeTheme(bundle.manifest, bundleResolvers);
1222
1243
  if (theme.colorsAndType) theme.colorsAndType = normalizeColorsAndType(theme.colorsAndType);
1223
1244
  if (!theme.colorsAndType) {
1224
1245
  jsonResponse(res, 400, { error: "Bundle carries no theme" });
@@ -1239,7 +1260,8 @@ ${lines.join("\n")}
1239
1260
  ok: true,
1240
1261
  theme: finalName,
1241
1262
  renames,
1242
- dropped
1263
+ dropped,
1264
+ filled
1243
1265
  });
1244
1266
  }
1245
1267
  async function handleAdoptLook({ res }) {