@motion-proto/live-tokens 0.50.0 → 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 (91) hide show
  1. package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +7 -1
  2. package/CHANGELOG.md +108 -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-76TFDTJO.js → chunk-PB2JTK2H.js} +179 -128
  12. package/dist-plugin/{chunk-44RSTAII.js → chunk-TKZBVIW5.js} +33 -5
  13. package/dist-plugin/chunk-YR6GPXW2.js +129 -0
  14. package/dist-plugin/generateColorsAndType/index.cjs +135 -63
  15. package/dist-plugin/generateColorsAndType/index.d.cts +2 -1
  16. package/dist-plugin/generateColorsAndType/index.d.ts +2 -1
  17. package/dist-plugin/generateColorsAndType/index.js +28 -784
  18. package/dist-plugin/{themeTypes-DSwKq-bj.d.ts → index-DpTIRZ2H.d.cts} +67 -26
  19. package/dist-plugin/{themeTypes-DSwKq-bj.d.cts → index-DpTIRZ2H.d.ts} +67 -26
  20. package/dist-plugin/index.cjs +1254 -140
  21. package/dist-plugin/index.js +106 -82
  22. package/dist-plugin/migrateData/index.cjs +956 -14
  23. package/dist-plugin/migrateData/index.js +10 -3
  24. package/dist-plugin/tokensCssMigrations/index.cjs +71 -5
  25. package/dist-plugin/tokensCssMigrations/index.d.cts +20 -9
  26. package/dist-plugin/tokensCssMigrations/index.d.ts +20 -9
  27. package/dist-plugin/tokensCssMigrations/index.js +4 -1
  28. package/package.json +8 -4
  29. package/src/editor/component-editor/NotificationEditor.svelte +6 -0
  30. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +9 -23
  31. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +8 -8
  32. package/src/editor/core/components/adjustAliases.ts +49 -22
  33. package/src/editor/core/components/aliasKinds.ts +7 -2
  34. package/src/editor/core/components/componentConfigService.ts +1 -1
  35. package/src/editor/core/palettes/oklch.ts +3 -137
  36. package/src/editor/core/palettes/paletteDerivation.ts +79 -37
  37. package/src/editor/core/store/editorStore.ts +7 -197
  38. package/src/editor/core/store/editorTypes.ts +9 -30
  39. package/src/editor/core/themes/generateColorsAndType.ts +18 -8
  40. package/src/editor/core/themes/lookSummary.ts +3 -25
  41. package/src/editor/core/themes/migrateComponentConfig.ts +205 -0
  42. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +1 -1
  43. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +1 -1
  44. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +1 -1
  45. package/src/editor/core/themes/parsers/gradient.ts +211 -0
  46. package/src/editor/core/themes/slices/gradients.ts +5 -58
  47. package/src/editor/core/themes/themeService.ts +22 -9
  48. package/src/editor/core/themes/themeTypes.ts +89 -27
  49. package/src/editor/docs/content/editing-tokens.md +6 -2
  50. package/src/editor/docs/content/themes-workflow.md +9 -11
  51. package/src/editor/docs/content/where-themes-live.md +2 -2
  52. package/src/editor/docs/content.generated.ts +3 -3
  53. package/src/editor/index.ts +1 -0
  54. package/src/editor/styles/ui-editor.css +5 -2
  55. package/src/editor/ui/BezierCurveEditor.svelte +90 -67
  56. package/src/editor/ui/PaletteEditor.svelte +180 -59
  57. package/src/editor/ui/ThemePanel.svelte +24 -15
  58. package/src/editor/ui/Toggle.svelte +36 -24
  59. package/src/editor/ui/UIPillButton.svelte +1 -1
  60. package/src/editor/ui/UIReveal.svelte +47 -0
  61. package/src/editor/ui/UISquareButton.svelte +1 -1
  62. package/src/editor/ui/colors/ColorStory.svelte +12 -8
  63. package/src/editor/ui/colors/ColorsTab.svelte +4 -5
  64. package/src/editor/ui/curveEngine.ts +101 -21
  65. package/src/editor/ui/palette/OverridesPanel.svelte +23 -8
  66. package/src/editor/ui/palette/PaletteBase.svelte +17 -78
  67. package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -3
  68. package/src/editor/ui/palette/ScaleCurveEditor.svelte +110 -20
  69. package/src/editor/ui/palette/curveSummary.ts +20 -0
  70. package/src/editor/ui/palette/dockMagnify.ts +5 -13
  71. package/src/editor/ui/palette/paletteMath.ts +7 -4
  72. package/src/live-tokens/data/colors-and-type/autumn.json +528 -528
  73. package/src/live-tokens/data/colors-and-type/default.json +247 -247
  74. package/src/live-tokens/data/colors-and-type/halloween.json +532 -532
  75. package/src/live-tokens/data/colors-and-type/midnight-study.json +538 -538
  76. package/src/live-tokens/data/colors-and-type/ocean.json +521 -521
  77. package/src/live-tokens/data/colors-and-type/royal-velvet.json +532 -532
  78. package/src/live-tokens/data/colors-and-type/spring-meadow.json +504 -504
  79. package/src/live-tokens/data/colors-and-type/sunset.json +531 -531
  80. package/src/live-tokens/data/themes/autumn.json +604 -568
  81. package/src/live-tokens/data/themes/halloween.json +584 -583
  82. package/src/live-tokens/data/themes/midnight-study.json +673 -605
  83. package/src/live-tokens/data/themes/ocean.json +597 -561
  84. package/src/live-tokens/data/themes/royal-velvet.json +574 -573
  85. package/src/live-tokens/data/themes/spring-meadow.json +590 -556
  86. package/src/live-tokens/data/themes/sunset.json +632 -571
  87. package/src/live-tokens/data/tokens.generated.css +282 -282
  88. package/src/system/components/FloatingTokenTags.css +0 -1
  89. package/src/system/internal/backgroundContrast.ts +7 -0
  90. package/src/system/internal/oklch.ts +189 -0
  91. package/src/system/styles/tokens.css +283 -283
@@ -59,32 +59,134 @@ function parseColorOpacity(value) {
59
59
  if (!m) return null;
60
60
  return { name: m[1], opacity: parseInt(m[2], 10) };
61
61
  }
62
+ function formatColorOpacity(name, opacity) {
63
+ return `color-mix(in srgb, var(${name}) ${opacity}%, transparent)`;
64
+ }
65
+
66
+ // src/editor/core/themes/parsers/gradient.ts
67
+ var RADIAL_BASE_PX = 100;
68
+ function formatStopColor(s) {
69
+ const opacity = s.opacity ?? 100;
70
+ if (s.color.startsWith("--")) {
71
+ return opacity >= 100 ? `var(${s.color})` : formatColorOpacity(s.color, opacity);
72
+ }
73
+ return opacity >= 100 ? s.color : `color-mix(in srgb, ${s.color} ${opacity}%, transparent)`;
74
+ }
75
+ function formatGradientStops(stops) {
76
+ return stops.map((s) => `${formatStopColor(s)} ${s.position}%`).join(", ");
77
+ }
78
+ function formatRadialShape(v) {
79
+ const ax = v.aspectX ?? 1;
80
+ const ay = v.aspectY ?? 1;
81
+ if (ax === 1 && ay === 1) {
82
+ return v.radius && v.radius > 0 ? `circle ${v.radius}px` : "circle";
83
+ }
84
+ const base = v.radius && v.radius > 0 ? v.radius : RADIAL_BASE_PX;
85
+ return `ellipse ${(base * ax).toFixed(2)}px ${(base * ay).toFixed(2)}px`;
86
+ }
87
+ function formatGradientValue(v) {
88
+ if (v.type === "none") return "transparent";
89
+ if (v.type === "solid") {
90
+ const first = v.stops[0];
91
+ return first ? formatStopColor(first) : "transparent";
92
+ }
93
+ const stops = formatGradientStops(v.stops);
94
+ if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
95
+ return `radial-gradient(${formatRadialShape(v)} at ${v.centerX ?? 50}% 50%, ${stops})`;
96
+ }
97
+ var NUM = String.raw`-?\d+(?:\.\d+)?`;
98
+ var LINEAR_RE = new RegExp(String.raw`^linear-gradient\(\s*(${NUM})deg\s*,\s*(.+)\)$`, "i");
99
+ var RADIAL_RE = new RegExp(
100
+ String.raw`^radial-gradient\(\s*(circle|circle\s+${NUM}px|ellipse\s+${NUM}px\s+${NUM}px)\s+at\s+(${NUM})%\s+50%\s*,\s*(.+)\)$`,
101
+ "i"
102
+ );
103
+ var STOP_RE = new RegExp(String.raw`^(.*?)\s+(${NUM})%$`);
104
+ var CIRCLE_RADIUS_RE = new RegExp(String.raw`^circle\s+(${NUM})px$`, "i");
105
+ var ELLIPSE_RE = new RegExp(String.raw`^ellipse\s+(${NUM})px\s+(${NUM})px$`, "i");
106
+ var VAR_RE = /^var\((--[a-z0-9-]+)\)$/i;
107
+ function splitTopLevel(s) {
108
+ const parts = [];
109
+ let depth = 0;
110
+ let start = 0;
111
+ for (let i = 0; i < s.length; i++) {
112
+ const c = s[i];
113
+ if (c === "(") depth++;
114
+ else if (c === ")") depth--;
115
+ else if (c === "," && depth === 0) {
116
+ parts.push(s.slice(start, i));
117
+ start = i + 1;
118
+ }
119
+ }
120
+ parts.push(s.slice(start));
121
+ return parts;
122
+ }
123
+ function parseStop(raw) {
124
+ const m = raw.trim().match(STOP_RE);
125
+ if (!m) return null;
126
+ const position = parseFloat(m[2]);
127
+ const color = m[1].trim();
128
+ const opacity = parseColorOpacity(color);
129
+ if (opacity) return { position, color: opacity.name, opacity: opacity.opacity };
130
+ const token = color.match(VAR_RE);
131
+ return { position, color: token ? token[1] : color };
132
+ }
133
+ function parseStops(raw) {
134
+ const stops = [];
135
+ for (const part of splitTopLevel(raw)) {
136
+ const stop = parseStop(part);
137
+ if (!stop) return null;
138
+ stops.push(stop);
139
+ }
140
+ return stops.length > 0 ? stops : null;
141
+ }
142
+ function parseRadialShape(raw) {
143
+ const shape = raw.trim();
144
+ if (/^circle$/i.test(shape)) return {};
145
+ const circle = shape.match(CIRCLE_RADIUS_RE);
146
+ if (circle) return { radius: parseFloat(circle[1]) };
147
+ const ellipse = shape.match(ELLIPSE_RE);
148
+ if (!ellipse) return null;
149
+ const ax = parseFloat(ellipse[1]) / RADIAL_BASE_PX;
150
+ const ay = parseFloat(ellipse[2]) / RADIAL_BASE_PX;
151
+ return { ...ax === 1 ? {} : { aspectX: ax }, ...ay === 1 ? {} : { aspectY: ay } };
152
+ }
153
+ function parseGradientValue(value) {
154
+ const css = value.trim();
155
+ const linear = css.match(LINEAR_RE);
156
+ if (linear) {
157
+ const stops2 = parseStops(linear[2]);
158
+ return stops2 && { type: "linear", angle: parseFloat(linear[1]), stops: stops2 };
159
+ }
160
+ const radial = css.match(RADIAL_RE);
161
+ if (!radial) return null;
162
+ const shape = parseRadialShape(radial[1]);
163
+ const stops = parseStops(radial[3]);
164
+ if (!shape || !stops) return null;
165
+ const centerX = parseFloat(radial[2]);
166
+ return {
167
+ type: "radial",
168
+ angle: 0,
169
+ ...shape,
170
+ ...centerX === 50 ? {} : { centerX },
171
+ stops
172
+ };
173
+ }
62
174
 
63
175
  // src/editor/core/storage/files/versionedFileResourceClient.ts
64
176
  function sanitizeFileName(name) {
65
177
  return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^[-_]+|-+$/g, "") || "unnamed";
66
178
  }
67
179
 
68
- // src/editor/core/palettes/oklch.ts
180
+ // src/system/internal/oklch.ts
69
181
  function srgbToLinear(c) {
70
182
  return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
71
183
  }
72
- function linearToSrgb(c) {
73
- return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
74
- }
75
184
  function hexToLinearRgb(hex) {
76
185
  const r = parseInt(hex.slice(1, 3), 16) / 255;
77
186
  const g = parseInt(hex.slice(3, 5), 16) / 255;
78
187
  const b = parseInt(hex.slice(5, 7), 16) / 255;
79
188
  return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
80
189
  }
