@motion-proto/live-tokens 0.7.1 → 0.9.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 (96) hide show
  1. package/.claude/skills/live-tokens-add-component/SKILL.md +488 -0
  2. package/README.md +34 -0
  3. package/dist-plugin/index.cjs +707 -90
  4. package/dist-plugin/index.d.cts +1 -0
  5. package/dist-plugin/index.d.ts +1 -0
  6. package/dist-plugin/index.js +707 -90
  7. package/package.json +6 -2
  8. package/src/app/site.css +1 -1
  9. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +34 -27
  10. package/src/editor/component-editor/DialogEditor.svelte +4 -4
  11. package/src/editor/component-editor/NotificationEditor.svelte +3 -1
  12. package/src/editor/component-editor/SectionDividerEditor.svelte +439 -112
  13. package/src/editor/component-editor/StandardButtonsEditor.svelte +13 -1
  14. package/src/editor/component-editor/editors.d.ts +10 -0
  15. package/src/editor/component-editor/index.ts +16 -1
  16. package/src/editor/component-editor/registry.ts +103 -26
  17. package/src/editor/component-editor/scaffolding/AngleDial.svelte +52 -13
  18. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -11
  19. package/src/editor/component-editor/scaffolding/ComponentsTab.svelte +2 -2
  20. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +0 -1
  21. package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
  22. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +15 -2
  23. package/src/editor/component-editor/scaffolding/StateBlock.svelte +103 -15
  24. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +9 -6
  25. package/src/editor/component-editor/scaffolding/TypeEditor.svelte +13 -1
  26. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +239 -25
  27. package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -0
  28. package/src/editor/component-editor/scaffolding/componentSources.ts +3 -3
  29. package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
  30. package/src/editor/component-editor/scaffolding/types.ts +11 -0
  31. package/src/editor/core/components/componentConfigKeys.ts +22 -3
  32. package/src/editor/core/components/componentConfigService.ts +2 -2
  33. package/src/editor/core/components/componentPersist.ts +7 -5
  34. package/src/editor/core/manifests/manifestService.ts +58 -3
  35. package/src/editor/core/palettes/familySwap.ts +99 -0
  36. package/src/editor/core/palettes/paletteDerivation.ts +69 -0
  37. package/src/editor/core/palettes/tokenRegistry.ts +4 -1
  38. package/src/editor/core/store/editorStore.ts +206 -12
  39. package/src/editor/core/store/editorTypes.ts +55 -12
  40. package/src/editor/core/store/gradientSource.ts +192 -0
  41. package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
  42. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
  43. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
  44. package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
  45. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
  46. package/src/editor/core/themes/migrations/index.ts +10 -0
  47. package/src/editor/core/themes/slices/components.ts +27 -4
  48. package/src/editor/core/themes/slices/gradients.ts +88 -13
  49. package/src/editor/core/themes/themeInit.ts +2 -2
  50. package/src/editor/core/themes/themeTypes.ts +56 -1
  51. package/src/editor/index.ts +10 -1
  52. package/src/editor/overlay/ColumnsOverlay.svelte +0 -1
  53. package/src/editor/overlay/LiveEditorOverlay.svelte +1 -4
  54. package/src/editor/pages/ComponentEditorPage.svelte +53 -3
  55. package/src/editor/pages/EditorShell.svelte +53 -3
  56. package/src/editor/styles/ui-editor.css +1 -0
  57. package/src/editor/styles/ui-form-controls.css +19 -20
  58. package/src/editor/ui/BezierCurveEditor.svelte +114 -63
  59. package/src/editor/ui/EditorViewSwitcher.svelte +0 -1
  60. package/src/editor/ui/FileLoadList.svelte +22 -5
  61. package/src/editor/ui/FontStackEditor.svelte +214 -76
  62. package/src/editor/ui/GradientEditor.svelte +435 -215
  63. package/src/editor/ui/GradientStopPicker.svelte +11 -3
  64. package/src/editor/ui/ManifestFileManager.svelte +71 -4
  65. package/src/editor/ui/PaletteEditor.svelte +52 -79
  66. package/src/editor/ui/ProjectFontsSection.svelte +328 -293
  67. package/src/editor/ui/ThemeFileManager.svelte +0 -4
  68. package/src/editor/ui/UIFontFamilySelector.svelte +0 -1
  69. package/src/editor/ui/UIFontSizeSelector.svelte +3 -0
  70. package/src/editor/ui/UIInfoPopover.svelte +0 -1
  71. package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
  72. package/src/editor/ui/UIPaletteSelector.svelte +31 -4
  73. package/src/editor/ui/UIPillButton.svelte +33 -3
  74. package/src/editor/ui/UISegmentedControl.svelte +114 -0
  75. package/src/editor/ui/UITokenSelector.svelte +4 -1
  76. package/src/editor/ui/VariablesTab.svelte +41 -35
  77. package/src/editor/ui/palette/OverridesPanel.svelte +14 -37
  78. package/src/editor/ui/palette/PaletteBase.svelte +3 -3
  79. package/src/editor/ui/sections/ColumnsSection.svelte +1 -2
  80. package/src/editor/ui/sections/GradientsSection.svelte +1 -1
  81. package/src/editor/ui/sections/OverlaysSection.svelte +1 -1
  82. package/src/editor/ui/sections/ShadowsSection.svelte +1 -1
  83. package/src/system/components/Button.svelte +2 -2
  84. package/src/system/components/Card.svelte +29 -1
  85. package/src/system/components/CollapsibleSection.svelte +25 -2
  86. package/src/system/components/Dialog.svelte +24 -4
  87. package/src/system/components/FloatingTokenTags.css +43 -24
  88. package/src/system/components/FloatingTokenTags.svelte +88 -137
  89. package/src/system/components/Notification.svelte +8 -1
  90. package/src/system/components/SectionDivider.svelte +532 -381
  91. package/src/system/styles/CONVENTIONS.md +1 -1
  92. package/src/system/styles/fonts.css +3 -16
  93. package/src/system/styles/tokens.css +356 -1199
  94. package/src/system/styles/tokens.generated.css +544 -0
  95. package/src/editor/component-editor/scaffolding/DividerEditor.svelte +0 -94
  96. package/src/editor/component-editor/scaffolding/GradientCard.svelte +0 -296