81
- function linearRgbToHex(r, g, b) {
82
- const toHex = (c) => {
83
- const v = Math.round(Math.max(0, Math.min(1, linearToSrgb(c))) * 255);
84
- return v.toString(16).padStart(2, "0");
85
- };
86
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
87
- }
88
190
  function linearRgbToOklab(r, g, b) {
89
191
  const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
90
192
  const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
@@ -126,19 +228,10 @@ function hexToOklch(hex) {
126
228
  const [L, a, bVal] = linearRgbToOklab(r, g, b);
127
229
  return oklabToOklch(L, a, bVal);
128
230
  }
129
- function oklchToHex(l, c, h) {
130
- const [L, a, b] = oklchToOklab(l, c, h);
131
- const [r, g, bVal] = oklabToLinearRgb(L, a, b);
132
- return linearRgbToHex(r, g, bVal);
133
- }
134
231
  function isInGamut(r, g, b) {
135
232
  const eps = 1e-4;
136
233
  return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;
137
234
  }
138
- function oklchToHexClamped(l, c, h) {
139
- const g = gamutClamp(l, c, h);
140
- return oklchToHex(g.l, g.c, g.h);
141
- }
142
235
  function gamutClamp(l, c, h) {
143
236
  if (l <= 0) return { l: 0, c: 0, h };
144
237
  if (l >= 1) return { l: 1, c: 0, h };
@@ -159,6 +252,31 @@ function gamutClamp(l, c, h) {
159
252
  }
160
253
  return { l, c: lo, h };
161
254
  }
255
+ function trim(value, decimals) {
256
+ return Number(value.toFixed(decimals)).toString();
257
+ }
258
+ function oklchToCss(l, c, h) {
259
+ const cs = trim(c, 4);
260
+ return `oklch(${trim(l, 4)} ${cs} ${cs === "0" ? "0" : trim(h, 2)})`;
261
+ }
262
+ function oklchToCssClamped(l, c, h) {
263
+ const g = gamutClamp(l, c, h);
264
+ return oklchToCss(g.l, g.c, g.h);
265
+ }
266
+ var OKLCH_RE = /^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)(%?)\s+([\d.]+)(?:deg)?\s*\)$/i;
267
+ var HEX6_RE = /^#[0-9a-f]{6}$/i;
268
+ function parseOklchCss(value) {
269
+ const m = OKLCH_RE.exec(value.trim());
270
+ if (!m) return null;
271
+ const l = Number.parseFloat(m[1]) / (m[2] === "%" ? 100 : 1);
272
+ const c = Number.parseFloat(m[3]) * (m[4] === "%" ? 4e-3 : 1);
273
+ return { l, c, h: Number.parseFloat(m[5]) };
274
+ }
275
+ function cssColorToOklch(value) {
276
+ const raw = value.trim();
277
+ if (HEX6_RE.test(raw)) return hexToOklch(raw);
278
+ return parseOklchCss(raw);
279
+ }
162
280
 
163
281
  // src/editor/ui/curveEngine.ts
164
282
  function makeAnchor(x, y, tangentLen = 15) {
@@ -183,6 +301,15 @@ function tangentAnchor(x, y, prev, next) {
183
301
  const outDx = next ? (next.x - x) * HANDLE_SPAN : 0;
184
302
  return { x, y, inDx, inDy: m * inDx, outDx, outDy: m * outDx };
185
303
  }
304
+ function isCornerAnchor(a) {
305
+ return a.inDx === 0 && a.inDy === 0 && a.outDx === 0 && a.outDy === 0;
306
+ }
307
+ function resmoothAutoCurve(anchors) {
308
+ if (anchors.length < 2) return anchors;
309
+ return anchors.map(
310
+ (a, i) => isCornerAnchor(a) ? a : tangentAnchor(a.x, a.y, anchors[i - 1] ?? null, anchors[i + 1] ?? null)
311
+ );
312
+ }
186
313
  function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
187
314
  const u = 1 - t, u2 = u * u, u3 = u2 * u;
188
315
  const t2 = t * t, t3 = t2 * t;
@@ -278,37 +405,39 @@ var SCALES = [
278
405
  ];
279
406
  var BW_GUARD_MIN_L = 0.17;
280
407
  var BW_GUARD_MAX_L = 0.93;
281
- var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
282
- var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
408
+ var autoCurve = (...anchors) => resmoothAutoCurve(anchors);
409
+ var DEFAULT_PALETTE_LIGHTNESS = () => autoCurve(makeAnchor(0, 95), makeAnchor(100, 8));
410
+ var DEFAULT_PALETTE_SATURATION = () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 100));
411
+ var wrapHue = (h) => (h % 360 + 360) % 360;
283
412
  function scaleCurveDefaults(scheme = "dark") {
284
413
  if (scheme === "light") {
285
414
  return {
286
415
  Surfaces: {
287
- lightness: () => [makeAnchor(0, 98, 5), makeAnchor(100, 82, 5)],
288
- saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
416
+ lightness: () => autoCurve(makeAnchor(0, 98), makeAnchor(100, 82)),
417
+ saturation: () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 100))
289
418
  },
290
419
  Borders: {
291
- lightness: () => [makeAnchor(0, 92, 5), makeAnchor(100, 45, 5)],
292
- saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
420
+ lightness: () => autoCurve(makeAnchor(0, 92), makeAnchor(100, 45)),
421
+ saturation: () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 100))
293
422
  },
294
423
  Text: {
295
- lightness: () => [makeAnchor(0, 30, 30), makeAnchor(100, 120, 30)],
296
- saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
424
+ lightness: () => autoCurve(makeAnchor(0, 30), makeAnchor(100, 120)),
425
+ saturation: () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 15))
297
426
  }
298
427
  };
299
428
  }
300
429
  return {
301
430
  Surfaces: {
302
- lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
303
- saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
431
+ lightness: () => autoCurve(makeAnchor(0, 15), makeAnchor(100, 47)),
432
+ saturation: () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 100))
304
433
  },
305
434
  Borders: {
306
- lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
307
- saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
435
+ lightness: () => autoCurve(makeAnchor(0, 25), makeAnchor(100, 80)),
436
+ saturation: () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 100))
308
437
  },
309
438
  Text: {
310
- lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
311
- saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
439
+ lightness: () => autoCurve(makeAnchor(0, 120), makeAnchor(100, 55)),
440
+ saturation: () => autoCurve(makeAnchor(0, 100), makeAnchor(100, 15))
312
441
  }
313
442
  };
314
443
  }
@@ -327,14 +456,16 @@ function scaleStepToX(step, scale) {
327
456
  return scale.steps.length > 1 ? idx / (scale.steps.length - 1) * 100 : 50;
328
457
  }
329
458
  function serializeDerivedValue(value) {
330
- return value.kind === "raw" ? value.css : oklchToHexClamped(value.l, value.c, value.h);
459
+ return value.kind === "raw" ? value.css : oklchToCssClamped(value.l, value.c, value.h);
331
460
  }