@@ -54,6 +54,410 @@ function sanitizeFileName(name) {
54
54
  return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
55
55
  }
56
56
 
57
+ // src/editor/core/palettes/oklch.ts
58
+ function srgbToLinear(c) {
59
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
60
+ }
61
+ function linearToSrgb(c) {
62
+ return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
63
+ }
64
+ function hexToLinearRgb(hex) {
65
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
66
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
67
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
68
+ return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
69
+ }
70
+ function linearRgbToHex(r, g, b) {
71
+ const toHex = (c) => {
72
+ const v = Math.round(Math.max(0, Math.min(1, linearToSrgb(c))) * 255);
73
+ return v.toString(16).padStart(2, "0");
74
+ };
75
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
76
+ }
77
+ function linearRgbToOklab(r, g, b) {
78
+ const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
79
+ const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
80
+ const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
81
+ const l = Math.cbrt(l_);
82
+ const m = Math.cbrt(m_);
83
+ const s = Math.cbrt(s_);
84
+ return [
85
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
86
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
87
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
88
+ ];
89
+ }
90
+ function oklabToLinearRgb(L, a, b) {
91
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
92
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
93
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
94
+ const l = l_ * l_ * l_;
95
+ const m = m_ * m_ * m_;
96
+ const s = s_ * s_ * s_;
97
+ return [
98
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
99
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
100
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
101
+ ];
102
+ }
103
+ function oklabToOklch(L, a, b) {
104
+ const c = Math.sqrt(a * a + b * b);
105
+ let h = Math.atan2(b, a) * 180 / Math.PI;
106
+ if (h < 0) h += 360;
107
+ return { l: L, c, h };
108
+ }
109
+ function oklchToOklab(l, c, h) {
110
+ const hRad = h * Math.PI / 180;
111
+ return [l, c * Math.cos(hRad), c * Math.sin(hRad)];
112
+ }
113
+ function hexToOklch(hex) {
114
+ const [r, g, b] = hexToLinearRgb(hex);
115
+ const [L, a, bVal] = linearRgbToOklab(r, g, b);
116
+ return oklabToOklch(L, a, bVal);
117
+ }
118
+ function oklchToHex(l, c, h) {
119
+ const [L, a, b] = oklchToOklab(l, c, h);
120
+ const [r, g, bVal] = oklabToLinearRgb(L, a, b);
121
+ return linearRgbToHex(r, g, bVal);
122
+ }
123
+ function isInGamut(r, g, b) {
124
+ const eps = 1e-4;
125
+ return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;
126
+ }
127
+ function gamutClamp(l, c, h) {
128
+ if (l <= 0) return { l: 0, c: 0, h };
129
+ if (l >= 1) return { l: 1, c: 0, h };
130
+ const [L, a, b] = oklchToOklab(l, c, h);
131
+ const [r, g, bVal] = oklabToLinearRgb(L, a, b);
132
+ if (isInGamut(r, g, bVal)) return { l, c, h };
133
+ let lo = 0;
134
+ let hi = c;
135
+ for (let i = 0; i < 20; i++) {
136
+ const mid = (lo + hi) / 2;
137
+ const [La, aa, ba] = oklchToOklab(l, mid, h);
138
+ const [rr, gg, bb] = oklabToLinearRgb(La, aa, ba);
139
+ if (isInGamut(rr, gg, bb)) {
140
+ lo = mid;
141
+ } else {
142
+ hi = mid;
143
+ }
144
+ }
145
+ return { l, c: lo, h };
146
+ }
147
+
148
+ // src/editor/ui/curveEngine.ts
149
+ function makeAnchor(x, y, tangentLen = 15) {
150
+ return { x, y, inDx: -tangentLen, inDy: 0, outDx: tangentLen, outDy: 0 };
151
+ }
152
+ function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
153
+ const u = 1 - t, u2 = u * u, u3 = u2 * u;
154
+ const t2 = t * t, t3 = t2 * t;
155
+ return {
156
+ x: u3 * p0x + 3 * u2 * t * c0x + 3 * u * t2 * c1x + t3 * p1x,
157
+ y: u3 * p0y + 3 * u2 * t * c0y + 3 * u * t2 * c1y + t3 * p1y
158
+ };
159
+ }
160
+ function sampleCurve(anchors, xPos) {
161
+ if (anchors.length === 0) return 0;
162
+ if (anchors.length === 1) return anchors[0].y;
163
+ if (xPos <= anchors[0].x) return anchors[0].y;
164
+ if (xPos >= anchors[anchors.length - 1].x) return anchors[anchors.length - 1].y;
165
+ let seg = 0;
166
+ while (seg < anchors.length - 2 && anchors[seg + 1].x < xPos) seg++;
167
+ const a0 = anchors[seg], a1 = anchors[seg + 1];
168
+ const p0x = a0.x, p0y = a0.y;
169
+ const c0x = a0.x + a0.outDx, c0y = a0.y + a0.outDy;
170
+ const c1x = a1.x + a1.inDx, c1y = a1.y + a1.inDy;
171
+ const p1x = a1.x, p1y = a1.y;
172
+ let lo = 0, hi = 1;
173
+ for (let i = 0; i < 20; i++) {
174
+ const mid = (lo + hi) / 2;
175
+ const pt = evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, mid);
176
+ if (pt.x < xPos) lo = mid;
177
+ else hi = mid;
178
+ }
179
+ return evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, (lo + hi) / 2).y;
180
+ }
181
+
182
+ // src/editor/core/palettes/paletteDerivation.ts
183
+ var PALETTE_SPECS = [
184
+ { label: "Neutral", cssNamespace: "neutral", mode: "gray", initialColor: "#808080" },
185
+ { label: "Alternate", cssNamespace: "alternate", mode: "gray", initialColor: "#808080" },
186
+ { label: "Background", cssNamespace: "canvas", mode: "chromatic", emptySelector: true, initialColor: "#1a1a2e" },
187
+ { label: "Brand", cssNamespace: "brand", mode: "chromatic", initialColor: "#c93636" },
188
+ { label: "Accent", cssNamespace: "accent", mode: "chromatic", initialColor: "#f49e0b" },
189
+ { label: "Special", cssNamespace: "special", mode: "chromatic", initialColor: "#8b5cf6" },
190
+ { label: "Info", cssNamespace: "info", mode: "chromatic", initialColor: "#3077e8" },
191
+ { label: "Success", cssNamespace: "success", mode: "chromatic", initialColor: "#21c45d" },
192
+ { label: "Warning", cssNamespace: "warning", mode: "chromatic", initialColor: "#e66e1a" },
193
+ { label: "Danger", cssNamespace: "danger", mode: "chromatic", initialColor: "#e8304f" }
194
+ ];
195
+ var PALETTE_STEPS = [
196
+ { label: "100" },
197
+ { label: "200" },
198
+ { label: "300" },
199
+ { label: "400" },
200
+ { label: "500" },
201
+ { label: "600" },
202
+ { label: "700" },
203
+ { label: "800" },
204
+ { label: "850" },
205
+ { label: "900" },
206
+ { label: "950" }
207
+ ];
208
+ var GRAY_STEPS = [
209
+ { label: "100" },
210
+ { label: "200" },
211
+ { label: "300" },
212
+ { label: "400" },
213
+ { label: "500" },
214
+ { label: "600" },
215
+ { label: "700" },
216
+ { label: "800" },
217
+ { label: "850" },
218
+ { label: "900" },
219
+ { label: "950" }
220
+ ];
221
+ var SCALES = [
222
+ {
223
+ title: "Surfaces",
224
+ isText: false,
225
+ steps: [
226
+ { name: "lowest", position: -1 },
227
+ { name: "lower", position: -2 / 3 },
228
+ { name: "low", position: -1 / 3 },
229
+ { name: "default", position: 0 },
230
+ { name: "high", position: 1 / 3 },
231
+ { name: "higher", position: 2 / 3 },
232
+ { name: "highest", position: 1 }
233
+ ]
234
+ },
235
+ {
236
+ title: "Borders",
237
+ isText: false,
238
+ steps: [
239
+ { name: "faint", position: -1 },
240
+ { name: "subtle", position: -0.5 },
241
+ { name: "default", position: 0 },
242
+ { name: "medium", position: 0.5 },
243
+ { name: "strong", position: 1 }
244
+ ]
245
+ },
246
+ {
247
+ title: "Text",
248
+ isText: true,
249
+ steps: [
250
+ { name: "primary", position: 0 },
251
+ { name: "secondary", position: 0 },
252
+ { name: "tertiary", position: 0 },
253
+ { name: "muted", position: 0 },
254
+ { name: "disabled", position: 0 }
255
+ ]
256
+ }
257
+ ];
258
+ var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
259
+ var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
260
+ var DEFAULT_GRAY_LIGHTNESS = () => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
261
+ var DEFAULT_GRAY_SATURATION = () => [makeAnchor(0, 20, 30), makeAnchor(100, 20, 30)];
262
+ var DEFAULT_TINT_CHROMA = 0.04;
263
+ var defaultScaleCurves = {
264
+ Surfaces: {
265
+ lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
266
+ saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
267
+ },
268
+ Borders: {
269
+ lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
270
+ saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
271
+ },
272
+ Text: {
273
+ lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
274
+ saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
275
+ }
276
+ };
277
+ function paletteStepKey(label) {
278
+ return `Palette-${label}`;
279
+ }
280
+ function grayStepKey(label) {
281
+ return `gray-${label}`;
282
+ }
283
+ function stepKey(scaleTitle, stepName) {
284
+ return `${scaleTitle}-${stepName}`;
285
+ }
286
+ function stepIndexToX(index, total) {
287
+ return total > 1 ? index / (total - 1) * 100 : 50;
288
+ }
289
+ function computePaletteColor(index, base, lightnessCurve, saturationCurve, curveOffset) {
290
+ const { c: baseC, h } = hexToOklch(base);
291
+ const xPos = stepIndexToX(index, PALETTE_STEPS.length);
292
+ const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
293
+ const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
294
+ const targetC = baseC * satMul;
295
+ const clamped = gamutClamp(targetL, targetC, h);
296
+ return oklchToHex(clamped.l, clamped.c, clamped.h);
297
+ }
298
+ function computeGrayColor(index, hue, chroma, grayLightnessCurve, graySaturationCurve, curveOffset) {
299
+ const xPos = stepIndexToX(index, GRAY_STEPS.length);
300
+ const lOff = curveOffset["gray-lightness"] ?? 0;
301
+ const sOff = curveOffset["gray-saturation"] ?? 0;
302
+ const targetL = Math.max(0, Math.min(100, sampleCurve(grayLightnessCurve, xPos) + lOff)) / 100;
303
+ const satMul = Math.max(0, Math.min(2, (sampleCurve(graySaturationCurve, xPos) + sOff) / 100));
304
+ const targetC = chroma * satMul;
305
+ const clamped = gamutClamp(targetL, targetC, hue);
306
+ return oklchToHex(clamped.l, clamped.c, clamped.h);
307
+ }
308
+ function computeDerivedColor(step, base, scaleTitle, scaleCurves, curveOffset) {
309
+ const scale = SCALES.find((s) => s.title === scaleTitle);
310
+ const idx = scale.steps.indexOf(step);
311
+ const xPos = stepIndexToX(idx, scale.steps.length);
312
+ const defs = defaultScaleCurves[scaleTitle];
313
+ const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
314
+ const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
315
+ const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
316
+ const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
317
+ const { l: baseL, c: baseC, h: baseH } = hexToOklch(base);
318
+ let targetL;
319
+ if (scale.isText) {
320
+ const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
321
+ targetL = Math.max(0, Math.min(1, baseL * lMul));
322
+ } else {
323
+ targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
324
+ }
325
+ const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
326
+ const targetC = baseC * satMul;
327
+ const clamped = gamutClamp(targetL, targetC, baseH);
328
+ return oklchToHex(clamped.l, clamped.c, clamped.h);
329
+ }
330
+ function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
331
+ if (cssNamespace === null) return null;
332
+ if (scaleTitle === "Surfaces") {
333
+ const suffix = stepName === "default" ? "" : `-${stepName}`;
334
+ return cssNamespace === "neutral" ? `--surface-neutral${suffix}` : `--surface-${cssNamespace}${suffix}`;
335
+ }
336
+ if (scaleTitle === "Borders") {
337
+ const suffix = stepName === "default" ? "" : `-${stepName}`;
338
+ return cssNamespace === "neutral" ? `--border-neutral${suffix}` : `--border-${cssNamespace}${suffix}`;
339
+ }
340
+ if (scaleTitle === "Text") {
341
+ if (cssNamespace === "neutral") return `--text-${stepName}`;
342
+ return stepName === "primary" ? `--text-${cssNamespace}` : `--text-${cssNamespace}-${stepName}`;
343
+ }
344
+ return null;
345
+ }
346
+ function derivePaletteVars(spec, config) {
347
+ const out = {};
348
+ if (!config) return out;
349
+ const baseColor = config.baseColor ?? spec.initialColor;
350
+ const overrides = config.overrides ?? {};
351
+ const curveOffset = config.curveOffset ?? {};
352
+ const scaleCurves = config.scaleCurves ?? {};
353
+ let baseForScales;
354
+ if (spec.mode === "gray") {
355
+ const grayLightnessCurve = config.grayLightnessCurve ?? DEFAULT_GRAY_LIGHTNESS();
356
+ const graySaturationCurve = config.graySaturationCurve ?? DEFAULT_GRAY_SATURATION();
357
+ const tintHue = config.tintHue ?? 240;
358
+ const tintChroma = config.tintChroma ?? DEFAULT_TINT_CHROMA;
359
+ let gray500 = "#808080";
360
+ GRAY_STEPS.forEach((step, index) => {
361
+ const k = grayStepKey(step.label);
362
+ const hex = computeGrayColor(index, tintHue, tintChroma, grayLightnessCurve, graySaturationCurve, curveOffset);
363
+ const effective = k in overrides ? overrides[k] : hex;
364
+ out[`--color-${spec.cssNamespace}-${step.label}`] = effective;
365
+ if (step.label === "500") gray500 = hex;
366
+ });
367
+ baseForScales = gray500;
368
+ } else {
369
+ const lightnessCurve = config.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS();
370
+ const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
371
+ PALETTE_STEPS.forEach((ps, index) => {
372
+ const k = paletteStepKey(ps.label);
373
+ const hex = computePaletteColor(index, baseColor, lightnessCurve, saturationCurve, curveOffset);
374
+ const effective = k in overrides ? overrides[k] : hex;
375
+ out[`--color-${spec.cssNamespace}-${ps.label}`] = effective;
376
+ });
377
+ baseForScales = baseColor;
378
+ }
379
+ for (const scale of SCALES) {
380
+ for (const step of scale.steps) {
381
+ const k = stepKey(scale.title, step.name);
382
+ const hex = k in overrides ? overrides[k] : computeDerivedColor(step, baseForScales, scale.title, scaleCurves, curveOffset);
383
+ const varName = scaleToCssVar(scale.title, step.name, spec.cssNamespace);
384
+ if (varName) out[varName] = hex;
385
+ }
386
+ }
387
+ if (spec.emptySelector) {
388
+ const emptyMode = config.emptyMode ?? "solid";
389
+ const emptyStep = config.emptyStep ?? "850";
390
+ const gradientStyle = config.gradientStyle ?? "linear";
391
+ const gradientAngle = config.gradientAngle ?? 180;
392
+ const gradientReverse = config.gradientReverse ?? false;
393
+ const gradientSize = config.gradientSize ?? "page";
394
+ const gradientStops = config.gradientStops ?? [
395
+ { position: 0, paletteLabel: "800" },
396
+ { position: 100, paletteLabel: "950" }
397
+ ];
398
+ if (emptyMode === "solid") {
399
+ const stepHex = out[`--color-${spec.cssNamespace}-${emptyStep}`];
400
+ if (stepHex) out["--page-bg"] = stepHex;
401
+ out["--page-bg-attachment"] = "scroll";
402
+ } else {
403
+ const sortedStops = [...gradientStops].sort(
404
+ (a, b) => gradientReverse ? b.position - a.position : a.position - b.position
405
+ );
406
+ const stopsCss = sortedStops.map((s) => `${out[`--color-${spec.cssNamespace}-${s.paletteLabel}`] ?? "#000000"} ${s.position}%`).join(", ");
407
+ let gradient;
408
+ switch (gradientStyle) {
409
+ case "radial":
410
+ gradient = `radial-gradient(circle, ${stopsCss})`;
411
+ break;
412
+ case "conic":
413
+ gradient = `conic-gradient(from ${gradientAngle}deg, ${stopsCss})`;
414
+ break;
415
+ default:
416
+ gradient = `linear-gradient(${gradientAngle}deg, ${stopsCss})`;
417
+ }
418
+ out["--page-bg"] = gradient;
419
+ out["--page-bg-attachment"] = gradientSize === "window" ? "fixed" : "scroll";
420
+ }
421
+ }
422
+ return out;
423
+ }
424
+ function palettesToVars(palettes) {
425
+ const out = {};
426
+ for (const spec of PALETTE_SPECS) {
427
+ Object.assign(out, derivePaletteVars(spec, palettes[spec.label]));
428
+ }
429
+ return out;
430
+ }
431
+ var HEX_RE = /^#[0-9a-f]{6}$/i;
432
+ function reconcilePalettesFromCssVars(palettes, cssVars) {
433
+ const next = structuredClone(palettes);
434
+ const consumed = /* @__PURE__ */ new Set();
435
+ const snapped = /* @__PURE__ */ new Set();
436
+ for (const spec of PALETTE_SPECS) {
437
+ const current = next[spec.label];
438
+ if (current === void 0) continue;
439
+ if (current._imported === true) {
440
+ const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
441
+ if (anchorHex && HEX_RE.test(anchorHex.trim())) {
442
+ const hex = anchorHex.trim();
443
+ if (spec.mode === "gray") {
444
+ const { c, h } = hexToOklch(hex);
445
+ next[spec.label] = { ...current, tintHue: h, tintChroma: c, _imported: false };
446
+ } else {
447
+ next[spec.label] = { ...current, baseColor: hex, _imported: false };
448
+ }
449
+ snapped.add(spec.label);
450
+ } else {
451
+ next[spec.label] = { ...current, _imported: false };
452
+ }
453
+ }
454
+ for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
455
+ consumed.add(k);
456
+ }
457
+ }
458
+ return { palettes: next, consumed, snapped };
459
+ }
460
+
57
461
  // vite-plugin/files/versionedFileResourceServer.ts