332
- function computePaletteOklch(index, base, lightnessCurve, saturationCurve, curveOffset) {
333
- const { c: baseC, h } = base;
461
+ function computePaletteOklch(index, base, lightnessCurve, saturationCurve, curveOffset, hueCurve) {
462
+ const { c: baseC } = base;
334
463
  const xPos = stepIndexToX(index);
335
464
  const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
336
465
  const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
337
466
  const targetC = baseC * satMul;
467
+ const hueDelta = (hueCurve ? sampleCurve(hueCurve, xPos) : 0) + (curveOffset.hue ?? 0);
468
+ const h = hueDelta === 0 ? base.h : wrapHue(base.h + hueDelta);
338
469
  return gamutClamp(targetL, targetC, h);
339
470
  }
340
471
  function nearestPaletteStep(lightnessCurve, baseL) {
@@ -430,10 +561,12 @@ function syncBaseAnchor(cfg) {
430
561
  const fresh = prev === void 0;
431
562
  let lCurve = cfg.lightnessCurve;
432
563
  let sCurve = cfg.saturationCurve;
564
+ let hCurve = cfg.hueCurve;
433
565
  if (prev) {
434
566
  const prevX = stepIndexToX(prev.step);
435
567
  lCurve = liftCurveAnchor(lCurve, prevX, prev.displacedL);
436
568
  sCurve = liftCurveAnchor(sCurve, prevX, prev.displacedS);
569
+ if (hCurve) hCurve = liftCurveAnchor(hCurve, prevX, prev.displacedH);
437
570
  }
438
571
  const step = nearestPaletteStep(lCurve, cfg.baseColor.l);
439
572
  const x = stepIndexToX(step);
@@ -441,12 +574,19 @@ function syncBaseAnchor(cfg) {
441
574
  const s = setCurveAnchor(sCurve, x, 100, fresh);
442
575
  cfg.lightnessCurve = l.curve;
443
576
  cfg.saturationCurve = s.curve;
577
+ const h = hCurve ? setCurveAnchor(hCurve, x, 0, fresh) : void 0;
578
+ if (h) cfg.hueCurve = h.curve;
444
579
  cfg.anchorPlacement = {
445
580
  step,
446
581
  displacedL: l.displacedY,
447
582
  displacedS: s.displacedY,
583
+ displacedH: h?.displacedY,
448
584
  priorLightnessEndpoints: prev?.priorLightnessEndpoints ?? l.priorEndpoints,
449
- priorSaturationEndpoints: prev?.priorSaturationEndpoints ?? s.priorEndpoints
585
+ priorSaturationEndpoints: prev?.priorSaturationEndpoints ?? s.priorEndpoints,
586
+ // No else-branch reading `prev?.priorHueEndpoints`: that could only fire
587
+ // if a placed hue curve later went absent, which RJC 7 (reset never
588
+ // deletes) never lets happen.
589
+ priorHueEndpoints: h ? prev?.priorHueEndpoints ?? h.priorEndpoints : void 0
450
590
  };
451
591
  }
452
592
  function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
@@ -455,8 +595,10 @@ function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
455
595
  const defs = defaultScaleCurves[scaleTitle];
456
596
  const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
457
597
  const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
598
+ const hCurve = scaleCurves[scaleTitle]?.hue;
458
599
  const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
459
600
  const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
601
+ const hOff = curveOffset[`${scaleTitle}-hue`] ?? 0;
460
602
  const { l: baseL, c: baseC, h: baseH } = base;
461
603
  let targetL;
462
604
  if (scale.isText) {
@@ -467,7 +609,9 @@ function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
467
609
  }
468
610
  const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
469
611
  const targetC = baseC * satMul;
470
- return gamutClamp(targetL, targetC, baseH);
612
+ const hueDelta = (hCurve ? sampleCurve(hCurve, xPos) : 0) + hOff;
613
+ const targetH = hueDelta === 0 ? baseH : wrapHue(baseH + hueDelta);
614
+ return gamutClamp(targetL, targetC, targetH);
471
615
  }
472
616
  function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
473
617
  if (cssNamespace === null) return null;
@@ -496,7 +640,7 @@ function derivePaletteValues(spec, config) {
496
640
  const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
497
641
  PALETTE_STEPS.forEach((ps, index) => {
498
642
  const k = paletteStepKey(ps.label);
499
- const value = k in overrides ? { kind: "color", ...overrides[k] } : { kind: "color", ...computePaletteOklch(index, baseColor, lightnessCurve, saturationCurve, curveOffset) };
643
+ const value = k in overrides ? { kind: "color", ...overrides[k] } : { kind: "color", ...computePaletteOklch(index, baseColor, lightnessCurve, saturationCurve, curveOffset, config.hueCurve) };
500
644
  out[`--color-${spec.cssNamespace}-${ps.label}`] = value;
501
645
  });
502
646
  for (const scale of SCALES) {
@@ -531,8 +675,8 @@ function derivePaletteValues(spec, config) {
531
675
  );
532
676
  const stopsCss = sortedStops.map((s) => {
533
677
  const stopValue = out[`--color-${spec.cssNamespace}-${s.paletteLabel}`];
534
- const hex = stopValue ? serializeDerivedValue(stopValue) : "#000000";
535
- return `${hex} ${s.position}%`;
678
+ const css = stopValue ? serializeDerivedValue(stopValue) : "oklch(0 0 0)";
679
+ return `${css} ${s.position}%`;
536
680
  }).join(", ");
537
681
  let gradient;
538
682
  switch (gradientStyle) {
@@ -551,13 +695,6 @@ function derivePaletteValues(spec, config) {
551
695
  }
552
696
  return out;
553
697
  }
554
- function derivePaletteVars(spec, config) {
555
- const out = {};
556
- for (const [k, v] of Object.entries(derivePaletteValues(spec, config))) {
557
- out[k] = serializeDerivedValue(v);
558
- }
559
- return out;
560
- }
561
698
  function palettesToValues(palettes) {
562
699
  const out = {};
563
700
  for (const spec of PALETTE_SPECS) {
@@ -572,7 +709,6 @@ function palettesToVars(palettes) {
572
709
  }
573
710
  return out;
574
711
  }
575
- var HEX_RE = /^#[0-9a-f]{6}$/i;
576
712
  function reconcilePalettesFromCssVars(palettes, cssVars) {
577
713
  const next = structuredClone(palettes);
578
714
  const consumed = /* @__PURE__ */ new Set();
@@ -581,16 +717,17 @@ function reconcilePalettesFromCssVars(palettes, cssVars) {
581
717
  const current = next[spec.label];
582
718
  if (current === void 0) continue;
583
719
  if (current._imported === true) {
584
- const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
585
- if (anchorHex && HEX_RE.test(anchorHex.trim())) {
586
- next[spec.label] = { ...current, baseColor: hexToOklch(anchorHex.trim()), _imported: false };
720
+ const anchor = cssVars[`--color-${spec.cssNamespace}-500`];
721
+ const anchorOklch = anchor ? cssColorToOklch(anchor) : null;
722
+ if (anchorOklch) {
723
+ next[spec.label] = { ...current, baseColor: anchorOklch, _imported: false };
587
724
  syncBaseAnchor(next[spec.label]);
588
725
  snapped.add(spec.label);
589
726
  } else {
590
727
  next[spec.label] = { ...current, _imported: false };
591
728
  }
592
729
  }
593
- for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
730
+ for (const k of Object.keys(derivePaletteValues(spec, next[spec.label]))) {
594
731
  consumed.add(k);
595
732
  }
596
733
  }
@@ -617,6 +754,754 @@ function migratePaletteColorsToOklch(editorConfigs) {
617
754
  return out;
618
755
  }
619
756
 
757
+ // src/editor/core/themes/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts
758
+ var LEGACY_KEY_RENAMES = {
759
+ "--empty": "--page-bg",
760
+ "--empty-attachment": "--page-bg-attachment",
761
+ // Orphan exports dropped from tokens.css; no new home — silently discard.
762
+ "--border-neutral": null,
763
+ // Shadow cleanup: these five tokens were removed (low/zero consumers,
764
+ // aesthetic drift). Dialog migrated onto --shadow-2xl; focus rings moved
765
+ // to their own --ring-focus-* namespace, which themes don't carry by
766
+ // default — tokens.css provides the canonical values.
767
+ "--shadow-app": null,
768
+ "--shadow-card": null,
769
+ "--shadow-overlay": null,
770
+ "--shadow-focus": null,
771
+ "--shadow-glow-green": null
772
+ };
773
+ var BG_TO_CANVAS_PREFIXES = ["--color-bg-", "--surface-bg", "--border-bg", "--text-bg"];
774
+ function migrateBgToCanvas(rawVars) {
775
+ for (const oldKey of Object.keys(rawVars)) {
776
+ for (const prefix of BG_TO_CANVAS_PREFIXES) {
777
+ if (!oldKey.startsWith(prefix)) continue;
778
+ const suffix = oldKey.slice(prefix.length);
779
+ if (suffix && !suffix.startsWith("-")) continue;
780
+ const newKey = prefix.replace("-bg", "-canvas") + suffix;
781
+ if (newKey === oldKey) continue;
782
+ const value = rawVars[oldKey];
783
+ delete rawVars[oldKey];
784
+ if (!(newKey in rawVars)) rawVars[newKey] = value;
785
+ break;
786
+ }
787
+ }
788
+ }
789
+ function migrateLegacyKeys(rawVars) {
790
+ for (const [oldKey, newKey] of Object.entries(LEGACY_KEY_RENAMES)) {
791
+ if (!(oldKey in rawVars)) continue;
792
+ const value = rawVars[oldKey];
793
+ delete rawVars[oldKey];
794
+ if (newKey && !(newKey in rawVars)) rawVars[newKey] = value;
795
+ }
796
+ migrateBgToCanvas(rawVars);
797
+ }
798
+ var colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas = {
799
+ id: "2026-04-24-legacy-keys-and-bg-to-canvas",
800
+ fromVersion: 0,
801
+ toVersion: 1,
802
+ appliesTo: "colors-and-type",
803
+ apply(rawVars) {
804
+ const out = { ...rawVars };
805
+ migrateLegacyKeys(out);
806
+ return out;
807
+ }
808
+ };
809
+
810
+ // src/editor/core/themes/migrations/2026-04-24-component-prefix-and-suffix-renames.ts
811
+ var COMPONENT_PREFIX_RENAMES = {
812
+ "--segment-": "--segmentedcontrol-",
813
+ "--collapsible-": "--collapsiblesection-",
814
+ "--progress-": "--progressbar-",
815
+ "--section-divider-": "--sectiondivider-",
816
+ "--radio-": "--radiobutton-",
817
+ "--inline-edit-": "--inlineeditactions-"
818
+ };
819
+ var COMPONENT_SUFFIX_RENAMES = {
820
+ progressbar: [["-track-bg", "-track-surface"]],
821
+ inlineeditactions: [
822
+ ["-bg-hover", "-hover-surface"],
823
+ ["-bg", "-surface"]
824
+ ]
825
+ };
826
+ var componentMigration_2026_04_24_prefixAndSuffixRenames = {
827
+ id: "2026-04-24-component-prefix-and-suffix-renames",
828
+ fromVersion: 0,
829
+ toVersion: 1,
830
+ appliesTo: "component-config",
831
+ apply(rawVars, meta) {
832
+ const component = meta.component ?? "";
833
+ const out = {};
834
+ const suffixRules = COMPONENT_SUFFIX_RENAMES[component] ?? [];
835
+ for (const [oldKey, value] of Object.entries(rawVars)) {
836
+ let key = oldKey;
837
+ if (key === "--detail-nav-bg") key = "--detailnav-surface";
838
+ for (const [oldPrefix, newPrefix] of Object.entries(COMPONENT_PREFIX_RENAMES)) {
839
+ if (key.startsWith(oldPrefix)) {
840
+ key = newPrefix + key.slice(oldPrefix.length);
841
+ break;
842
+ }
843
+ }
844
+ for (const [oldSuffix, newSuffix] of suffixRules) {
845
+ if (key.endsWith(oldSuffix)) {
846
+ key = key.slice(0, -oldSuffix.length) + newSuffix;
847
+ break;
848
+ }
849
+ }
850
+ if (!(key in out)) out[key] = value;
851
+ }
852
+ return out;
853
+ }
854
+ };
855
+
856
+ // src/editor/core/themes/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts
857
+ var OPTION_DISABLED_PREFIX = "--segmentedcontrol-option-disabled-";
858
+ var DISABLED_PREFIX = "--segmentedcontrol-disabled-";
859
+ var SELECTED_DISABLED_PREFIX = "--segmentedcontrol-selected-disabled-";
860
+ var SELECTED_HOVER_PREFIX = "--segmentedcontrol-selected-hover-";
861
+ var componentMigration_2026_04_27_segmentedcontrolDisabledFlatten = {
862
+ id: "2026-04-27-segmentedcontrol-disabled-flatten",
863
+ fromVersion: 1,
864
+ toVersion: 2,
865
+ appliesTo: "component-config",
866
+ apply(rawVars, meta) {
867
+ if (meta.component !== "segmentedcontrol") return { ...rawVars };
868
+ const out = {};
869
+ for (const [oldKey, value] of Object.entries(rawVars)) {
870
+ let key = oldKey;
871
+ if (key.startsWith(SELECTED_DISABLED_PREFIX)) continue;
872
+ if (key.startsWith(SELECTED_HOVER_PREFIX)) continue;
873
+ if (key.startsWith(OPTION_DISABLED_PREFIX)) {
874
+ key = DISABLED_PREFIX + key.slice(OPTION_DISABLED_PREFIX.length);
875
+ }
876
+ if (!(key in out)) out[key] = value;
877
+ }
878
+ return out;
879
+ }
880
+ };
881
+
882
+ // src/editor/core/themes/migrations/2026-05-08-collapsiblesection-variant-namespace.ts
883
+ var PREFIX = "--collapsiblesection-";
884
+ var STATE_NAMES = /* @__PURE__ */ new Set(["default", "hover", "active", "expanded"]);
885
+ var componentMigration_2026_05_08_collapsiblesectionVariantNamespace = {
886
+ id: "2026-05-08-collapsiblesection-variant-namespace",
887
+ fromVersion: 2,
888
+ toVersion: 3,
889
+ appliesTo: "component-config",
890
+ apply(rawVars, meta) {
891
+ if (meta.component !== "collapsiblesection") return { ...rawVars };
892
+ const out = {};
893
+ for (const [oldKey, value] of Object.entries(rawVars)) {
894
+ let key = oldKey;
895
+ if (key.startsWith(PREFIX)) {
896
+ const tail = key.slice(PREFIX.length);
897
+ const firstSegment = tail.split("-", 1)[0];
898
+ if (STATE_NAMES.has(firstSegment)) {
899
+ key = `${PREFIX}container-${tail}`;
900
+ }
901
+ }
902
+ if (!(key in out)) out[key] = value;
903
+ }
904
+ return out;
905
+ }
906
+ };
907
+
908
+ // src/editor/core/themes/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts
909
+ var PFX = "--collapsiblesection-";
910
+ var componentMigration_2026_05_08_collapsiblesectionFrameAndCleanup = {
911
+ id: "2026-05-08-collapsiblesection-frame-and-cleanup",
912
+ fromVersion: 3,
913
+ toVersion: 4,
914
+ appliesTo: "component-config",
915
+ apply(rawVars, meta) {
916
+ if (meta.component !== "collapsiblesection") return { ...rawVars };
917
+ const out = {};
918
+ for (const prop of ["surface", "border", "border-width", "radius"]) {
919
+ const src = `${PFX}container-default-${prop}`;
920
+ if (src in rawVars) {
921
+ out[`${PFX}container-frame-${prop}`] = rawVars[src];
922
+ }
923
+ }
924
+ for (const [key, value] of Object.entries(rawVars)) {
925
+ if (/^--collapsiblesection-chromeless-(default|hover|active)-(border|border-width|radius)$/.test(key)) continue;
926
+ if (/^--collapsiblesection-container-(hover|active)-(border|border-width|radius)$/.test(key)) continue;
927
+ if (/^--collapsiblesection-container-default-(border|border-width|radius)$/.test(key)) continue;
928
+ if (/^--collapsiblesection-divider-(default|hover|active)-radius$/.test(key)) continue;
929
+ if (/^--collapsiblesection-(chromeless|divider|container)-expanded-(border|border-width|radius)$/.test(key)) continue;
930
+ if (/^--collapsiblesection-(chromeless|divider)-expanded-surface$/.test(key)) continue;
931
+ if (!(key in out)) out[key] = value;
932
+ }
933
+ return out;
934
+ }
935
+ };
936
+
937
+ // src/editor/core/themes/migrations/2026-05-10-sectiondivider-gradient-stops.ts
938
+ var PFX2 = "--sectiondivider-";
939
+ var VARIANTS = ["canvas", "neutral", "alternate", "primary", "accent", "special"];
940
+ var OLD_STOP_RE = /^--sectiondivider-(canvas|neutral|alternate|primary|accent|special)-gradient-stop-\d+$/;
941
+ var componentMigration_2026_05_10_sectiondividerGradientStops = {
942
+ id: "2026-05-10-sectiondivider-gradient-stops",
943
+ fromVersion: 4,
944
+ toVersion: 5,
945
+ appliesTo: "component-config",
946
+ apply(rawVars, meta) {
947
+ if (meta.component !== "sectiondivider") return { ...rawVars };
948
+ const out = {};
949
+ for (const [key, value] of Object.entries(rawVars)) {
950
+ if (OLD_STOP_RE.test(key)) continue;
951
+ out[key] = value;
952
+ }
953
+ const setIfMissing = (key, fallback) => {
954
+ if (!(key in out)) out[key] = fallback;
955
+ };
956
+ for (const v of VARIANTS) {
957
+ const old1 = rawVars[`${PFX2}${v}-gradient-stop-1`];
958
+ const old2 = rawVars[`${PFX2}${v}-gradient-stop-2`];
959
+ const old4 = rawVars[`${PFX2}${v}-gradient-stop-4`];
960
+ setIfMissing(`${PFX2}${v}-gradient-angle`, "--gradient-angle-diagonal");
961
+ setIfMissing(`${PFX2}${v}-gradient-stop-1-color`, old1 ?? `--surface-${v}-highest`);
962
+ setIfMissing(`${PFX2}${v}-gradient-stop-1-position`, "--gradient-stop-start");
963
+ setIfMissing(`${PFX2}${v}-gradient-stop-2-color`, old2 ?? `--surface-${v}-higher`);
964
+ setIfMissing(`${PFX2}${v}-gradient-stop-2-position`, "--gradient-stop-mid");
965
+ setIfMissing(`${PFX2}${v}-gradient-stop-3-color`, old4 ?? `--surface-${v}`);
966
+ setIfMissing(`${PFX2}${v}-gradient-stop-3-position`, "--gradient-stop-end");
967
+ }
968
+ return out;
969
+ }
970
+ };
971
+
972
+ // src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts
973
+ var PALETTE_STEPS2 = ["100", "200", "300", "400", "500", "600", "700", "800", "850", "900", "950"];
974
+ var SURFACE_SUFFIXES = ["lowest", "lower", "low", "high", "higher", "highest"];
975
+ var BORDER_SUFFIXES = ["faint", "subtle", "medium", "strong"];
976
+ var TEXT_SUFFIXES = ["secondary", "tertiary", "muted", "disabled"];
977
+ var RENAME_MAP = (() => {
978
+ const m = {};
979
+ for (const step of PALETTE_STEPS2) m[`--color-primary-${step}`] = `--color-brand-${step}`;
980
+ m["--surface-primary"] = "--surface-brand";
981
+ for (const suf of SURFACE_SUFFIXES) m[`--surface-primary-${suf}`] = `--surface-brand-${suf}`;
982
+ m["--border-primary"] = "--border-brand";
983
+ for (const suf of BORDER_SUFFIXES) m[`--border-primary-${suf}`] = `--border-brand-${suf}`;
984
+ m["--text-primary-color"] = "--text-brand";
985
+ for (const suf of TEXT_SUFFIXES) m[`--text-primary-${suf}`] = `--text-brand-${suf}`;
986
+ return m;
987
+ })();
988
+ function renameToken(name) {
989
+ return RENAME_MAP[name] ?? name;
990
+ }
991
+ function renameKeys(rawVars) {
992
+ const out = {};
993
+ for (const [k, v] of Object.entries(rawVars)) {
994
+ out[renameToken(k)] = v;
995
+ }
996
+ return out;
997
+ }
998
+ function renameKeysAndValues(rawVars) {
999
+ const out = {};
1000
+ for (const [k, v] of Object.entries(rawVars)) {
1001
+ out[renameToken(k)] = renameToken(v);
1002
+ }
1003
+ return out;
1004
+ }
1005
+ var colorsAndTypeMigration_2026_05_13_primaryToBrand = {
1006
+ id: "2026-05-13-primary-to-brand-theme",
1007
+ fromVersion: 1,
1008
+ toVersion: 2,
1009
+ appliesTo: "colors-and-type",
1010
+ apply: renameKeys
1011
+ };
1012
+ var componentMigration_2026_05_13_primaryToBrand = {
1013
+ id: "2026-05-13-primary-to-brand-component",
1014
+ fromVersion: 5,
1015
+ toVersion: 6,
1016
+ appliesTo: "component-config",
1017
+ apply: renameKeysAndValues
1018
+ };
1019
+
1020
+ // src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts
1021
+ function dropFrameSurface(rawVars, meta) {
1022
+ if (meta.component !== "collapsiblesection") return rawVars;
1023
+ const { "--collapsiblesection-container-frame-surface": _drop, ...rest } = rawVars;
1024
+ return rest;
1025
+ }
1026
+ var componentMigration_2026_05_19_collapsiblesectionDropFrameSurface = {
1027
+ id: "2026-05-19-collapsiblesection-drop-frame-surface",
1028
+ fromVersion: 6,
1029
+ toVersion: 7,
1030
+ appliesTo: "component-config",
1031
+ apply: dropFrameSurface
1032
+ };
1033
+
1034
+ // src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts
1035
+ var FLAT_RE = /^--sectiondivider-(canvas|neutral|alternate|primary|accent|special)-gradient-(angle|stop-[123]-(color|position))$/;
1036
+ var componentMigration_2026_05_19_sectiondividerRichGradient = {
1037
+ id: "2026-05-19-sectiondivider-rich-gradient",
1038
+ fromVersion: 7,
1039
+ toVersion: 8,
1040
+ appliesTo: "component-config",
1041
+ apply(rawVars, meta) {
1042
+ if (meta.component !== "sectiondivider") return { ...rawVars };
1043
+ const out = {};
1044
+ for (const [key, value] of Object.entries(rawVars)) {
1045
+ if (FLAT_RE.test(key)) continue;
1046
+ out[key] = value;
1047
+ }
1048
+ return out;
1049
+ }
1050
+ };
1051
+
1052
+ // src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts
1053
+ var VARIANTS2 = ["lg", "md", "sm"];
1054
+ var SUFFIX_RENAMES = [
1055
+ [/-title-border-width$/, "-title-outline-width"],
1056
+ [/-title-stroke-color$/, "-title-outline-color"],
1057
+ [
1058
+ /^--sectiondivider-(canvas|neutral|alternate|primary|accent|special)-padding$/,
1059
+ "--sectiondivider-$1-spacing"
1060
+ ]
1061
+ ];
1062
+ function renameKeySuffix(key) {
1063
+ for (const [re, repl] of SUFFIX_RENAMES) {
1064
+ if (re.test(key)) return key.replace(re, repl);
1065
+ }
1066
+ return key;
1067
+ }
1068
+ function splitFamilyKey(key) {
1069
+ const m = key.match(/^--sectiondivider-(canvas|neutral|alternate|primary|accent|special)-(.+)$/);
1070
+ if (!m) return null;
1071
+ return { family: m[1], suffix: m[2] };
1072
+ }
1073
+ var componentMigration_2026_05_20_sectiondividerSlimVariants = {
1074
+ id: "2026-05-20-sectiondivider-slim-variants",
1075
+ fromVersion: 8,
1076
+ toVersion: 9,
1077
+ appliesTo: "component-config",
1078
+ apply(rawVars, meta) {
1079
+ if (meta.component !== "sectiondivider") return { ...rawVars };
1080
+ const out = {};
1081
+ const canvasValues = {};
1082
+ for (const [rawKey, value] of Object.entries(rawVars)) {
1083
+ const key = renameKeySuffix(rawKey);
1084
+ const parts = splitFamilyKey(key);
1085
+ if (!parts) {
1086
+ out[key] = value;
1087
+ continue;
1088
+ }
1089
+ if (parts.family !== "canvas") continue;
1090
+ canvasValues[parts.suffix] = value;
1091
+ }
1092
+ for (const suffix of Object.keys(canvasValues)) {
1093
+ for (const v of VARIANTS2) {
1094
+ out[`--sectiondivider-${v}-${suffix}`] = canvasValues[suffix];
1095
+ }
1096
+ }
1097
+ return out;
1098
+ }
1099
+ };
1100
+
1101
+ // src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts
1102
+ var componentMigration_2026_05_21_sectiondividerSpacingToPadding = {
1103
+ id: "2026-05-21-sectiondivider-spacing-to-padding",
1104
+ fromVersion: 9,
1105
+ toVersion: 10,
1106
+ appliesTo: "component-config",
1107
+ apply(rawVars, meta) {
1108
+ if (meta.component !== "sectiondivider") return { ...rawVars };
1109
+ const out = {};
1110
+ for (const [key, value] of Object.entries(rawVars)) {
1111
+ const renamed = key.replace(
1112
+ /^--sectiondivider-(lg|md|sm)-spacing$/,
1113
+ "--sectiondivider-$1-padding"
1114
+ );
1115
+ out[renamed] = value;
1116
+ }
1117
+ return out;
1118
+ }
1119
+ };
1120
+
1121
+ // src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts
1122
+ var componentMigration_2026_05_22_sectiondividerIntrinsicsToCss = {
1123
+ id: "2026-05-22-sectiondivider-intrinsics-to-css",
1124
+ fromVersion: 10,
1125
+ toVersion: 11,
1126
+ appliesTo: "component-config",
1127
+ apply(rawVars, meta) {
1128
+ if (meta.component !== "sectiondivider") return { ...rawVars };
1129
+ const VARIANTS4 = ["lg", "md", "sm"];
1130
+ const prefix = "--sectiondivider-";
1131
+ const dropped = /* @__PURE__ */ new Set();
1132
+ for (const v of VARIANTS4) {
1133
+ dropped.add(`${prefix}${v}-show-eyebrow`);
1134
+ dropped.add(`${prefix}${v}-show-description`);
1135
+ dropped.add(`${prefix}${v}-show-hairline`);
1136
+ dropped.add(`${prefix}${v}-hairline`);
1137
+ dropped.add(`${prefix}${v}-eyebrow-uppercase`);
1138
+ }
1139
+ const out = {};
1140
+ for (const [key, value] of Object.entries(rawVars)) {
1141
+ if (dropped.has(key)) continue;
1142
+ out[key] = value;
1143
+ }
1144
+ for (const v of VARIANTS4) {
1145
+ const showEyebrow = rawVars[`${prefix}${v}-show-eyebrow`];
1146
+ if (showEyebrow !== void 0) {
1147
+ out[`${prefix}${v}-eyebrow-display`] = showEyebrow === "1" ? "block" : "none";
1148
+ }
1149
+ const showDesc = rawVars[`${prefix}${v}-show-description`];
1150
+ if (showDesc !== void 0) {
1151
+ out[`${prefix}${v}-description-display`] = showDesc === "1" ? "flex" : "none";
1152
+ }
1153
+ const showHair = rawVars[`${prefix}${v}-show-hairline`];
1154
+ const oldHair = rawVars[`${prefix}${v}-hairline`];
1155
+ const oldHairIsPos = typeof oldHair === "string" && oldHair !== "" && oldHair !== "off";
1156
+ let newHair;
1157
+ if (showHair === "") newHair = "none";
1158
+ else if (showHair === "1") newHair = oldHairIsPos ? oldHair : "above-label";
1159
+ else if (oldHair !== void 0) newHair = oldHairIsPos ? oldHair : "none";
1160
+ if (newHair !== void 0) {
1161
+ out[`${prefix}${v}-hairline`] = newHair;
1162
+ }
1163
+ const upper = rawVars[`${prefix}${v}-eyebrow-uppercase`];
1164
+ if (upper !== void 0) {
1165
+ out[`${prefix}${v}-eyebrow-text-transform`] = upper === "1" ? "uppercase" : "none";
1166
+ }
1167
+ }
1168
+ return out;
1169
+ }
1170
+ };
1171
+
1172
+ // src/editor/core/themes/migrations/2026-05-24-segmentedcontrol-divider-inset.ts
1173
+ var componentMigration_2026_05_24_segmentedcontrolDividerInset = {
1174
+ id: "2026-05-24-segmentedcontrol-divider-inset",
1175
+ fromVersion: 11,
1176
+ toVersion: 12,
1177
+ appliesTo: "component-config",
1178
+ apply(rawVars, meta) {
1179
+ if (meta.component !== "segmentedcontrol") return { ...rawVars };
1180
+ const out = {};
1181
+ for (const [key, value] of Object.entries(rawVars)) {
1182
+ if (key === "--segmentedcontrol-divider-height") continue;
1183
+ out[key] = value;
1184
+ }
1185
+ return out;
1186
+ }
1187
+ };
1188
+
1189
+ // src/editor/core/themes/migrations/2026-05-24-promote-state-shared-tokens.ts
1190
+ var BUTTON_VARIANTS = ["primary", "secondary", "outline", "success", "danger", "warning"];
1191
+ var BUTTON_STATES = ["hover", "disabled"];
1192
+ var BUTTON_PROPS = ["padding", "radius", "border-width"];
1193
+ var buttonDeadKeys = new Set(
1194
+ BUTTON_VARIANTS.flatMap(
1195
+ (v) => BUTTON_STATES.flatMap(
1196
+ (s) => BUTTON_PROPS.map((p) => `--button-${v}-${s}-${p}`)
1197
+ )
1198
+ )
1199
+ );
1200
+ var segmentedcontrolDeadKeys = /* @__PURE__ */ new Set([
1201
+ "--segmentedcontrol-selected-icon-size",
1202
+ "--segmentedcontrol-option-hover-icon-size",
1203
+ "--segmentedcontrol-disabled-icon-size"
1204
+ ]);
1205
+ var componentMigration_2026_05_24_promoteStateSharedTokens = {
1206
+ id: "2026-05-24-promote-state-shared-tokens",
1207
+ fromVersion: 12,
1208
+ toVersion: 13,
1209
+ appliesTo: "component-config",
1210
+ apply(rawVars, meta) {
1211
+ const dead = meta.component === "button" ? buttonDeadKeys : meta.component === "segmentedcontrol" ? segmentedcontrolDeadKeys : null;
1212
+ if (!dead) return { ...rawVars };
1213
+ const out = {};
1214
+ for (const [key, value] of Object.entries(rawVars)) {
1215
+ if (dead.has(key)) continue;
1216
+ out[key] = value;
1217
+ }
1218
+ return out;
1219
+ }
1220
+ };
1221
+
1222
+ // src/editor/core/themes/migrations/2026-05-24-progressbar-collapse-variants.ts
1223
+ var PRIMARY_PREFIX = "--progressbar-primary-";
1224
+ var NEW_PREFIX = "--progressbar-";
1225
+ var DROP_PREFIXES = [
1226
+ "--progressbar-success-",
1227
+ "--progressbar-warning-",
1228
+ "--progressbar-danger-",
1229
+ "--progressbar-info-"
1230
+ ];
1231
+ var componentMigration_2026_05_24_progressbarCollapseVariants = {
1232
+ id: "2026-05-24-progressbar-collapse-variants",
1233
+ fromVersion: 13,
1234
+ toVersion: 14,
1235
+ appliesTo: "component-config",
1236
+ apply(rawVars, meta) {
1237
+ if (meta.component !== "progressbar") return { ...rawVars };
1238
+ const out = {};
1239
+ for (const [oldKey, value] of Object.entries(rawVars)) {
1240
+ if (DROP_PREFIXES.some((p) => oldKey.startsWith(p))) continue;
1241
+ const key = oldKey.startsWith(PRIMARY_PREFIX) ? NEW_PREFIX + oldKey.slice(PRIMARY_PREFIX.length) : oldKey;
1242
+ if (!(key in out)) out[key] = value;
1243
+ }
1244
+ return out;
1245
+ }
1246
+ };
1247
+
1248
+ // src/editor/core/themes/migrations/2026-05-24-collapsiblesection-drop-active-state.ts
1249
+ function dropActiveState(rawVars, meta) {
1250
+ if (meta.component !== "collapsiblesection") return rawVars;
1251
+ const re = /^--collapsiblesection-(chromeless|divider|container)-active-/;
1252
+ const out = {};
1253
+ for (const [key, value] of Object.entries(rawVars)) {
1254
+ if (re.test(key)) continue;
1255
+ out[key] = value;
1256
+ }
1257
+ return out;
1258
+ }
1259
+ var componentMigration_2026_05_24_collapsiblesectionDropActiveState = {
1260
+ id: "2026-05-24-collapsiblesection-drop-active-state",
1261
+ fromVersion: 14,
1262
+ toVersion: 15,
1263
+ appliesTo: "component-config",
1264
+ apply: dropActiveState
1265
+ };
1266
+
1267
+ // src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts
1268
+ var VARIANTS3 = ["primary", "accent", "neutral", "alternate", "canvas", "special", "success", "warning", "danger", "info"];
1269
+ var RE = new RegExp(`^--corner-badge-(${VARIANTS3.join("|")})-(.+)$`);
1270
+ var CURRENT_COLOR_PROPS = /* @__PURE__ */ new Set(["surface", "border", "text"]);
1271
+ function flattenVariants(rawVars, meta) {
1272
+ if (meta.component !== "cornerbadge") return rawVars;
1273
+ const out = {};
1274
+ const collected = /* @__PURE__ */ new Map();
1275
+ for (const [key, value] of Object.entries(rawVars)) {
1276
+ const m = key.match(RE);
1277
+ if (m) {
1278
+ const prop = m[2];
1279
+ if (CURRENT_COLOR_PROPS.has(prop)) {
1280
+ out[key] = value;
1281
+ continue;
1282
+ }
1283
+ if (!collected.has(prop)) collected.set(prop, value);
1284
+ continue;
1285
+ }
1286
+ out[key] = value;
1287
+ }
1288
+ for (const [prop, value] of collected) {
1289
+ const flatKey = `--corner-badge-${prop}`;
1290
+ if (!(flatKey in out)) out[flatKey] = value;
1291
+ }
1292
+ return out;
1293
+ }
1294
+ var componentMigration_2026_05_25_cornerbadgeFlattenVariants = {
1295
+ id: "2026-05-25-cornerbadge-flatten-variants",
1296
+ fromVersion: 15,
1297
+ toVersion: 16,
1298
+ appliesTo: "component-config",
1299
+ apply: flattenVariants
1300
+ };
1301
+
1302
+ // src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts
1303
+ var DROPPED_TO_KEPT = {
1304
+ "--overlay-lowest": "--overlay-low",
1305
+ "--overlay-lower": "--overlay-low",
1306
+ "--overlay-higher": "--overlay-high",
1307
+ "--overlay-highest": "--overlay-high"
1308
+ };
1309
+ var colorsAndTypeMigration_2026_05_26_dropOverlayExtraStops = {
1310
+ id: "2026-05-26-drop-overlay-extra-stops-theme",
1311
+ fromVersion: 2,
1312
+ toVersion: 3,
1313
+ appliesTo: "colors-and-type",
1314
+ apply(rawVars) {
1315
+ const out = {};
1316
+ for (const [key, value] of Object.entries(rawVars)) {
1317
+ if (key in DROPPED_TO_KEPT) continue;
1318
+ out[key] = value;
1319
+ }
1320
+ return out;
1321
+ }
1322
+ };
1323
+ var componentMigration_2026_05_26_dropOverlayExtraStops = {
1324
+ id: "2026-05-26-drop-overlay-extra-stops-component",
1325
+ fromVersion: 16,
1326
+ toVersion: 17,
1327
+ appliesTo: "component-config",
1328
+ apply(rawVars) {
1329
+ const out = {};
1330
+ for (const [key, value] of Object.entries(rawVars)) {
1331
+ out[key] = DROPPED_TO_KEPT[value] ?? value;
1332
+ }
1333
+ return out;
1334
+ }
1335
+ };
1336
+
1337
+ // src/editor/core/themes/migrations/2026-05-29-tabbar-indicator-thickness-to-per-state-width.ts
1338
+ var TABBAR_STATES = ["default", "hover", "active", "disabled"];
1339
+ var OLD_KEY = "--tabbar-bar-indicator-thickness";
1340
+ var FALLBACK = "--border-width-2";
1341
+ var componentMigration_2026_05_29_tabbarIndicatorThicknessToPerStateWidth = {
1342
+ id: "2026-05-29-tabbar-indicator-thickness-to-per-state-width",
1343
+ fromVersion: 17,
1344
+ toVersion: 18,
1345
+ appliesTo: "component-config",
1346
+ apply(rawVars, meta) {
1347
+ if (meta.component !== "tabbar") return { ...rawVars };
1348
+ const seed = rawVars[OLD_KEY] ?? FALLBACK;
1349
+ const out = {};
1350
+ for (const [key, value] of Object.entries(rawVars)) {
1351
+ if (key === OLD_KEY) continue;
1352
+ out[key] = value;
1353
+ }
1354
+ for (const state of TABBAR_STATES) {
1355
+ const newKey = `--tabbar-${state}-indicator-border-width`;
1356
+ if (!(newKey in out)) out[newKey] = seed;
1357
+ }
1358
+ return out;
1359
+ }
1360
+ };
1361
+
1362
+ // src/editor/core/themes/migrations/2026-05-29-segmentedcontrol-small-divider-rename.ts
1363
+ var RENAMES = {
1364
+ "--segmentedcontrol-divider-small-thickness": "--segmentedcontrol-small-divider-thickness",
1365
+ "--segmentedcontrol-divider-small-inset": "--segmentedcontrol-small-divider-inset"
1366
+ };
1367
+ var componentMigration_2026_05_29_segmentedcontrolSmallDividerRename = {
1368
+ id: "2026-05-29-segmentedcontrol-small-divider-rename",
1369
+ fromVersion: 18,
1370
+ toVersion: 19,
1371
+ appliesTo: "component-config",
1372
+ apply(rawVars, meta) {
1373
+ if (meta.component !== "segmentedcontrol") return { ...rawVars };
1374
+ const out = {};
1375
+ for (const [key, value] of Object.entries(rawVars)) {
1376
+ out[RENAMES[key] ?? key] = value;
1377
+ }
1378
+ return out;
1379
+ }
1380
+ };
1381
+
1382
+ // src/editor/core/themes/migrations/2026-05-29-toggle-derive-track-from-thumb.ts
1383
+ var componentMigration_2026_05_29_toggleDeriveTrackFromThumb = {
1384
+ id: "2026-05-29-toggle-derive-track-from-thumb",
1385
+ fromVersion: 19,
1386
+ toVersion: 20,
1387
+ appliesTo: "component-config",
1388
+ apply(rawVars, meta) {
1389
+ if (meta.component !== "toggle") return { ...rawVars };
1390
+ const out = {};
1391
+ for (const [key, value] of Object.entries(rawVars)) {
1392
+ if (key === "--toggle-track-width" || key === "--toggle-track-thickness") continue;
1393
+ out[key] = value;
1394
+ }
1395
+ if (!("--toggle-track-padding" in out)) {
1396
+ out["--toggle-track-padding"] = "--space-2";
1397
+ }
1398
+ return out;
1399
+ }
1400
+ };
1401
+
1402
+ // src/editor/core/themes/migrations/2026-08-13-drop-legacy-shape-space-keys.ts
1403
+ var DROPPED = /* @__PURE__ */ new Set([
1404
+ "--badge-trait-radius",
1405
+ "--badge-trait-padding",
1406
+ "--sectiondivider-padding",
1407
+ "--dialog-primary-default-radius",
1408
+ "--dialog-primary-default-padding",
1409
+ "--dialog-primary-hover-radius",
1410
+ "--dialog-primary-hover-padding",
1411
+ "--dialog-secondary-default-radius",
1412
+ "--dialog-secondary-default-padding",
1413
+ "--dialog-secondary-hover-radius",
1414
+ "--dialog-secondary-hover-padding"
1415
+ ]);
1416
+ var colorsAndTypeMigration_2026_08_13_dropLegacyShapeSpaceKeys = {
1417
+ id: "2026-08-13-drop-legacy-shape-space-keys",
1418
+ fromVersion: 3,
1419
+ toVersion: 4,
1420
+ appliesTo: "colors-and-type",
1421
+ apply(rawVars) {
1422
+ const out = {};
1423
+ for (const [key, value] of Object.entries(rawVars)) {
1424
+ if (DROPPED.has(key)) continue;
1425
+ out[key] = value;
1426
+ }
1427
+ return out;
1428
+ }
1429
+ };
1430
+
1431
+ // src/editor/core/themes/migrations/2026-08-15-line-height-scale-rename.ts
1432
+ var RENAMED = {
1433
+ "--line-height-xs": "--line-height-none",
1434
+ "--line-height-sm": "--line-height-tighter",
1435
+ "--line-height-md": "--line-height-normal",
1436
+ "--line-height-lg": "--line-height-relaxed",
1437
+ "--line-height-xl": "--line-height-relaxed"
1438
+ };
1439
+ var REFERENCE = /--line-height-(?:xs|sm|md|lg|xl)(?![\w-])/g;
1440
+ function renameValues(rawVars) {
1441
+ const out = {};
1442
+ for (const [key, value] of Object.entries(rawVars)) {
1443
+ out[key] = typeof value === "string" ? value.replace(REFERENCE, (name) => RENAMED[name]) : value;
1444
+ }
1445
+ return out;
1446
+ }
1447
+ var colorsAndTypeMigration_2026_08_15_lineHeightScaleRename = {
1448
+ id: "2026-08-15-line-height-scale-rename-colors-and-type",
1449
+ fromVersion: 4,
1450
+ toVersion: 5,
1451
+ appliesTo: "colors-and-type",
1452
+ apply: renameValues
1453
+ };
1454
+ var componentMigration_2026_08_15_lineHeightScaleRename = {
1455
+ id: "2026-08-15-line-height-scale-rename-component",
1456
+ fromVersion: 20,
1457
+ toVersion: 21,
1458
+ appliesTo: "component-config",
1459
+ apply: renameValues
1460
+ };
1461
+
1462
+ // src/editor/core/themes/migrations/index.ts
1463
+ var MIGRATIONS = [
1464
+ colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
1465
+ colorsAndTypeMigration_2026_05_13_primaryToBrand,
1466
+ componentMigration_2026_04_24_prefixAndSuffixRenames,
1467
+ componentMigration_2026_04_27_segmentedcontrolDisabledFlatten,
1468
+ componentMigration_2026_05_08_collapsiblesectionVariantNamespace,
1469
+ componentMigration_2026_05_08_collapsiblesectionFrameAndCleanup,
1470
+ componentMigration_2026_05_10_sectiondividerGradientStops,
1471
+ componentMigration_2026_05_13_primaryToBrand,
1472
+ componentMigration_2026_05_19_collapsiblesectionDropFrameSurface,
1473
+ componentMigration_2026_05_19_sectiondividerRichGradient,
1474
+ componentMigration_2026_05_20_sectiondividerSlimVariants,
1475
+ componentMigration_2026_05_21_sectiondividerSpacingToPadding,
1476
+ componentMigration_2026_05_22_sectiondividerIntrinsicsToCss,
1477
+ componentMigration_2026_05_24_segmentedcontrolDividerInset,
1478
+ componentMigration_2026_05_24_promoteStateSharedTokens,
1479
+ componentMigration_2026_05_24_progressbarCollapseVariants,
1480
+ componentMigration_2026_05_24_collapsiblesectionDropActiveState,
1481
+ componentMigration_2026_05_25_cornerbadgeFlattenVariants,
1482
+ colorsAndTypeMigration_2026_05_26_dropOverlayExtraStops,
1483
+ componentMigration_2026_05_26_dropOverlayExtraStops,
1484
+ componentMigration_2026_05_29_tabbarIndicatorThicknessToPerStateWidth,
1485
+ componentMigration_2026_05_29_segmentedcontrolSmallDividerRename,
1486
+ componentMigration_2026_05_29_toggleDeriveTrackFromThumb,
1487
+ colorsAndTypeMigration_2026_08_13_dropLegacyShapeSpaceKeys,
1488
+ colorsAndTypeMigration_2026_08_15_lineHeightScaleRename,
1489
+ componentMigration_2026_08_15_lineHeightScaleRename
1490
+ ];
1491
+ function countFor(kind) {
1492
+ return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
1493
+ }
1494
+ var CURRENT_COLORS_AND_TYPE_SCHEMA_VERSION = countFor("colors-and-type");
1495
+ var CURRENT_COMPONENT_SCHEMA_VERSION = countFor("component-config");
1496
+ function runMigrations(kind, fileVersion, rawVars, meta = {}) {
1497
+ const applicable = MIGRATIONS.filter((m) => m.appliesTo === kind && m.fromVersion >= fileVersion).sort((a, b) => a.toVersion - b.toVersion);
1498
+ let out = { ...rawVars };
1499
+ for (const m of applicable) {
1500
+ out = m.apply(out, meta);
1501
+ }
1502
+ return out;
1503
+ }
1504
+
620
1505
  // vite-plugin/files/versionedFileResourceServer.ts
621
1506
  var import_fs = __toESM(require("fs"), 1);
622
1507
  var import_path = __toESM(require("path"), 1);
@@ -776,8 +1661,141 @@ async function dispatch(req, res, routes) {
776
1661
  return false;
777
1662
  }
778
1663
 
1664
+ // src/editor/core/themes/migrateComponentConfig.ts
1665
+ function migrateComponentConfig(component, aliasesIn, rawConfig, fileVersion) {
1666
+ const stringPart = {};
1667
+ const objectPart = {};
1668
+ for (const [k, v] of Object.entries(aliasesIn)) {
1669
+ if (typeof v === "string") stringPart[k] = v;
1670
+ else objectPart[k] = v;
1671
+ }
1672
+ const configOut = {};
1673
+ for (const [k, v] of Object.entries(rawConfig ?? {})) {
1674
+ if (typeof v === "string") stringPart[k] = v;
1675
+ else configOut[k] = v;
1676
+ }
1677
+ if (component === "sectiondivider") {
1678
+ synthesizeSectionDividerGradients(stringPart, objectPart);
1679
+ }
1680
+ const migratedString = runMigrations("component-config", fileVersion, stringPart, { component });
1681
+ const migratedObject = component === "sectiondivider" ? renameSectionDividerObjectSlots(objectPart) : objectPart;
1682
+ return {
1683
+ aliases: { ...migratedString, ...migratedObject },
1684
+ config: configOut
1685
+ };
1686
+ }
1687
+ var FAMILIES = ["canvas", "neutral", "alternate", "primary", "accent", "special"];
1688
+ var SIZE_VARIANTS = ["lg", "md", "sm"];
1689
+ function renameSectionDividerObjectSlots(objectPart) {
1690
+ const canvasSourceKeys = [
1691
+ "--sectiondivider-canvas-background",
1692
+ "--sectiondivider-canvas-gradient",
1693
+ "--sectiondivider-color-canvas-background"
1694
+ ];
1695
+ let canvasGradient;
1696
+ for (const key of canvasSourceKeys) {
1697
+ if (objectPart[key] !== void 0) {
1698
+ canvasGradient = objectPart[key];
1699
+ break;
1700
+ }
1701
+ }
1702
+ const out = {};
1703
+ for (const [key, value] of Object.entries(objectPart)) {
1704
+ let isFamilyGradient = false;
1705
+ for (const f of FAMILIES) {
1706
+ if (key === `--sectiondivider-${f}-background` || key === `--sectiondivider-${f}-gradient` || key === `--sectiondivider-color-${f}-background`) {
1707
+ isFamilyGradient = true;
1708
+ break;
1709
+ }
1710
+ }
1711
+ if (isFamilyGradient) continue;
1712
+ out[key] = value;
1713
+ }
1714
+ if (canvasGradient !== void 0) {
1715
+ for (const v of SIZE_VARIANTS) {
1716
+ out[`--sectiondivider-${v}-background`] = canvasGradient;
1717
+ }
1718
+ }
1719
+ return out;
1720
+ }
1721
+ var SECTIONDIVIDER_VARIANTS = ["canvas", "neutral", "alternate", "primary", "accent", "special"];
1722
+ function synthesizeSectionDividerGradients(stringPart, objectPart) {
1723
+ const GRADIENT_ANGLE_TOKENS = {
1724
+ "--gradient-angle-horizontal": 90,
1725
+ "--gradient-angle-vertical": 180,
1726
+ "--gradient-angle-diagonal": 135,
1727
+ "--gradient-angle-counter-diagonal": 45
1728
+ };
1729
+ const GRADIENT_STOP_POSITIONS = {
1730
+ "--gradient-stop-start": 0,
1731
+ "--gradient-stop-mid": 50,
1732
+ "--gradient-stop-end": 100
1733
+ };
1734
+ const parseAngle = (raw) => {
1735
+ if (!raw) return 135;
1736
+ if (raw.startsWith("--")) return GRADIENT_ANGLE_TOKENS[raw] ?? 135;
1737
+ const m = raw.match(/^(-?\d+(?:\.\d+)?)\s*deg$/i);
1738
+ return m ? parseFloat(m[1]) : 135;
1739
+ };
1740
+ const parsePosition = (raw, fallback) => {
1741
+ if (!raw) return fallback;
1742
+ if (raw.startsWith("--")) return GRADIENT_STOP_POSITIONS[raw] ?? fallback;
1743
+ const m = raw.match(/^(-?\d+(?:\.\d+)?)\s*%$/);
1744
+ return m ? parseFloat(m[1]) : fallback;
1745
+ };
1746
+ const parseStopColor = (raw) => {
1747
+ if (!raw) return null;
1748
+ if (raw.startsWith("--")) return { color: raw };
1749
+ const mix = raw.match(/^color-mix\(\s*in\s+srgb\s*,\s*var\((--[a-z0-9-]+)\)\s+([\d.]+)%\s*,\s*transparent\s*\)$/i);
1750
+ if (mix) return { color: mix[1], opacity: parseFloat(mix[2]) };
1751
+ return null;
1752
+ };
1753
+ for (const v of SECTIONDIVIDER_VARIANTS) {
1754
+ const slot = `--sectiondivider-${v}-gradient`;
1755
+ if (slot in objectPart) continue;
1756
+ const angleKey = `${slot}-angle`;
1757
+ const has1 = stringPart[`${slot}-stop-1-color`] !== void 0;
1758
+ const has2 = stringPart[`${slot}-stop-2-color`] !== void 0;
1759
+ const has3 = stringPart[`${slot}-stop-3-color`] !== void 0;
1760
+ if (!has1 && !has2 && !has3) continue;
1761
+ const stops = [];
1762
+ const positionDefaults = [0, 50, 100];
1763
+ for (let i = 1; i <= 3; i++) {
1764
+ const c = parseStopColor(stringPart[`${slot}-stop-${i}-color`]);
1765
+ if (!c) continue;
1766
+ const pos = parsePosition(stringPart[`${slot}-stop-${i}-position`], positionDefaults[i - 1]);
1767
+ stops.push({ position: pos, color: c.color, ...c.opacity !== void 0 ? { opacity: c.opacity } : {} });
1768
+ }
1769
+ if (stops.length < 2) continue;
1770
+ objectPart[slot] = {
1771
+ kind: "gradient",
1772
+ value: {
1773
+ type: "linear",
1774
+ angle: parseAngle(stringPart[angleKey]),
1775
+ stops
1776
+ }
1777
+ };
1778
+ }
1779
+ }
1780
+
1781
+ // src/editor/core/themes/themeTypes.ts
1782
+ var THEME_SCHEMA_VERSION = 4;
1783
+
1784
+ // src/editor/core/components/componentConfigKeys.ts
1785
+ var KNOWN_COMPONENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
1786
+ "--dialog-confirm-variant",
1787
+ "--dialog-cancel-variant",
1788
+ // SectionDivider per-variant `color-family` is editor metadata that drives
1789
+ // the family-swap rewrite on aliases. It is not a runtime CSS value, so it
1790
+ // stays in the config bucket. The other intrinsics (align, hairline,
1791
+ // eyebrow/description visibility, eyebrow text-transform) now flow through
1792
+ // the alias bucket as cascading CSS vars — see the 2026-05-22 migration.
1793
+ "--sectiondivider-lg-color-family",
1794
+ "--sectiondivider-md-color-family",
1795
+ "--sectiondivider-sm-color-family"
1796
+ ]);
1797
+
779
1798
  // vite-plugin/themes/normalizeTheme.ts
780
- var THEME_SCHEMA_VERSION = 3;
781
1799
  function asObject(value) {
782
1800
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
783
1801
  }
@@ -799,10 +1817,41 @@ function migrateEmbeddedKey(src) {
799
1817
  const { theme, ...rest } = src;
800
1818
  return { ...rest, colorsAndType: theme };
801
1819
  }
1820
+ function routeKnownConfigKeys(aliases, config) {
1821
+ const outAliases = { ...aliases };
1822
+ const outConfig = { ...config };
1823
+ for (const key of Object.keys(outAliases)) {
1824
+ if (!KNOWN_COMPONENT_CONFIG_KEYS.has(key)) continue;
1825
+ if (outConfig[key] === void 0) outConfig[key] = outAliases[key];
1826
+ delete outAliases[key];
1827
+ }
1828
+ return { aliases: outAliases, config: outConfig };
1829
+ }
1830
+ function migrateEmbeddedComponentConfig(comp, config, fileVersion) {
1831
+ const { schemaVersion: _entryStamp, aliases: rawAliases, config: rawConfig, ...rest } = config;
1832
+ const aliasesIn = asObject(rawAliases);
1833
+ if (!aliasesIn) {
1834
+ return rawConfig !== void 0 ? { ...rest, config: rawConfig } : rest;
1835
+ }
1836
+ const migratedConfig = migrateComponentConfig(
1837
+ comp,
1838
+ aliasesIn,
1839
+ asObject(rawConfig) ?? void 0,
1840
+ fileVersion
1841
+ );
1842
+ const split = routeKnownConfigKeys(migratedConfig.aliases, migratedConfig.config);
1843
+ const out = { ...rest, aliases: split.aliases };
1844
+ if (rawConfig !== void 0 || Object.keys(split.config).length > 0) {
1845
+ out.config = split.config;
1846
+ }
1847
+ return out;
1848
+ }
802
1849
  function normalizeTheme(raw, resolvers) {
803
1850
  const input = asObject(raw) ?? {};
804
1851
  const migrated = input.schemaVersion !== THEME_SCHEMA_VERSION;
805
- const src = migrated ? migrateEmbeddedKey(input) : input;
1852
+ const inputVersion = typeof input.schemaVersion === "number" ? input.schemaVersion : 0;
1853
+ const src = inputVersion < 3 ? migrateEmbeddedKey(input) : input;
1854
+ const componentSchemaVersion = typeof input.componentSchemaVersion === "number" ? input.componentSchemaVersion : 0;
806
1855
  const dropped = [];
807
1856
  const now = (/* @__PURE__ */ new Date()).toISOString();
808
1857
  const embeddedColorsAndType = asObject(src.colorsAndType);
@@ -821,18 +1870,42 @@ function normalizeTheme(raw, resolvers) {
821
1870
  const componentConfigs = {};
822
1871
  for (const [comp, value] of Object.entries(asObject(src.componentConfigs) ?? {})) {
823
1872
  const embedded = asObject(value);
1873
+ let resolvedConfig;
824
1874
  if (embedded) {
825
- componentConfigs[comp] = stripFileMarker(embedded);
1875
+ resolvedConfig = stripFileMarker(embedded);
1876
+ } else {
1877
+ const configName = asString(value, "default");
1878
+ const resolved = asObject(resolvers.readComponentConfig(comp, configName));
1879
+ if (!resolved) {
1880
+ dropped.push(`${comp}/${configName}`);
1881
+ continue;
1882
+ }
1883
+ resolvedConfig = stripFileMarker(resolved);
1884
+ }
1885
+ componentConfigs[comp] = migrateEmbeddedComponentConfig(comp, resolvedConfig, componentSchemaVersion);
1886
+ }
1887
+ const filled = { components: [], aliases: 0, orphans: 0 };
1888
+ for (const comp of resolvers.listComponentNames()) {
1889
+ const base = asObject(resolvers.readComponentConfig(comp, "default"));
1890
+ const baseAliases = base ? asObject(base.aliases) : null;
1891
+ if (!base || !baseAliases) continue;
1892
+ const entry = componentConfigs[comp];
1893
+ if (!entry) {
1894
+ const { schemaVersion: _standaloneStamp, ...rest } = stripFileMarker(base);
1895
+ componentConfigs[comp] = structuredClone(rest);
1896
+ filled.components.push(comp);
826
1897
  continue;
827
1898
  }
828
- const configName = asString(value, "default");
829
- if (configName === "default") continue;
830
- const resolved = asObject(resolvers.readComponentConfig(comp, configName));
831
- if (!resolved) {
832
- dropped.push(`${comp}/${configName}`);
833
- continue;
1899
+ const entryAliases = asObject(entry.aliases) ?? {};
1900
+ for (const key of Object.keys(baseAliases)) {
1901
+ if (key in entryAliases) continue;
1902
+ entryAliases[key] = structuredClone(baseAliases[key]);
1903
+ filled.aliases++;
1904
+ }
1905
+ entry.aliases = entryAliases;
1906
+ for (const key of Object.keys(entryAliases)) {
1907
+ if (!(key in baseAliases)) filled.orphans++;
834
1908
  }
835
- componentConfigs[comp] = stripFileMarker(resolved);
836
1909
  }
837
1910
  return {
838
1911
  theme: {
@@ -841,10 +1914,12 @@ function normalizeTheme(raw, resolvers) {
841
1914
  updatedAt: asString(src.updatedAt, now),
842
1915
  schemaVersion: THEME_SCHEMA_VERSION,
843
1916
  colorsAndType,
844
- componentConfigs
1917
+ componentConfigs,
1918
+ componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION
845
1919
  },
846
1920
  dropped,
847
- migrated
1921
+ migrated,
1922
+ filled
848
1923
  };
849
1924
  }
850
1925
 
@@ -987,7 +2062,7 @@ function ensureScale(css, opts) {
987
2062
  lines.splice(insertAfter + 1, 0, ...block);
988
2063
  return lines.join("\n");
989
2064
  }
990
- function renameToken(css, oldName, newName) {
2065
+ function renameToken2(css, oldName, newName) {
991
2066
  const defined = collectDefinedTokens(css);
992
2067
  if (!defined.has(oldName) || defined.has(newName)) return css;
993
2068
  const re = new RegExp(escapeRe(oldName) + "(?![a-z0-9-])", "gi");
@@ -1206,7 +2281,7 @@ var tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions = {
1206
2281
  };
1207
2282
 
1208
2283
  // vite-plugin/tokensCssMigrations/migrations/2026-07-20-line-height-rename.ts
1209
- var RENAMES = [
2284
+ var RENAMES2 = [
1210
2285
  ["--line-height-xs", "--line-height-none"],
1211
2286
  ["--line-height-sm", "--line-height-tighter"],
1212
2287
  ["--line-height-md", "--line-height-normal"],
@@ -1226,7 +2301,7 @@ var tokensCssMigration_2026_07_20_lineHeightRename = {
1226
2301
  description: "Reshape --line-height-{xs..xl} to leading vocabulary (none/tightest/tighter/tight/normal/relaxed); retire the 2.0 slot",
1227
2302
  apply(css) {
1228
2303
  let out = css;
1229
- for (const [oldName, newName] of RENAMES) out = renameToken(out, oldName, newName);
2304
+ for (const [oldName, newName] of RENAMES2) out = renameToken2(out, oldName, newName);
1230
2305
  out = removeToken(out, "--line-height-xl");
1231
2306
  out = ensureScale(out, {
1232
2307
  anchorPrefixes: ["--line-height-", "--font-size-", "--font-weight-", "--font-"],
@@ -1303,6 +2378,20 @@ var tokensCssMigration_2026_07_20_semanticTextStyles = {
1303
2378
  }
1304
2379
  };
1305
2380
 
2381
+ // vite-plugin/tokensCssMigrations/migrations/2026-08-19-oklch-color-values.ts
2382
+ var HEX_DECLARATION_RE = /(--[a-z0-9-]+:\s*)#([0-9a-f]{6})(?=\s*[;}])/gi;
2383
+ var tokensCssMigration_2026_08_19_oklchColorValues = {
2384
+ id: "2026-08-19-oklch-color-values",
2385
+ kind: "breaking",
2386
+ description: "Rewrite hex token values as oklch()",
2387
+ apply(css) {
2388
+ return css.replace(HEX_DECLARATION_RE, (_match, prefix, hex) => {
2389
+ const { l, c, h } = hexToOklch(`#${hex}`);
2390
+ return `${prefix}${oklchToCss(l, c, h)}`;
2391
+ });
2392
+ }
2393
+ };
2394
+
1306
2395
  // vite-plugin/tokensCssMigrations/index.ts
1307
2396
  var TOKENS_CSS_MIGRATIONS = [
1308
2397
  tokensCssMigration_2026_05_29_typographyScaleAdditions,
@@ -1311,7 +2400,8 @@ var TOKENS_CSS_MIGRATIONS = [
1311
2400
  tokensCssMigration_2026_06_04_removeDeadSizeIconScale,
1312
2401
  tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions,
1313
2402
  tokensCssMigration_2026_07_20_lineHeightRename,
1314
- tokensCssMigration_2026_07_20_semanticTextStyles
2403
+ tokensCssMigration_2026_07_20_semanticTextStyles,
2404
+ tokensCssMigration_2026_08_19_oklchColorValues
1315
2405
  ];
1316
2406
  function runTokensCssMigrations(css) {
1317
2407
  return foldMigrations(css, () => true);
@@ -1379,8 +2469,29 @@ var PKG_VERSION = (() => {
1379
2469
  })();
1380
2470
  var BUNDLE_KIND_BY_VERSION = {
1381
2471
  1: "manifest-bundle",
2472
+ 3: "theme-bundle",
1382
2473
  [THEME_SCHEMA_VERSION]: "theme-bundle"
1383
2474
  };
2475
+ function extractAliasDeclarations(body) {
2476
+ const aliases = {};
2477
+ const re = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
2478
+ let m;
2479
+ while ((m = re.exec(body)) !== null) {
2480
+ const value = m[2].trim();
2481
+ const plain = value.match(/^var\((--[a-z0-9-]+)\)$/i);
2482
+ if (plain) {
2483
+ aliases[m[1]] = plain[1];
2484
+ continue;
2485
+ }
2486
+ if (parseColorOpacity(value)) {
2487
+ aliases[m[1]] = value;
2488
+ continue;
2489
+ }
2490
+ const gradient = parseGradientValue(value);
2491
+ if (gradient) aliases[m[1]] = { kind: "gradient", value: gradient };
2492
+ }
2493
+ return aliases;
2494
+ }
1384
2495
  function themeFileApi(opts) {
1385
2496
  const dataDirs = resolveDataDirs({
1386
2497
  dataDir: opts.dataDir,
@@ -1557,6 +2668,7 @@ function themeFileApi(opts) {
1557
2668
  const overrides = [];
1558
2669
  const defaultAliases = defaultCfg.aliases ?? {};
1559
2670
  for (const [varName, semanticValue] of Object.entries(prodCfg.aliases ?? {})) {
2671
+ if (!(varName in defaultAliases)) continue;
1560
2672
  if (!aliasValuesEqual(defaultAliases[varName], semanticValue)) {
1561
2673
  overrides.push([varName, semanticValue]);
1562
2674
  }
@@ -1619,18 +2731,6 @@ function themeFileApi(opts) {
1619
2731
  import_fs4.default.writeFileSync(FONTS_CSS_PATH, content);
1620
2732
  console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${import_path4.default.basename(FONTS_CSS_PATH)}`);
1621
2733
  }
1622
- function extractAliasDeclarations(body) {
1623
- const aliases = {};
1624
- const re = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
1625
- let m;
1626
- while ((m = re.exec(body)) !== null) {
1627
- const value = m[2].trim();
1628
- const plain = value.match(/^var\((--[a-z0-9-]+)\)$/i);
1629
- if (plain) aliases[m[1]] = plain[1];
1630
- else if (parseColorOpacity(value)) aliases[m[1]] = value;
1631
- }
1632
- return aliases;
1633
- }
1634
2734
  function componentNameFromFile(filePath) {
1635
2735
  return import_path4.default.basename(filePath, ".svelte").toLowerCase();
1636
2736
  }
@@ -1706,17 +2806,10 @@ ${lines.join("\n")}
1706
2806
  r.ensureDir();
1707
2807
  const defaultPath = r.filePath("default");
1708
2808
  const sourceStat = import_fs4.default.statSync(sourcePath);
1709
- if (import_fs4.default.existsSync(defaultPath)) {
1710
- const defaultStat = import_fs4.default.statSync(defaultPath);
1711
- if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
1712
- }
1713
- const source = import_fs4.default.readFileSync(sourcePath, "utf-8");
1714
- const body = extractGlobalRootBody(source);
1715
- const aliases = extractAliasDeclarations(body);
1716
- const now = (/* @__PURE__ */ new Date()).toISOString();
1717
- let createdAt = now;
2809
+ let createdAt = (/* @__PURE__ */ new Date()).toISOString();
1718
2810
  let existingUpdatedAt;
1719
2811
  let existingAliases;
2812
+ let existingSchemaVersion;
1720
2813
  if (import_fs4.default.existsSync(defaultPath)) {
1721
2814
  try {
1722
2815
  const existing = JSON.parse(import_fs4.default.readFileSync(defaultPath, "utf-8"));
@@ -1725,25 +2818,29 @@ ${lines.join("\n")}
1725
2818
  if (existing.aliases && typeof existing.aliases === "object") {
1726
2819
  existingAliases = existing.aliases;
1727
2820
  }
2821
+ if (typeof existing.schemaVersion === "number") existingSchemaVersion = existing.schemaVersion;
1728
2822
  } catch {
1729
2823
  }
1730
- }
1731
- if (existingAliases) {
1732
- for (const [token, val] of Object.entries(existingAliases)) {
1733
- if (val !== null && typeof val === "object" && !(token in aliases)) {
1734
- aliases[token] = val;
1735
- }
2824
+ const defaultStat = import_fs4.default.statSync(defaultPath);
2825
+ if (defaultStat.mtimeMs >= sourceStat.mtimeMs && existingSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION) {
2826
+ return;
1736
2827
  }
1737
2828
  }
2829
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2830
+ const source = import_fs4.default.readFileSync(sourcePath, "utf-8");
2831
+ const body = extractGlobalRootBody(source);
2832
+ const aliases = extractAliasDeclarations(body);
1738
2833
  const aliasesUnchanged = existingAliases !== void 0 && JSON.stringify(existingAliases) === JSON.stringify(aliases);
2834
+ const schemaStampCurrent = existingSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION;
1739
2835
  const defaultConfig = {
1740
2836
  name: "default",
1741
2837
  component: comp,
1742
2838
  createdAt,
1743
2839
  updatedAt: aliasesUnchanged && existingUpdatedAt ? existingUpdatedAt : now,
1744
- aliases
2840
+ aliases,
2841
+ schemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION
1745
2842
  };
1746
- if (aliasesUnchanged && existingUpdatedAt) {
2843
+ if (aliasesUnchanged && schemaStampCurrent && existingUpdatedAt) {
1747
2844
  const t = /* @__PURE__ */ new Date();
1748
2845
  import_fs4.default.utimesSync(defaultPath, t, t);
1749
2846
  return;
@@ -1791,6 +2888,7 @@ ${lines.join("\n")}
1791
2888
  const diskThemeResolvers = {
1792
2889
  readColorsAndType: (name) => colorsAndTypeResource.readJson(sanitizeFileName(name)),
1793
2890
  readComponentConfig: (comp, name) => readComponentConfig(comp, sanitizeFileName(name)),
2891
+ listComponentNames,
1794
2892
  normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
1795
2893
  };
1796
2894
  function readTheme(fileName) {
@@ -1816,7 +2914,7 @@ ${lines.join("\n")}
1816
2914
  return "corrupt";
1817
2915
  }
1818
2916
  function notAThemeError(fileName) {
1819
- return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it.`;
2917
+ return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme generator skill "generate-theme".`;
1820
2918
  }
1821
2919
  function respondUnreadableTheme(res, fileName, missingError) {
1822
2920
  switch (unreadableThemeReason(fileName)) {
@@ -1878,19 +2976,26 @@ ${lines.join("\n")}
1878
2976
  const componentConfigs = {};
1879
2977
  for (const comp of listComponentNames()) {
1880
2978
  const cfg = readComponentConfig(comp, "default");
1881
- if (cfg) componentConfigs[comp] = cfg;
2979
+ if (cfg) {
2980
+ const { schemaVersion: _standaloneStamp, ...rest } = cfg;
2981
+ componentConfigs[comp] = rest;
2982
+ }
1882
2983
  }
1883
- if (existing?.schemaVersion === THEME_SCHEMA_VERSION && JSON.stringify(existing.colorsAndType) === JSON.stringify(colorsAndType) && JSON.stringify(existing.componentConfigs) === JSON.stringify(componentConfigs)) {
2984
+ const contentUnchanged = JSON.stringify(existing?.colorsAndType) === JSON.stringify(colorsAndType) && JSON.stringify(existing?.componentConfigs) === JSON.stringify(componentConfigs);
2985
+ if (existing?.schemaVersion === THEME_SCHEMA_VERSION && existing?.componentSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION && contentUnchanged) {
1884
2986
  return;
1885
2987
  }
1886
2988
  const now = (/* @__PURE__ */ new Date()).toISOString();
1887
2989
  writeTheme("default", {
1888
2990
  name: "Motion Proto",
1889
2991
  createdAt: typeof existing?.createdAt === "string" ? existing.createdAt : now,
1890
- updatedAt: now,
2992
+ // A stamp-only rewrite (componentSchemaVersion catching up) is not a
2993
+ // content change, so it doesn't bump this the way a real edit would.
2994
+ updatedAt: contentUnchanged && typeof existing?.updatedAt === "string" ? existing.updatedAt : now,
1891
2995
  schemaVersion: THEME_SCHEMA_VERSION,
1892
2996
  colorsAndType,
1893
- componentConfigs
2997
+ componentConfigs,
2998
+ componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION
1894
2999
  });
1895
3000
  }
1896
3001
  function ensureThemesDir(warn) {
@@ -1922,14 +3027,24 @@ ${lines.join("\n")}
1922
3027
  if (!shipped) return null;
1923
3028
  return { data: normalizeColorsAndType(shipped), source: "default" };
1924
3029
  }
3030
+ const loggedMissingThemeEntries = /* @__PURE__ */ new Set();
3031
+ function resolveComponentConfigOrDefault(comp, theme) {
3032
+ const embedded = theme?.componentConfigs[comp];
3033
+ if (embedded) return { ...embedded, component: comp };
3034
+ if (!loggedMissingThemeEntries.has(comp)) {
3035
+ loggedMissingThemeEntries.add(comp);
3036
+ console.warn(
3037
+ `[live-tokens] "${comp}" has no entry in the open theme; resolving from its shipped default.`
3038
+ );
3039
+ }
3040
+ return readComponentConfig(comp, "default");
3041
+ }
1925
3042
  function resolveLiveComponentConfig(comp, theme) {
1926
3043
  const working = componentResource(comp).readWorking();
1927
3044
  if (working !== null) return { data: working, source: "working" };
1928
- const embedded = theme?.componentConfigs[comp];
1929
- if (embedded) return { data: { ...embedded, component: comp }, source: "theme" };
1930
- const cfg = readComponentConfig(comp, "default");
3045
+ const cfg = resolveComponentConfigOrDefault(comp, theme);
1931
3046
  if (!cfg) return null;
1932
- return { data: cfg, source: "default" };
3047
+ return { data: cfg, source: "theme" };
1933
3048
  }
1934
3049
  function resolveSavedColorsAndType(theme) {
1935
3050
  if (theme?.colorsAndType) return theme.colorsAndType;
@@ -1937,9 +3052,7 @@ ${lines.join("\n")}
1937
3052
  return shipped ? normalizeColorsAndType(shipped) : null;
1938
3053
  }
1939
3054
  function resolveSavedComponentConfig(comp, theme) {
1940
- const embedded = theme?.componentConfigs[comp];
1941
- if (embedded) return { ...embedded, component: comp };
1942
- return readComponentConfig(comp, "default");
3055
+ return resolveComponentConfigOrDefault(comp, theme);
1943
3056
  }
1944
3057
  function sameJsonValue(a, b) {
1945
3058
  if (a === null || b === null) return a === b;
@@ -1975,26 +3088,9 @@ ${lines.join("\n")}
1975
3088
  if (entry.isDirectory()) componentResource(entry.name).clearWorking();
1976
3089
  }
1977
3090
  }
1978
- function formatAliasGradient(v) {
1979
- const stopColor = (s) => {
1980
- const base = s.color.startsWith("--") ? `var(${s.color})` : s.color;
1981
- const opacity = s.opacity ?? 100;
1982
- return opacity >= 100 ? base : `color-mix(in srgb, ${base} ${opacity}%, transparent)`;
1983
- };
1984
- if (v.type === "none") return "transparent";
1985
- if (v.type === "solid") {
1986
- const first = v.stops[0];
1987
- if (!first) return "transparent";
1988
- return stopColor(first);
1989
- }
1990
- const stops = v.stops.map((s) => `${stopColor(s)} ${s.position}%`).join(", ");
1991
- if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
1992
- const radial = v.radius && v.radius > 0 ? `circle ${v.radius}px at center` : "circle";
1993
- return `radial-gradient(${radial}, ${stops})`;
1994
- }
1995
3091
  function aliasValueToCss(v) {
1996
3092
  if (typeof v === "string") return v.startsWith("--") ? `var(${v})` : v;
1997
- return formatAliasGradient(v.value);
3093
+ return formatGradientValue(v.value);
1998
3094
  }
1999
3095
  function aliasValuesEqual(a, b) {
2000
3096
  if (a === void 0 || b === void 0) return a === b;
@@ -2144,13 +3240,9 @@ ${lines.join("\n")}
2144
3240
  }
2145
3241
  }
2146
3242
  async function handleListComponents({ res }) {
2147
- const theme = activeTheme();
2148
3243
  const components = listComponentNames().map((comp) => ({
2149
3244
  name: comp,
2150
- // Presence, not a parse: one corrupt buffer must not take the listing
2151
- // down, and the door only has to say where each component's live config
2152
- // comes from.
2153
- source: componentResource(comp).hasWorking() ? "working" : theme?.componentConfigs[comp] ? "theme" : "default"
3245
+ source: componentResource(comp).hasWorking() ? "working" : "theme"
2154
3246
  }));
2155
3247
  jsonResponse(res, 200, { components });
2156
3248
  }
@@ -2161,6 +3253,7 @@ ${lines.join("\n")}
2161
3253
  }
2162
3254
  async function handleGetComponentActive({ params, res }) {
2163
3255
  const [comp] = params;
3256
+ if (rejectUnknownComponent(res, comp)) return;
2164
3257
  const resolved = resolveLiveComponentConfig(comp, activeTheme());
2165
3258
  if (!resolved) {
2166
3259
  jsonResponse(res, 404, { error: "Active config not found" });
@@ -2407,9 +3500,11 @@ ${lines.join("\n")}
2407
3500
  respondUnreadableTheme(res, fileName, "Theme not found");
2408
3501
  return;
2409
3502
  }
2410
- const { theme } = read;
3503
+ const { theme, filled } = read;
2411
3504
  if (!theme.colorsAndType) {
2412
- jsonResponse(res, 422, { error: "This theme carries no colors and type" });
3505
+ jsonResponse(res, 422, {
3506
+ error: 'This theme carries no colors and type. Build one with the theme generator skill "generate-theme".'
3507
+ });
2413
3508
  return;
2414
3509
  }
2415
3510
  clearAllWorking();
@@ -2433,7 +3528,8 @@ ${lines.join("\n")}
2433
3528
  _source: resolvedColorsAndType.source
2434
3529
  },
2435
3530
  componentConfigs: resolvedConfigs,
2436
- skippedComponents
3531
+ skippedComponents,
3532
+ filled
2437
3533
  });
2438
3534
  }
2439
3535
  async function handleExportTheme({ params, res }) {
@@ -2488,10 +3584,27 @@ ${lines.join("\n")}
2488
3584
  }
2489
3585
  const bundleResolvers = {
2490
3586
  readColorsAndType: () => bundle.theme,
2491
- readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`],
3587
+ // A named ref resolves inside the bundle only — a v1 bundle's
3588
+ // `${comp}/${name}` map is itself the sender's data, and a stale name
3589
+ // must still land in `dropped` rather than quietly picking up this
3590
+ // install's copy. But `'default'` never named a bundled entry; v1
3591
+ // spelled "no override" that way (normalizeTheme.ts's pointer-
3592
+ // resolution loop treats it identically), so it always meant *this
3593
+ // install's* default, not something the bundle carries. The
3594
+ // completeness fill (Wave 2 of docs/plans/theme-completeness.md) relies
3595
+ // on that fall-through: without it, `readComponentConfig(comp,
3596
+ // 'default')` answers undefined for every component (no bundle ever
3597
+ // carries a `<comp>/default` entry), the fill loop no-ops 25 times, and
3598
+ // an incomplete bundle stays incomplete forever — exactly the drift RJC
3599
+ // 10 exists to prevent.
3600
+ readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`] ?? (name === "default" ? readComponentConfig(comp, "default") : void 0),
3601
+ // Installed components are a property of this install, not of the
3602
+ // bundle being imported, so the completeness fill (Wave 2) uses the
3603
+ // same list the disk resolver does.
3604
+ listComponentNames,
2492
3605
  normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
2493
3606
  };
2494
- const { theme, dropped } = normalizeTheme(bundle.manifest, bundleResolvers);
3607
+ const { theme, dropped, filled } = normalizeTheme(bundle.manifest, bundleResolvers);
2495
3608
  if (theme.colorsAndType) theme.colorsAndType = normalizeColorsAndType(theme.colorsAndType);
2496
3609
  if (!theme.colorsAndType) {
2497
3610
  jsonResponse(res, 400, { error: "Bundle carries no theme" });
@@ -2512,7 +3625,8 @@ ${lines.join("\n")}
2512
3625
  ok: true,
2513
3626
  theme: finalName,
2514
3627
  renames,
2515
- dropped
3628
+ dropped,
3629
+ filled
2516
3630
  });
2517
3631
  }
2518
3632
  async function handleAdoptLook({ res }) {