58
462
  var import_fs = __toESM(require("fs"), 1);
59
463
  var import_path = __toESM(require("path"), 1);
@@ -142,6 +546,16 @@ async function dispatch(req, res, routes) {
142
546
  return false;
143
547
  }
144
548
 
549
+ // vite-plugin/files/nameAllocator.ts
550
+ function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
551
+ if (!exists(baseName)) return baseName;
552
+ for (let i = 2; i < maxAttempts; i++) {
553
+ const candidate = `${baseName}-${i}`;
554
+ if (!exists(candidate)) return candidate;
555
+ }
556
+ throw new Error(`Could not allocate a non-colliding name for "${baseName}"`);
557
+ }
558
+
145
559
  // vite-plugin/themeFileApi.ts
146
560
  var import_node_url = require("url");
147
561
  var import_meta = {};
@@ -165,6 +579,7 @@ var PKG_VERSION = (() => {
165
579
  function themeFileApi(opts) {
166
580
  const THEMES_DIR = import_path2.default.resolve(opts.themesDir);
167
581
  const CSS_PATH = import_path2.default.resolve(opts.tokensCssPath);
582
+ const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ? import_path2.default.resolve(opts.tokensGeneratedCssPath) : import_path2.default.join(import_path2.default.dirname(CSS_PATH), "tokens.generated.css");
168
583
  const FONTS_CSS_PATH = opts.fontsCssPath ? import_path2.default.resolve(opts.fontsCssPath) : import_path2.default.join(import_path2.default.dirname(CSS_PATH), "fonts.css");
169
584
  const API_BASE = opts.apiBase ?? "/api";
170
585
  const COMPONENT_CONFIGS_DIR = opts.componentConfigsDir ? import_path2.default.resolve(opts.componentConfigsDir) : import_path2.default.resolve("component-configs");
@@ -211,6 +626,17 @@ function themeFileApi(opts) {
211
626
  res.setHeader("Content-Type", "application/json");
212
627
  res.end(JSON.stringify(data));
213
628
  }
629
+ function normalizeTheme(theme) {
630
+ if (!theme || typeof theme !== "object") return theme;
631
+ const palettes = theme.editorConfigs ?? {};
632
+ const cssVars = theme.cssVariables ?? {};
633
+ const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
634
+ const nextCssVars = {};
635
+ for (const [k, v] of Object.entries(cssVars)) {
636
+ if (!consumed.has(k)) nextCssVars[k] = v;
637
+ }
638
+ return { ...theme, editorConfigs: nextPalettes, cssVariables: nextCssVars };
639
+ }
214
640
  const SYSTEM_CASCADES_SSR = {
215
641
  "system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
216
642
  "system-ui-serif": 'Georgia, "Times New Roman", serif',
@@ -242,42 +668,78 @@ function themeFileApi(opts) {
242
668
  }
243
669
  return out;
244
670
  }
245
- function syncTokensToCss(fileName) {
246
- const themePath = import_path2.default.join(THEMES_DIR, `${fileName}.json`);
247
- if (!import_fs2.default.existsSync(themePath)) return;
248
- const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
249
- const cssVars = { ...themeData.cssVariables || {} };
250
- const resolvedFontVars = resolveFontStacks(themeData);
251
- for (const [name, value] of Object.entries(resolvedFontVars)) {
252
- cssVars[name] = value;
253
- }
254
- if (Object.keys(cssVars).length === 0) return;
255
- const cssContent = import_fs2.default.readFileSync(CSS_PATH, "utf-8");
256
- const remaining = new Set(Object.keys(cssVars));
257
- const updatedContent = cssContent.replace(
258
- /^(\s*)(--[\w-]+):\s*(.+);/gm,
259
- (_match, indent, varName, oldValue) => {
260
- if (cssVars[varName] !== void 0) {
261
- remaining.delete(varName);
262
- return `${indent}${varName}: ${cssVars[varName]};`;
671
+ function regenerateTokensCss() {
672
+ const lines = [];
673
+ lines.push("/* Generated by themeFileApi from the production theme and component configs. Do not edit. */");
674
+ lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
675
+ lines.push("");
676
+ const productionThemeName = themesResource.getProductionName();
677
+ const themePath = import_path2.default.join(THEMES_DIR, `${productionThemeName}.json`);
678
+ let themeVarCount = 0;
679
+ if (import_fs2.default.existsSync(themePath)) {
680
+ const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
681
+ const cssVars = { ...themeData.cssVariables || {} };
682
+ Object.assign(cssVars, palettesToVars(themeData.editorConfigs ?? {}));
683
+ const resolvedFontVars = resolveFontStacks(themeData);
684
+ for (const [name, value] of Object.entries(resolvedFontVars)) {
685
+ cssVars[name] = value;
686
+ }
687
+ themeVarCount = Object.keys(cssVars).length;
688
+ if (themeVarCount > 0) {
689
+ lines.push(`/* Production theme: ${productionThemeName} */`);
690
+ lines.push(":root:root {");
691
+ for (const [name, value] of Object.entries(cssVars)) {
692
+ lines.push(` ${name}: ${value};`);
263
693
  }
264
- return `${indent}${varName}: ${oldValue.trim()};`;
694
+ lines.push("}");
695
+ lines.push("");
265
696
  }
266
- );
267
- let finalContent = updatedContent;
268
- if (remaining.size > 0) {
269
- const newVars = [...remaining].map((name) => ` ${name}: ${cssVars[name]};`).join("\n");
270
- finalContent = finalContent.replace(
271
- /\n\}(\s*)$/,
272
- `
273
-
274
- /* Token additions */
275
- ${newVars}
276
- }$1`
277
- );
278
697
  }
279
- import_fs2.default.writeFileSync(CSS_PATH, finalContent);
280
- console.log(`[syncTokensToCss] Wrote ${Object.keys(cssVars).length} variables from "${fileName}" into tokens.css`);
698
+ let componentOverrideCount = 0;
699
+ if (import_fs2.default.existsSync(COMPONENT_CONFIGS_DIR)) {
700
+ const blocks = [];
701
+ for (const comp of listComponentNames()) {
702
+ const prod = componentResource(comp).getProductionName();
703
+ if (prod === "default") continue;
704
+ const prodCfg = readComponentConfig(comp, prod);
705
+ const defaultCfg = readComponentConfig(comp, "default");
706
+ if (!prodCfg || !defaultCfg) continue;
707
+ const overrides = [];
708
+ const defaultAliases = defaultCfg.aliases ?? {};
709
+ for (const [varName, semanticValue] of Object.entries(prodCfg.aliases ?? {})) {
710
+ if (!aliasValuesEqual(defaultAliases[varName], semanticValue)) {
711
+ overrides.push([varName, semanticValue]);
712
+ }
713
+ }
714
+ if (overrides.length === 0) continue;
715
+ const block = [` /* ${comp} (${prod}) */`];
716
+ for (const [varName, semanticValue] of overrides) {
717
+ block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
718
+ }
719
+ blocks.push(block);
720
+ componentOverrideCount += overrides.length;
721
+ }
722
+ if (blocks.length > 0) {
723
+ lines.push("/* Component aliases (production configs differing from defaults) */");
724
+ lines.push(":root:root {");
725
+ for (let i = 0; i < blocks.length; i++) {
726
+ if (i > 0) lines.push("");
727
+ lines.push(...blocks[i]);
728
+ }
729
+ lines.push("}");
730
+ lines.push("");
731
+ }
732
+ }
733
+ if (!import_fs2.default.existsSync(import_path2.default.dirname(GENERATED_CSS_PATH))) {
734
+ import_fs2.default.mkdirSync(import_path2.default.dirname(GENERATED_CSS_PATH), { recursive: true });
735
+ }
736
+ import_fs2.default.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
737
+ console.log(
738
+ `[regenerateTokensCss] Wrote ${import_path2.default.basename(GENERATED_CSS_PATH)} (${themeVarCount} theme vars, ${componentOverrideCount} component overrides)`
739
+ );
740
+ }
741
+ function syncTokensToCss(_fileName) {
742
+ regenerateTokensCss();
281
743
  }
282
744
  function syncFontsToCss(fileName) {
283
745
  const themePath = import_path2.default.join(THEMES_DIR, `${fileName}.json`);
@@ -289,15 +751,20 @@ ${newVars}
289
751
  lines.push("/* Generated from the production theme by syncFontsToCss. Do not edit. */");
290
752
  lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
291
753
  lines.push("");
292
- for (const source of sources) {
754
+ const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
755
+ const faceSources = sources.filter((s) => s.kind === "font-face" && s.cssText);
756
+ for (const source of urlSources) {
293
757
  const familyNames = source.families.map((f) => f.name).join(", ");
294
758
  const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
295
759
  lines.push(`/* ${label} */`);
296
- if (source.kind === "font-face") {
297
- if (source.cssText) lines.push(source.cssText);
298
- } else if (source.url) {
299
- lines.push(`@import url('${source.url}');`);
300
- }
760
+ lines.push(`@import url('${source.url}');`);
761
+ lines.push("");
762
+ }
763
+ for (const source of faceSources) {
764
+ const familyNames = source.families.map((f) => f.name).join(", ");
765
+ const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
766
+ lines.push(`/* ${label} */`);
767
+ lines.push(source.cssText);
301
768
  lines.push("");
302
769
  }
303
770
  const content = lines.join("\n");
@@ -437,6 +904,33 @@ ${newVars}
437
904
  import_fs2.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
438
905
  return true;
439
906
  }
907
+ function formatAliasGradient(v) {
908
+ const stopColor = (s) => {
909
+ const base = s.color.startsWith("--") ? `var(${s.color})` : s.color;
910
+ const opacity = s.opacity ?? 100;
911
+ return opacity >= 100 ? base : `color-mix(in srgb, ${base} ${opacity}%, transparent)`;
912
+ };
913
+ if (v.type === "none") return "transparent";
914
+ if (v.type === "solid") {
915
+ const first = v.stops[0];
916
+ if (!first) return "transparent";
917
+ return stopColor(first);
918
+ }
919
+ const stops = v.stops.map((s) => `${stopColor(s)} ${s.position}%`).join(", ");
920
+ if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
921
+ const radial = v.radius && v.radius > 0 ? `circle ${v.radius}px at center` : "circle";
922
+ return `radial-gradient(${radial}, ${stops})`;
923
+ }
924
+ function aliasValueToCss(v) {
925
+ if (typeof v === "string") return v.startsWith("--") ? `var(${v})` : v;
926
+ return formatAliasGradient(v.value);
927
+ }
928
+ function aliasValuesEqual(a, b) {
929
+ if (a === void 0 || b === void 0) return a === b;
930
+ if (typeof a === "string" && typeof b === "string") return a === b;
931
+ if (typeof a !== typeof b) return false;
932
+ return JSON.stringify(a) === JSON.stringify(b);
933
+ }
440
934
  function readComponentConfig(comp, name) {
441
935
  const filePath = componentResource(comp).filePath(name);
442
936
  if (!import_fs2.default.existsSync(filePath)) return null;
@@ -446,54 +940,8 @@ ${newVars}
446
940
  return null;
447
941
  }
448
942
  }
449
- const COMPONENT_OVERRIDES_START = "/* component-aliases:start */";
450
- const COMPONENT_OVERRIDES_END = "/* component-aliases:end */";
451
943
  function syncComponentsToCss() {
452
- if (!import_fs2.default.existsSync(CSS_PATH)) return;
453
- if (!import_fs2.default.existsSync(COMPONENT_CONFIGS_DIR)) return;
454
- const lines = [];
455
- const components = listComponentNames();
456
- for (const comp of components) {
457
- const prod = componentResource(comp).getProductionName();
458
- if (prod === "default") continue;
459
- const prodCfg = readComponentConfig(comp, prod);
460
- const defaultCfg = readComponentConfig(comp, "default");
461
- if (!prodCfg || !defaultCfg) continue;
462
- const overrides = [];
463
- for (const [varName, semanticName] of Object.entries(prodCfg.aliases ?? {})) {
464
- if ((defaultCfg.aliases ?? {})[varName] !== semanticName) {
465
- overrides.push([varName, semanticName]);
466
- }
467
- }
468
- if (overrides.length === 0) continue;
469
- lines.push(` /* ${comp} (${prod}) */`);
470
- for (const [varName, semanticName] of overrides) {
471
- lines.push(` ${varName}: var(${semanticName});`);
472
- }
473
- }
474
- const block = lines.length > 0 ? `
475
-
476
- ${COMPONENT_OVERRIDES_START}
477
- :root:root {
478
- ${lines.join("\n")}
479
- }
480
- ${COMPONENT_OVERRIDES_END}
481
- ` : "";
482
- let cssContent = import_fs2.default.readFileSync(CSS_PATH, "utf-8");
483
- const startIdx = cssContent.indexOf(COMPONENT_OVERRIDES_START);
484
- const endIdx = cssContent.indexOf(COMPONENT_OVERRIDES_END);
485
- if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
486
- let stripStart = startIdx;
487
- while (stripStart > 0 && cssContent[stripStart - 1] === "\n") stripStart--;
488
- const after = cssContent.slice(endIdx + COMPONENT_OVERRIDES_END.length);
489
- cssContent = cssContent.slice(0, stripStart) + (block || "\n") + after.replace(/^\n+/, "");
490
- } else if (block) {
491
- cssContent = cssContent.replace(/\n*$/, "") + block;
492
- }
493
- import_fs2.default.writeFileSync(CSS_PATH, cssContent);
494
- console.log(
495
- `[syncComponentsToCss] Wrote ${lines.filter((l) => !l.trim().startsWith("/*")).length} alias override(s) to tokens.css`
496
- );
944
+ regenerateTokensCss();
497
945
  }
498
946
  const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
499
947
  const THEMES_ROUTE = `${API_BASE}/themes`;
@@ -508,6 +956,8 @@ ${COMPONENT_OVERRIDES_END}
508
956
  const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
509
957
  const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
510
958
  const MANIFEST_APPLY_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/apply$`);
959
+ const MANIFEST_EXPORT_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/export$`);
960
+ const MANIFEST_IMPORT_ROUTE = `${API_BASE}/manifests/import`;
511
961
  const MANIFEST_BY_NAME_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)$`);
512
962
  async function handleListThemes(_ctx) {
513
963
  const activeFile = themesResource.getActiveName();
@@ -531,7 +981,7 @@ ${COMPONENT_OVERRIDES_END}
531
981
  jsonResponse(res, 404, { error: "Active theme not found" });
532
982
  return;
533
983
  }
534
- const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
984
+ const data = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8")));
535
985
  data._fileName = activeFile;
536
986
  jsonResponse(res, 200, data);
537
987
  }
@@ -552,7 +1002,7 @@ ${COMPONENT_OVERRIDES_END}
552
1002
  jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
553
1003
  return;
554
1004
  }
555
- const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
1005
+ const data = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8")));
556
1006
  jsonResponse(res, 200, {
557
1007
  fileName: prodFile,
558
1008
  name: data.name || prodFile,
@@ -595,7 +1045,7 @@ ${COMPONENT_OVERRIDES_END}
595
1045
  jsonResponse(res, 404, { error: "Not found" });
596
1046
  return;
597
1047
  }
598
- const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
1048
+ const data = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8")));
599
1049
  data._fileName = fileName;
600
1050
  jsonResponse(res, 200, data);
601
1051
  return;
@@ -915,11 +1365,15 @@ ${COMPONENT_OVERRIDES_END}
915
1365
  apply.push([comp, sanitized]);
916
1366
  }
917
1367
  themesResource.setActiveName(themeName);
918
- const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
1368
+ themesResource.setProductionName(themeName);
1369
+ syncTokensToCss(themeName);
1370
+ syncFontsToCss(themeName);
1371
+ const themeData = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8")));
919
1372
  themeData._fileName = themeName;
920
1373
  for (const [comp, configFile] of apply) {
921
1374
  const r = componentResource(comp);
922
1375
  r.setActiveName(configFile);
1376
+ r.setProductionName(configFile);
923
1377
  const cfg = readComponentConfig(comp, configFile);
924
1378
  if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
925
1379
  }
@@ -929,6 +1383,7 @@ ${COMPONENT_OVERRIDES_END}
929
1383
  const cfg = readComponentConfig(comp, activeName);
930
1384
  if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
931
1385
  }
1386
+ syncComponentsToCss();
932
1387
  manifestsResource.setActiveName(fileName);
933
1388
  jsonResponse(res, 200, {
934
1389
  ok: true,
@@ -937,6 +1392,157 @@ ${COMPONENT_OVERRIDES_END}
937
1392
  componentConfigs: resolvedConfigs
938
1393
  });
939
1394
  }
1395
+ async function handleExportManifest({ params, res }) {
1396
+ const [fileName] = params;
1397
+ const manifestPath = manifestsResource.filePath(fileName);
1398
+ if (!import_fs2.default.existsSync(manifestPath)) {
1399
+ jsonResponse(res, 404, { error: "Manifest not found" });
1400
+ return;
1401
+ }
1402
+ const manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
1403
+ const themeName = sanitizeFileName(manifest.theme || "default");
1404
+ const themePath = themesResource.filePath(themeName);
1405
+ if (!import_fs2.default.existsSync(themePath)) {
1406
+ jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
1407
+ return;
1408
+ }
1409
+ const theme = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
1410
+ const knownComponents = new Set(listComponentNames());
1411
+ const componentConfigs = {};
1412
+ for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
1413
+ if (!knownComponents.has(comp)) continue;
1414
+ const sanitized = sanitizeFileName(String(configFile) || "default");
1415
+ if (sanitized === "default") continue;
1416
+ const cfgPath = componentResource(comp).filePath(sanitized);
1417
+ if (!import_fs2.default.existsSync(cfgPath)) {
1418
+ jsonResponse(res, 422, {
1419
+ error: `Manifest references missing config: ${comp}/${sanitized}`
1420
+ });
1421
+ return;
1422
+ }
1423
+ const cfg = JSON.parse(import_fs2.default.readFileSync(cfgPath, "utf-8"));
1424
+ componentConfigs[`${comp}/${sanitized}`] = cfg;
1425
+ }
1426
+ const bundle = {
1427
+ kind: "manifest-bundle",
1428
+ schemaVersion: 1,
1429
+ liveTokensVersion: PKG_VERSION,
1430
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1431
+ manifest,
1432
+ theme,
1433
+ componentConfigs
1434
+ };
1435
+ res.statusCode = 200;
1436
+ res.setHeader("Content-Type", "application/json");
1437
+ res.setHeader(
1438
+ "Content-Disposition",
1439
+ `attachment; filename="${fileName}.bundle.json"`
1440
+ );
1441
+ res.end(JSON.stringify(bundle, null, 2));
1442
+ }
1443
+ function nextAvailableName2(resourceFilePath, baseName) {
1444
+ return nextAvailableName(
1445
+ (n) => import_fs2.default.existsSync(resourceFilePath(n)),
1446
+ sanitizeFileName(baseName)
1447
+ );
1448
+ }
1449
+ async function handleImportManifest({ req, res }) {
1450
+ let bundle;
1451
+ try {
1452
+ bundle = JSON.parse(await readBody(req));
1453
+ } catch {
1454
+ jsonResponse(res, 400, { error: "Body is not valid JSON" });
1455
+ return;
1456
+ }
1457
+ if (!bundle || bundle.kind !== "manifest-bundle") {
1458
+ jsonResponse(res, 400, {
1459
+ error: "Not a manifest bundle (kind discriminator missing or wrong)"
1460
+ });
1461
+ return;
1462
+ }
1463
+ if (bundle.schemaVersion !== 1) {
1464
+ jsonResponse(res, 400, {
1465
+ error: `Unsupported bundle schemaVersion: ${bundle.schemaVersion}`
1466
+ });
1467
+ return;
1468
+ }
1469
+ if (!bundle.manifest || !bundle.theme || !bundle.componentConfigs) {
1470
+ jsonResponse(res, 400, { error: "Bundle missing manifest / theme / componentConfigs" });
1471
+ return;
1472
+ }
1473
+ const renames = {};
1474
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1475
+ const originalThemeName = sanitizeFileName(bundle.manifest.theme || "default");
1476
+ const finalThemeName = nextAvailableName2(
1477
+ (n) => themesResource.filePath(n),
1478
+ originalThemeName
1479
+ );
1480
+ if (finalThemeName !== originalThemeName) {
1481
+ renames[`theme:${originalThemeName}`] = finalThemeName;
1482
+ }
1483
+ const themeBody = { ...bundle.theme };
1484
+ themeBody.updatedAt = now;
1485
+ if (!themeBody.createdAt) themeBody.createdAt = now;
1486
+ themesResource.ensureDir();
1487
+ import_fs2.default.writeFileSync(themesResource.filePath(finalThemeName), JSON.stringify(themeBody, null, 2));
1488
+ const knownComponents = new Set(listComponentNames());
1489
+ const componentRenames = {};
1490
+ for (const [key, cfgValue] of Object.entries(bundle.componentConfigs)) {
1491
+ const [comp, originalName] = key.split("/");
1492
+ if (!comp || !originalName) continue;
1493
+ if (!knownComponents.has(comp)) continue;
1494
+ const r = componentResource(comp);
1495
+ const finalName = nextAvailableName2(
1496
+ (n) => r.filePath(n),
1497
+ originalName
1498
+ );
1499
+ if (finalName !== originalName) {
1500
+ renames[`componentConfig:${comp}/${originalName}`] = finalName;
1501
+ }
1502
+ componentRenames[`${comp}/${originalName}`] = finalName;
1503
+ const cfgBody = { ...cfgValue };
1504
+ cfgBody.component = comp;
1505
+ cfgBody.name = finalName;
1506
+ cfgBody.updatedAt = now;
1507
+ if (!cfgBody.createdAt) cfgBody.createdAt = now;
1508
+ r.ensureDir();
1509
+ import_fs2.default.writeFileSync(r.filePath(finalName), JSON.stringify(cfgBody, null, 2));
1510
+ }
1511
+ const rewrittenManifest = {
1512
+ ...bundle.manifest,
1513
+ theme: finalThemeName,
1514
+ componentConfigs: {}
1515
+ };
1516
+ for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
1517
+ const original = String(configName);
1518
+ if (original === "default") {
1519
+ rewrittenManifest.componentConfigs[comp] = "default";
1520
+ continue;
1521
+ }
1522
+ const finalName = componentRenames[`${comp}/${original}`] ?? original;
1523
+ rewrittenManifest.componentConfigs[comp] = finalName;
1524
+ }
1525
+ rewrittenManifest.updatedAt = now;
1526
+ if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
1527
+ const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
1528
+ const finalManifestName = nextAvailableName2(
1529
+ (n) => manifestsResource.filePath(n),
1530
+ originalManifestName
1531
+ );
1532
+ if (finalManifestName !== originalManifestName) {
1533
+ renames[`manifest:${originalManifestName}`] = finalManifestName;
1534
+ }
1535
+ manifestsResource.ensureDir();
1536
+ import_fs2.default.writeFileSync(
1537
+ manifestsResource.filePath(finalManifestName),
1538
+ JSON.stringify(rewrittenManifest, null, 2)
1539
+ );
1540
+ jsonResponse(res, 200, {
1541
+ ok: true,
1542
+ manifest: finalManifestName,
1543
+ renames
1544
+ });
1545
+ }
940
1546
  function methodNotAllowed({ res }) {
941
1547
  jsonResponse(res, 405, { error: "Method not allowed" });
942
1548
  }
@@ -974,11 +1580,21 @@ ${COMPONENT_OVERRIDES_END}
974
1580
  { method: "GET", pattern: MANIFESTS_ROUTE, handler: handleListManifests },
975
1581
  { method: "GET", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleGetActiveManifest },
976
1582
  { method: "PUT", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleSetActiveManifest },
1583
+ // Manifests — exact import route runs before :name regexes
1584
+ { method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
1585
+ { method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
1586
+ { method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
1587
+ { method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
977
1588
  // Manifests — :name/apply (more specific than :name)
978
1589
  { method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
979
1590
  { method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
980
1591
  { method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
981
1592
  { method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
1593
+ // Manifests — :name/export (more specific than :name)
1594
+ { method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
1595
+ { method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
1596
+ { method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
1597
+ { method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
982
1598
  // Manifests — :name CRUD (broadest manifest route, runs last)
983
1599
  { method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
984
1600
  { method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
@@ -998,6 +1614,7 @@ ${COMPONENT_OVERRIDES_END}
998
1614
  ensureThemesDir();
999
1615
  ensureComponentConfigsDir();
1000
1616
  ensureManifestsDir();
1617
+ regenerateTokensCss();
1001
1618
  server.middlewares.use(async (req, res, next) => {
1002
1619
  const handled = await dispatch(req, res, routes);
1003
1620
  if (!handled) next();