@motion-proto/live-tokens 0.6.2 → 0.8.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 (232) hide show
  1. package/README.md +14 -13
  2. package/dist-plugin/index.cjs +854 -226
  3. package/dist-plugin/index.d.cts +2 -1
  4. package/dist-plugin/index.d.ts +2 -1
  5. package/dist-plugin/index.js +852 -225
  6. package/package.json +26 -40
  7. package/src/{styles → app}/site.css +1 -1
  8. package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
  9. package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
  10. package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
  11. package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +37 -30
  12. package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
  13. package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
  14. package/src/editor/component-editor/ImageEditor.svelte +30 -0
  15. package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
  16. package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
  17. package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +67 -38
  18. package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
  19. package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
  20. package/src/editor/component-editor/SectionDividerEditor.svelte +565 -0
  21. package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
  22. package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +29 -21
  23. package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
  24. package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
  25. package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
  26. package/src/editor/component-editor/editors.d.ts +10 -0
  27. package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
  28. package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +54 -15
  29. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
  30. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +151 -424
  31. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
  32. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
  33. package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
  34. package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
  35. package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
  36. package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -12
  37. package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
  38. package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
  39. package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
  40. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +85 -0
  41. package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
  42. package/src/editor/component-editor/scaffolding/StateBlock.svelte +345 -0
  43. package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +17 -12
  44. package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +13 -1
  45. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +858 -0
  46. package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +1 -0
  47. package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
  48. package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
  49. package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +25 -0
  50. package/src/{lib → editor/core/components}/componentConfigKeys.ts +8 -0
  51. package/src/{lib → editor/core/components}/componentConfigService.ts +3 -3
  52. package/src/{lib → editor/core/components}/componentPersist.ts +11 -9
  53. package/src/editor/core/flashStatus.ts +30 -0
  54. package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
  55. package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
  56. package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
  57. package/src/editor/core/manifests/manifestService.ts +171 -0
  58. package/src/editor/core/palettes/familySwap.ts +99 -0
  59. package/src/{lib → editor/core/palettes}/paletteDerivation.ts +71 -2
  60. package/src/{lib → editor/core/palettes}/tokenRegistry.ts +9 -6
  61. package/src/editor/core/productionPulse.ts +37 -0
  62. package/src/{lib → editor/core/routing}/router.ts +1 -1
  63. package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
  64. package/src/{lib → editor/core/store}/editorCore.ts +24 -8
  65. package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
  66. package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
  67. package/src/{lib → editor/core/store}/editorStore.ts +222 -28
  68. package/src/{lib → editor/core/store}/editorTypes.ts +56 -13
  69. package/src/editor/core/store/gradientSource.ts +192 -0
  70. package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
  71. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
  72. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
  73. package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
  74. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
  75. package/src/{lib → editor/core/themes}/migrations/index.ts +10 -0
  76. package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
  77. package/src/{lib → editor/core/themes}/slices/components.ts +20 -6
  78. package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
  79. package/src/{lib → editor/core/themes}/slices/gradients.ts +89 -14
  80. package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
  81. package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
  82. package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
  83. package/src/{lib → editor/core/themes}/themeInit.ts +8 -8
  84. package/src/{lib → editor/core/themes}/themeService.ts +6 -6
  85. package/src/{lib → editor/core/themes}/themeTypes.ts +67 -8
  86. package/src/editor/index.ts +69 -0
  87. package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -1
  88. package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +80 -129
  89. package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
  90. package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
  91. package/src/{pages → editor/pages}/Editor.svelte +4 -4
  92. package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
  93. package/src/{styles → editor/styles}/ui-editor.css +43 -22
  94. package/src/{styles → editor/styles}/ui-form-controls.css +23 -24
  95. package/src/{ui → editor/ui}/BezierCurveEditor.svelte +119 -68
  96. package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
  97. package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +7 -6
  98. package/src/editor/ui/FileLoadList.svelte +367 -0
  99. package/src/editor/ui/FilePill.svelte +80 -0
  100. package/src/editor/ui/FontStackEditor.svelte +499 -0
  101. package/src/editor/ui/GradientEditor.svelte +690 -0
  102. package/src/{ui → editor/ui}/GradientStopPicker.svelte +12 -4
  103. package/src/editor/ui/ManifestFileManager.svelte +438 -0
  104. package/src/{ui → editor/ui}/PaletteEditor.svelte +180 -673
  105. package/src/editor/ui/ProjectFontsSection.svelte +638 -0
  106. package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
  107. package/src/{ui → editor/ui}/TextTab.svelte +3 -3
  108. package/src/editor/ui/ThemeFileManager.svelte +783 -0
  109. package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
  110. package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -7
  111. package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +4 -1
  112. package/src/editor/ui/UIInfoPopover.svelte +243 -0
  113. package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
  114. package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
  115. package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
  116. package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
  117. package/src/{ui → editor/ui}/UIPaletteSelector.svelte +57 -30
  118. package/src/editor/ui/UIPillButton.svelte +168 -0
  119. package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
  120. package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
  121. package/src/editor/ui/UISegmentedControl.svelte +114 -0
  122. package/src/editor/ui/UISquareButton.svelte +172 -0
  123. package/src/{ui → editor/ui}/UITokenSelector.svelte +14 -11
  124. package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
  125. package/src/{ui → editor/ui}/VariablesTab.svelte +46 -17
  126. package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
  127. package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +24 -47
  128. package/src/{ui → editor/ui}/palette/PaletteBase.svelte +11 -8
  129. package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
  130. package/src/editor/ui/palette/paletteMath.ts +275 -0
  131. package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -18
  132. package/src/{ui → editor/ui}/sections/GradientsSection.svelte +8 -8
  133. package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +18 -18
  134. package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +23 -23
  135. package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
  136. package/src/{components → system/components}/Badge.svelte +0 -36
  137. package/src/{components → system/components}/Button.svelte +2 -2
  138. package/src/{components → system/components}/Card.svelte +34 -60
  139. package/src/{components → system/components}/CollapsibleSection.svelte +25 -2
  140. package/src/{components → system/components}/CornerBadge.svelte +8 -24
  141. package/src/{components → system/components}/Dialog.svelte +1 -1
  142. package/src/system/components/FloatingTokenTags.css +275 -0
  143. package/src/system/components/FloatingTokenTags.svelte +543 -0
  144. package/src/{components → system/components}/InlineEditActions.svelte +6 -4
  145. package/src/system/components/MenuSelect.svelte +229 -0
  146. package/src/{components → system/components}/Notification.svelte +8 -1
  147. package/src/{components → system/components}/ProgressBar.svelte +29 -11
  148. package/src/system/components/SectionDivider.svelte +560 -0
  149. package/src/{components → system/components}/SegmentedControl.svelte +49 -43
  150. package/src/{components → system/components}/TabBar.svelte +81 -65
  151. package/src/{components → system/components}/Table.svelte +17 -3
  152. package/src/{components → system/components}/Tooltip.svelte +6 -4
  153. package/src/system/styles/CONVENTIONS.md +178 -0
  154. package/src/system/styles/fonts.css +20 -0
  155. package/src/system/styles/tokens.css +601 -0
  156. package/src/system/styles/tokens.generated.css +544 -0
  157. package/src/component-editor/ImageEditor.svelte +0 -74
  158. package/src/component-editor/SectionDividerEditor.svelte +0 -265
  159. package/src/component-editor/scaffolding/DividerEditor.svelte +0 -94
  160. package/src/component-editor/scaffolding/GradientCard.svelte +0 -296
  161. package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
  162. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
  163. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
  164. package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
  165. package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
  166. package/src/components/SectionDivider.svelte +0 -483
  167. package/src/data/google-fonts.json +0 -75
  168. package/src/lib/index.ts +0 -68
  169. package/src/lib/presetService.ts +0 -214
  170. package/src/lib/productionPulse.ts +0 -32
  171. package/src/styles/fonts.css +0 -30
  172. package/src/styles/tokens.css +0 -1324
  173. package/src/ui/FontStackEditor.svelte +0 -361
  174. package/src/ui/GradientEditor.svelte +0 -470
  175. package/src/ui/PresetFileManager.svelte +0 -1116
  176. package/src/ui/ProjectFontsSection.svelte +0 -645
  177. package/src/ui/ThemeFileManager.svelte +0 -1020
  178. package/src/ui/UnsavedComponentsDialog.svelte +0 -315
  179. /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
  180. /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
  181. /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
  182. /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
  183. /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
  184. /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
  185. /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
  186. /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
  187. /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
  188. /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
  189. /package/src/{lib → editor/core/storage}/storage.ts +0 -0
  190. /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
  191. /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
  192. /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
  193. /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
  194. /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
  195. /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
  196. /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
  197. /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
  198. /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
  199. /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
  200. /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
  201. /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
  202. /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
  203. /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
  204. /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
  205. /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
  206. /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
  207. /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
  208. /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
  209. /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
  210. /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
  211. /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
  212. /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
  213. /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
  214. /package/src/{ui → editor/ui}/index.ts +0 -0
  215. /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
  216. /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
  217. /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
  218. /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
  219. /package/src/{ui → editor/ui}/variantScales.ts +0 -0
  220. /package/src/{assets → system/assets}/newspaper.webp +0 -0
  221. /package/src/{assets → system/assets}/offering.webp +0 -0
  222. /package/src/{components → system/components}/Callout.svelte +0 -0
  223. /package/src/{components → system/components}/Image.svelte +0 -0
  224. /package/src/{components → system/components}/RadioButton.svelte +0 -0
  225. /package/src/{components → system/components}/types.ts +0 -0
  226. /package/src/{styles → system/styles}/_padding.scss +0 -0
  227. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  228. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  229. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  230. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  231. /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  232. /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
@@ -1,8 +1,8 @@
1
- // src/vite-plugin/themeFileApi.ts
1
+ // vite-plugin/themeFileApi.ts
2
2
  import fs2 from "fs";
3
3
  import path2 from "path";
4
4
 
5
- // src/lib/parsers/globalRootBlock.ts
5
+ // src/editor/core/themes/parsers/globalRootBlock.ts
6
6
  function extractGlobalRootBody(source) {
7
7
  const re = /:global\(:root\)\s*\{([^}]*)\}/g;
8
8
  const bodies = [];
@@ -13,12 +13,416 @@ function extractGlobalRootBody(source) {
13
13
  return bodies.join("\n");
14
14
  }
15
15
 
16
- // src/lib/files/versionedFileResource.ts
16
+ // src/editor/core/storage/files/versionedFileResourceClient.ts
17
17
  function sanitizeFileName(name) {
18
18
  return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
19
19
  }
20
20
 
21
- // src/vite-plugin/files/versionedFileResource.ts
21
+ // src/editor/core/palettes/oklch.ts
22
+ function srgbToLinear(c) {
23
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
24
+ }
25
+ function linearToSrgb(c) {
26
+ return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
27
+ }
28
+ function hexToLinearRgb(hex) {
29
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
30
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
31
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
32
+ return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
33
+ }
34
+ function linearRgbToHex(r, g, b) {
35
+ const toHex = (c) => {
36
+ const v = Math.round(Math.max(0, Math.min(1, linearToSrgb(c))) * 255);
37
+ return v.toString(16).padStart(2, "0");
38
+ };
39
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
40
+ }
41
+ function linearRgbToOklab(r, g, b) {
42
+ const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
43
+ const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
44
+ const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
45
+ const l = Math.cbrt(l_);
46
+ const m = Math.cbrt(m_);
47
+ const s = Math.cbrt(s_);
48
+ return [
49
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
50
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
51
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
52
+ ];
53
+ }
54
+ function oklabToLinearRgb(L, a, b) {
55
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
56
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
57
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
58
+ const l = l_ * l_ * l_;
59
+ const m = m_ * m_ * m_;
60
+ const s = s_ * s_ * s_;
61
+ return [
62
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
63
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
64
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
65
+ ];
66
+ }
67
+ function oklabToOklch(L, a, b) {
68
+ const c = Math.sqrt(a * a + b * b);
69
+ let h = Math.atan2(b, a) * 180 / Math.PI;
70
+ if (h < 0) h += 360;
71
+ return { l: L, c, h };
72
+ }
73
+ function oklchToOklab(l, c, h) {
74
+ const hRad = h * Math.PI / 180;
75
+ return [l, c * Math.cos(hRad), c * Math.sin(hRad)];
76
+ }
77
+ function hexToOklch(hex) {
78
+ const [r, g, b] = hexToLinearRgb(hex);
79
+ const [L, a, bVal] = linearRgbToOklab(r, g, b);
80
+ return oklabToOklch(L, a, bVal);
81
+ }
82
+ function oklchToHex(l, c, h) {
83
+ const [L, a, b] = oklchToOklab(l, c, h);
84
+ const [r, g, bVal] = oklabToLinearRgb(L, a, b);
85
+ return linearRgbToHex(r, g, bVal);
86
+ }
87
+ function isInGamut(r, g, b) {
88
+ const eps = 1e-4;
89
+ return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;
90
+ }
91
+ function gamutClamp(l, c, h) {
92
+ if (l <= 0) return { l: 0, c: 0, h };
93
+ if (l >= 1) return { l: 1, c: 0, h };
94
+ const [L, a, b] = oklchToOklab(l, c, h);
95
+ const [r, g, bVal] = oklabToLinearRgb(L, a, b);
96
+ if (isInGamut(r, g, bVal)) return { l, c, h };
97
+ let lo = 0;
98
+ let hi = c;
99
+ for (let i = 0; i < 20; i++) {
100
+ const mid = (lo + hi) / 2;
101
+ const [La, aa, ba] = oklchToOklab(l, mid, h);
102
+ const [rr, gg, bb] = oklabToLinearRgb(La, aa, ba);
103
+ if (isInGamut(rr, gg, bb)) {
104
+ lo = mid;
105
+ } else {
106
+ hi = mid;
107
+ }
108
+ }
109
+ return { l, c: lo, h };
110
+ }
111
+
112
+ // src/editor/ui/curveEngine.ts
113
+ function makeAnchor(x, y, tangentLen = 15) {
114
+ return { x, y, inDx: -tangentLen, inDy: 0, outDx: tangentLen, outDy: 0 };
115
+ }
116
+ function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
117
+ const u = 1 - t, u2 = u * u, u3 = u2 * u;
118
+ const t2 = t * t, t3 = t2 * t;
119
+ return {
120
+ x: u3 * p0x + 3 * u2 * t * c0x + 3 * u * t2 * c1x + t3 * p1x,
121
+ y: u3 * p0y + 3 * u2 * t * c0y + 3 * u * t2 * c1y + t3 * p1y
122
+ };
123
+ }
124
+ function sampleCurve(anchors, xPos) {
125
+ if (anchors.length === 0) return 0;
126
+ if (anchors.length === 1) return anchors[0].y;
127
+ if (xPos <= anchors[0].x) return anchors[0].y;
128
+ if (xPos >= anchors[anchors.length - 1].x) return anchors[anchors.length - 1].y;
129
+ let seg = 0;
130
+ while (seg < anchors.length - 2 && anchors[seg + 1].x < xPos) seg++;
131
+ const a0 = anchors[seg], a1 = anchors[seg + 1];
132
+ const p0x = a0.x, p0y = a0.y;
133
+ const c0x = a0.x + a0.outDx, c0y = a0.y + a0.outDy;
134
+ const c1x = a1.x + a1.inDx, c1y = a1.y + a1.inDy;
135
+ const p1x = a1.x, p1y = a1.y;
136
+ let lo = 0, hi = 1;
137
+ for (let i = 0; i < 20; i++) {
138
+ const mid = (lo + hi) / 2;
139
+ const pt = evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, mid);
140
+ if (pt.x < xPos) lo = mid;
141
+ else hi = mid;
142
+ }
143
+ return evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, (lo + hi) / 2).y;
144
+ }
145
+
146
+ // src/editor/core/palettes/paletteDerivation.ts
147
+ var PALETTE_SPECS = [
148
+ { label: "Neutral", cssNamespace: "neutral", mode: "gray", initialColor: "#808080" },
149
+ { label: "Alternate", cssNamespace: "alternate", mode: "gray", initialColor: "#808080" },
150
+ { label: "Background", cssNamespace: "canvas", mode: "chromatic", emptySelector: true, initialColor: "#1a1a2e" },
151
+ { label: "Brand", cssNamespace: "brand", mode: "chromatic", initialColor: "#c93636" },
152
+ { label: "Accent", cssNamespace: "accent", mode: "chromatic", initialColor: "#f49e0b" },
153
+ { label: "Special", cssNamespace: "special", mode: "chromatic", initialColor: "#8b5cf6" },
154
+ { label: "Info", cssNamespace: "info", mode: "chromatic", initialColor: "#3077e8" },
155
+ { label: "Success", cssNamespace: "success", mode: "chromatic", initialColor: "#21c45d" },
156
+ { label: "Warning", cssNamespace: "warning", mode: "chromatic", initialColor: "#e66e1a" },
157
+ { label: "Danger", cssNamespace: "danger", mode: "chromatic", initialColor: "#e8304f" }
158
+ ];
159
+ var PALETTE_STEPS = [
160
+ { label: "100" },
161
+ { label: "200" },
162
+ { label: "300" },
163
+ { label: "400" },
164
+ { label: "500" },
165
+ { label: "600" },
166
+ { label: "700" },
167
+ { label: "800" },
168
+ { label: "850" },
169
+ { label: "900" },
170
+ { label: "950" }
171
+ ];
172
+ var GRAY_STEPS = [
173
+ { label: "100" },
174
+ { label: "200" },
175
+ { label: "300" },
176
+ { label: "400" },
177
+ { label: "500" },
178
+ { label: "600" },
179
+ { label: "700" },
180
+ { label: "800" },
181
+ { label: "850" },
182
+ { label: "900" },
183
+ { label: "950" }
184
+ ];
185
+ var SCALES = [
186
+ {
187
+ title: "Surfaces",
188
+ isText: false,
189
+ steps: [
190
+ { name: "lowest", position: -1 },
191
+ { name: "lower", position: -2 / 3 },
192
+ { name: "low", position: -1 / 3 },
193
+ { name: "default", position: 0 },
194
+ { name: "high", position: 1 / 3 },
195
+ { name: "higher", position: 2 / 3 },
196
+ { name: "highest", position: 1 }
197
+ ]
198
+ },
199
+ {
200
+ title: "Borders",
201
+ isText: false,
202
+ steps: [
203
+ { name: "faint", position: -1 },
204
+ { name: "subtle", position: -0.5 },
205
+ { name: "default", position: 0 },
206
+ { name: "medium", position: 0.5 },
207
+ { name: "strong", position: 1 }
208
+ ]
209
+ },
210
+ {
211
+ title: "Text",
212
+ isText: true,
213
+ steps: [
214
+ { name: "primary", position: 0 },
215
+ { name: "secondary", position: 0 },
216
+ { name: "tertiary", position: 0 },
217
+ { name: "muted", position: 0 },
218
+ { name: "disabled", position: 0 }
219
+ ]
220
+ }
221
+ ];
222
+ var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
223
+ var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
224
+ var DEFAULT_GRAY_LIGHTNESS = () => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
225
+ var DEFAULT_GRAY_SATURATION = () => [makeAnchor(0, 20, 30), makeAnchor(100, 20, 30)];
226
+ var DEFAULT_TINT_CHROMA = 0.04;
227
+ var defaultScaleCurves = {
228
+ Surfaces: {
229
+ lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
230
+ saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
231
+ },
232
+ Borders: {
233
+ lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
234
+ saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
235
+ },
236
+ Text: {
237
+ lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
238
+ saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
239
+ }
240
+ };
241
+ function paletteStepKey(label) {
242
+ return `Palette-${label}`;
243
+ }
244
+ function grayStepKey(label) {
245
+ return `gray-${label}`;
246
+ }
247
+ function stepKey(scaleTitle, stepName) {
248
+ return `${scaleTitle}-${stepName}`;
249
+ }
250
+ function stepIndexToX(index, total) {
251
+ return total > 1 ? index / (total - 1) * 100 : 50;
252
+ }
253
+ function computePaletteColor(index, base, lightnessCurve, saturationCurve, curveOffset) {
254
+ const { c: baseC, h } = hexToOklch(base);
255
+ const xPos = stepIndexToX(index, PALETTE_STEPS.length);
256
+ const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
257
+ const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
258
+ const targetC = baseC * satMul;
259
+ const clamped = gamutClamp(targetL, targetC, h);
260
+ return oklchToHex(clamped.l, clamped.c, clamped.h);
261
+ }
262
+ function computeGrayColor(index, hue, chroma, grayLightnessCurve, graySaturationCurve, curveOffset) {
263
+ const xPos = stepIndexToX(index, GRAY_STEPS.length);
264
+ const lOff = curveOffset["gray-lightness"] ?? 0;
265
+ const sOff = curveOffset["gray-saturation"] ?? 0;
266
+ const targetL = Math.max(0, Math.min(100, sampleCurve(grayLightnessCurve, xPos) + lOff)) / 100;
267
+ const satMul = Math.max(0, Math.min(2, (sampleCurve(graySaturationCurve, xPos) + sOff) / 100));
268
+ const targetC = chroma * satMul;
269
+ const clamped = gamutClamp(targetL, targetC, hue);
270
+ return oklchToHex(clamped.l, clamped.c, clamped.h);
271
+ }
272
+ function computeDerivedColor(step, base, scaleTitle, scaleCurves, curveOffset) {
273
+ const scale = SCALES.find((s) => s.title === scaleTitle);
274
+ const idx = scale.steps.indexOf(step);
275
+ const xPos = stepIndexToX(idx, scale.steps.length);
276
+ const defs = defaultScaleCurves[scaleTitle];
277
+ const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
278
+ const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
279
+ const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
280
+ const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
281
+ const { l: baseL, c: baseC, h: baseH } = hexToOklch(base);
282
+ let targetL;
283
+ if (scale.isText) {
284
+ const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
285
+ targetL = Math.max(0, Math.min(1, baseL * lMul));
286
+ } else {
287
+ targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
288
+ }
289
+ const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
290
+ const targetC = baseC * satMul;
291
+ const clamped = gamutClamp(targetL, targetC, baseH);
292
+ return oklchToHex(clamped.l, clamped.c, clamped.h);
293
+ }
294
+ function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
295
+ if (cssNamespace === null) return null;
296
+ if (scaleTitle === "Surfaces") {
297
+ const suffix = stepName === "default" ? "" : `-${stepName}`;
298
+ return cssNamespace === "neutral" ? `--surface-neutral${suffix}` : `--surface-${cssNamespace}${suffix}`;
299
+ }
300
+ if (scaleTitle === "Borders") {
301
+ const suffix = stepName === "default" ? "" : `-${stepName}`;
302
+ return cssNamespace === "neutral" ? `--border-neutral${suffix}` : `--border-${cssNamespace}${suffix}`;
303
+ }
304
+ if (scaleTitle === "Text") {
305
+ if (cssNamespace === "neutral") return `--text-${stepName}`;
306
+ return stepName === "primary" ? `--text-${cssNamespace}` : `--text-${cssNamespace}-${stepName}`;
307
+ }
308
+ return null;
309
+ }
310
+ function derivePaletteVars(spec, config) {
311
+ const out = {};
312
+ if (!config) return out;
313
+ const baseColor = config.baseColor ?? spec.initialColor;
314
+ const overrides = config.overrides ?? {};
315
+ const curveOffset = config.curveOffset ?? {};
316
+ const scaleCurves = config.scaleCurves ?? {};
317
+ let baseForScales;
318
+ if (spec.mode === "gray") {
319
+ const grayLightnessCurve = config.grayLightnessCurve ?? DEFAULT_GRAY_LIGHTNESS();
320
+ const graySaturationCurve = config.graySaturationCurve ?? DEFAULT_GRAY_SATURATION();
321
+ const tintHue = config.tintHue ?? 240;
322
+ const tintChroma = config.tintChroma ?? DEFAULT_TINT_CHROMA;
323
+ let gray500 = "#808080";
324
+ GRAY_STEPS.forEach((step, index) => {
325
+ const k = grayStepKey(step.label);
326
+ const hex = computeGrayColor(index, tintHue, tintChroma, grayLightnessCurve, graySaturationCurve, curveOffset);
327
+ const effective = k in overrides ? overrides[k] : hex;
328
+ out[`--color-${spec.cssNamespace}-${step.label}`] = effective;
329
+ if (step.label === "500") gray500 = hex;
330
+ });
331
+ baseForScales = gray500;
332
+ } else {
333
+ const lightnessCurve = config.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS();
334
+ const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
335
+ PALETTE_STEPS.forEach((ps, index) => {
336
+ const k = paletteStepKey(ps.label);
337
+ const hex = computePaletteColor(index, baseColor, lightnessCurve, saturationCurve, curveOffset);
338
+ const effective = k in overrides ? overrides[k] : hex;
339
+ out[`--color-${spec.cssNamespace}-${ps.label}`] = effective;
340
+ });
341
+ baseForScales = baseColor;
342
+ }
343
+ for (const scale of SCALES) {
344
+ for (const step of scale.steps) {
345
+ const k = stepKey(scale.title, step.name);
346
+ const hex = k in overrides ? overrides[k] : computeDerivedColor(step, baseForScales, scale.title, scaleCurves, curveOffset);
347
+ const varName = scaleToCssVar(scale.title, step.name, spec.cssNamespace);
348
+ if (varName) out[varName] = hex;
349
+ }
350
+ }
351
+ if (spec.emptySelector) {
352
+ const emptyMode = config.emptyMode ?? "solid";
353
+ const emptyStep = config.emptyStep ?? "850";
354
+ const gradientStyle = config.gradientStyle ?? "linear";
355
+ const gradientAngle = config.gradientAngle ?? 180;
356
+ const gradientReverse = config.gradientReverse ?? false;
357
+ const gradientSize = config.gradientSize ?? "page";
358
+ const gradientStops = config.gradientStops ?? [
359
+ { position: 0, paletteLabel: "800" },
360
+ { position: 100, paletteLabel: "950" }
361
+ ];
362
+ if (emptyMode === "solid") {
363
+ const stepHex = out[`--color-${spec.cssNamespace}-${emptyStep}`];
364
+ if (stepHex) out["--page-bg"] = stepHex;
365
+ out["--page-bg-attachment"] = "scroll";
366
+ } else {
367
+ const sortedStops = [...gradientStops].sort(
368
+ (a, b) => gradientReverse ? b.position - a.position : a.position - b.position
369
+ );
370
+ const stopsCss = sortedStops.map((s) => `${out[`--color-${spec.cssNamespace}-${s.paletteLabel}`] ?? "#000000"} ${s.position}%`).join(", ");
371
+ let gradient;
372
+ switch (gradientStyle) {
373
+ case "radial":
374
+ gradient = `radial-gradient(circle, ${stopsCss})`;
375
+ break;
376
+ case "conic":
377
+ gradient = `conic-gradient(from ${gradientAngle}deg, ${stopsCss})`;
378
+ break;
379
+ default:
380
+ gradient = `linear-gradient(${gradientAngle}deg, ${stopsCss})`;
381
+ }
382
+ out["--page-bg"] = gradient;
383
+ out["--page-bg-attachment"] = gradientSize === "window" ? "fixed" : "scroll";
384
+ }
385
+ }
386
+ return out;
387
+ }
388
+ function palettesToVars(palettes) {
389
+ const out = {};
390
+ for (const spec of PALETTE_SPECS) {
391
+ Object.assign(out, derivePaletteVars(spec, palettes[spec.label]));
392
+ }
393
+ return out;
394
+ }
395
+ var HEX_RE = /^#[0-9a-f]{6}$/i;
396
+ function reconcilePalettesFromCssVars(palettes, cssVars) {
397
+ const next = structuredClone(palettes);
398
+ const consumed = /* @__PURE__ */ new Set();
399
+ const snapped = /* @__PURE__ */ new Set();
400
+ for (const spec of PALETTE_SPECS) {
401
+ const current = next[spec.label];
402
+ if (current === void 0) continue;
403
+ if (current._imported === true) {
404
+ const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
405
+ if (anchorHex && HEX_RE.test(anchorHex.trim())) {
406
+ const hex = anchorHex.trim();
407
+ if (spec.mode === "gray") {
408
+ const { c, h } = hexToOklch(hex);
409
+ next[spec.label] = { ...current, tintHue: h, tintChroma: c, _imported: false };
410
+ } else {
411
+ next[spec.label] = { ...current, baseColor: hex, _imported: false };
412
+ }
413
+ snapped.add(spec.label);
414
+ } else {
415
+ next[spec.label] = { ...current, _imported: false };
416
+ }
417
+ }
418
+ for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
419
+ consumed.add(k);
420
+ }
421
+ }
422
+ return { palettes: next, consumed, snapped };
423
+ }
424
+
425
+ // vite-plugin/files/versionedFileResourceServer.ts
22
426
  import fs from "fs";
23
427
  import path from "path";
24
428
  function versionedFileResourceServer(opts) {
@@ -78,7 +482,7 @@ function versionedFileResourceServer(opts) {
78
482
  };
79
483
  }
80
484
 
81
- // src/vite-plugin/files/routeTable.ts
485
+ // vite-plugin/files/routeTable.ts
82
486
  async function dispatch(req, res, routes) {
83
487
  const url = req.url || "";
84
488
  const method = req.method || "GET";
@@ -106,16 +510,45 @@ async function dispatch(req, res, routes) {
106
510
  return false;
107
511
  }
108
512
 
109
- // src/vite-plugin/themeFileApi.ts
110
- var PKG_VERSION = true ? "0.6.2" : "";
513
+ // vite-plugin/files/nameAllocator.ts
514
+ function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
515
+ if (!exists(baseName)) return baseName;
516
+ for (let i = 2; i < maxAttempts; i++) {
517
+ const candidate = `${baseName}-${i}`;
518
+ if (!exists(candidate)) return candidate;
519
+ }
520
+ throw new Error(`Could not allocate a non-colliding name for "${baseName}"`);
521
+ }
522
+
523
+ // vite-plugin/themeFileApi.ts
524
+ import { fileURLToPath } from "url";
525
+ var PKG_VERSION = (() => {
526
+ try {
527
+ let dir = path2.dirname(fileURLToPath(import.meta.url));
528
+ for (let i = 0; i < 4; i++) {
529
+ const p = path2.join(dir, "package.json");
530
+ if (fs2.existsSync(p)) {
531
+ const json = JSON.parse(fs2.readFileSync(p, "utf-8"));
532
+ if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
533
+ }
534
+ const up = path2.dirname(dir);
535
+ if (up === dir) break;
536
+ dir = up;
537
+ }
538
+ } catch {
539
+ }
540
+ return "";
541
+ })();
111
542
  function themeFileApi(opts) {
112
543
  const THEMES_DIR = path2.resolve(opts.themesDir);
113
544
  const CSS_PATH = path2.resolve(opts.tokensCssPath);
545
+ const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ? path2.resolve(opts.tokensGeneratedCssPath) : path2.join(path2.dirname(CSS_PATH), "tokens.generated.css");
114
546
  const FONTS_CSS_PATH = opts.fontsCssPath ? path2.resolve(opts.fontsCssPath) : path2.join(path2.dirname(CSS_PATH), "fonts.css");
115
547
  const API_BASE = opts.apiBase ?? "/api";
116
548
  const COMPONENT_CONFIGS_DIR = opts.componentConfigsDir ? path2.resolve(opts.componentConfigsDir) : path2.resolve("component-configs");
117
- const COMPONENTS_SRC_DIR = opts.componentsSrcDir ? path2.resolve(opts.componentsSrcDir) : path2.resolve("src/components");
118
- const PRESETS_DIR = opts.presetsDir ? path2.resolve(opts.presetsDir) : path2.resolve("presets");
549
+ const COMPONENTS_SRC_DIR = opts.componentsSrcDir ? path2.resolve(opts.componentsSrcDir) : path2.resolve("src/system/components");
550
+ const MANIFESTS_DIR = opts.manifestsDir ? path2.resolve(opts.manifestsDir) : path2.resolve("manifests");
551
+ const LEGACY_PRESETS_DIR = path2.resolve("presets");
119
552
  const themesResource = versionedFileResourceServer({
120
553
  dir: THEMES_DIR
121
554
  });
@@ -128,7 +561,7 @@ function themeFileApi(opts) {
128
561
  }
129
562
  return r;
130
563
  }
131
- const presetsResource = versionedFileResourceServer({ dir: PRESETS_DIR });
564
+ const manifestsResource = versionedFileResourceServer({ dir: MANIFESTS_DIR });
132
565
  function ensureThemesDir() {
133
566
  themesResource.ensureDir();
134
567
  if (!fs2.existsSync(path2.join(THEMES_DIR, "default.json"))) {
@@ -156,6 +589,17 @@ function themeFileApi(opts) {
156
589
  res.setHeader("Content-Type", "application/json");
157
590
  res.end(JSON.stringify(data));
158
591
  }
592
+ function normalizeTheme(theme) {
593
+ if (!theme || typeof theme !== "object") return theme;
594
+ const palettes = theme.editorConfigs ?? {};
595
+ const cssVars = theme.cssVariables ?? {};
596
+ const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
597
+ const nextCssVars = {};
598
+ for (const [k, v] of Object.entries(cssVars)) {
599
+ if (!consumed.has(k)) nextCssVars[k] = v;
600
+ }
601
+ return { ...theme, editorConfigs: nextPalettes, cssVariables: nextCssVars };
602
+ }
159
603
  const SYSTEM_CASCADES_SSR = {
160
604
  "system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
161
605
  "system-ui-serif": 'Georgia, "Times New Roman", serif',
@@ -187,42 +631,78 @@ function themeFileApi(opts) {
187
631
  }
188
632
  return out;
189
633
  }
190
- function syncTokensToCss(fileName) {
191
- const themePath = path2.join(THEMES_DIR, `${fileName}.json`);
192
- if (!fs2.existsSync(themePath)) return;
193
- const themeData = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
194
- const cssVars = { ...themeData.cssVariables || {} };
195
- const resolvedFontVars = resolveFontStacks(themeData);
196
- for (const [name, value] of Object.entries(resolvedFontVars)) {
197
- cssVars[name] = value;
198
- }
199
- if (Object.keys(cssVars).length === 0) return;
200
- const cssContent = fs2.readFileSync(CSS_PATH, "utf-8");
201
- const remaining = new Set(Object.keys(cssVars));
202
- const updatedContent = cssContent.replace(
203
- /^(\s*)(--[\w-]+):\s*(.+);/gm,
204
- (_match, indent, varName, oldValue) => {
205
- if (cssVars[varName] !== void 0) {
206
- remaining.delete(varName);
207
- return `${indent}${varName}: ${cssVars[varName]};`;
634
+ function regenerateTokensCss() {
635
+ const lines = [];
636
+ lines.push("/* Generated by themeFileApi from the production theme and component configs. Do not edit. */");
637
+ lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
638
+ lines.push("");
639
+ const productionThemeName = themesResource.getProductionName();
640
+ const themePath = path2.join(THEMES_DIR, `${productionThemeName}.json`);
641
+ let themeVarCount = 0;
642
+ if (fs2.existsSync(themePath)) {
643
+ const themeData = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
644
+ const cssVars = { ...themeData.cssVariables || {} };
645
+ Object.assign(cssVars, palettesToVars(themeData.editorConfigs ?? {}));
646
+ const resolvedFontVars = resolveFontStacks(themeData);
647
+ for (const [name, value] of Object.entries(resolvedFontVars)) {
648
+ cssVars[name] = value;
649
+ }
650
+ themeVarCount = Object.keys(cssVars).length;
651
+ if (themeVarCount > 0) {
652
+ lines.push(`/* Production theme: ${productionThemeName} */`);
653
+ lines.push(":root:root {");
654
+ for (const [name, value] of Object.entries(cssVars)) {
655
+ lines.push(` ${name}: ${value};`);
208
656
  }
209
- return `${indent}${varName}: ${oldValue.trim()};`;
657
+ lines.push("}");
658
+ lines.push("");
210
659
  }
211
- );
212
- let finalContent = updatedContent;
213
- if (remaining.size > 0) {
214
- const newVars = [...remaining].map((name) => ` ${name}: ${cssVars[name]};`).join("\n");
215
- finalContent = finalContent.replace(
216
- /\n\}(\s*)$/,
217
- `
218
-
219
- /* Token additions */
220
- ${newVars}
221
- }$1`
222
- );
223
660
  }
224
- fs2.writeFileSync(CSS_PATH, finalContent);
225
- console.log(`[syncTokensToCss] Wrote ${Object.keys(cssVars).length} variables from "${fileName}" into tokens.css`);
661
+ let componentOverrideCount = 0;
662
+ if (fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
663
+ const blocks = [];
664
+ for (const comp of listComponentNames()) {
665
+ const prod = componentResource(comp).getProductionName();
666
+ if (prod === "default") continue;
667
+ const prodCfg = readComponentConfig(comp, prod);
668
+ const defaultCfg = readComponentConfig(comp, "default");
669
+ if (!prodCfg || !defaultCfg) continue;
670
+ const overrides = [];
671
+ const defaultAliases = defaultCfg.aliases ?? {};
672
+ for (const [varName, semanticValue] of Object.entries(prodCfg.aliases ?? {})) {
673
+ if (!aliasValuesEqual(defaultAliases[varName], semanticValue)) {
674
+ overrides.push([varName, semanticValue]);
675
+ }
676
+ }
677
+ if (overrides.length === 0) continue;
678
+ const block = [` /* ${comp} (${prod}) */`];
679
+ for (const [varName, semanticValue] of overrides) {
680
+ block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
681
+ }
682
+ blocks.push(block);
683
+ componentOverrideCount += overrides.length;
684
+ }
685
+ if (blocks.length > 0) {
686
+ lines.push("/* Component aliases (production configs differing from defaults) */");
687
+ lines.push(":root:root {");
688
+ for (let i = 0; i < blocks.length; i++) {
689
+ if (i > 0) lines.push("");
690
+ lines.push(...blocks[i]);
691
+ }
692
+ lines.push("}");
693
+ lines.push("");
694
+ }
695
+ }
696
+ if (!fs2.existsSync(path2.dirname(GENERATED_CSS_PATH))) {
697
+ fs2.mkdirSync(path2.dirname(GENERATED_CSS_PATH), { recursive: true });
698
+ }
699
+ fs2.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
700
+ console.log(
701
+ `[regenerateTokensCss] Wrote ${path2.basename(GENERATED_CSS_PATH)} (${themeVarCount} theme vars, ${componentOverrideCount} component overrides)`
702
+ );
703
+ }
704
+ function syncTokensToCss(_fileName) {
705
+ regenerateTokensCss();
226
706
  }
227
707
  function syncFontsToCss(fileName) {
228
708
  const themePath = path2.join(THEMES_DIR, `${fileName}.json`);
@@ -234,15 +714,20 @@ ${newVars}
234
714
  lines.push("/* Generated from the production theme by syncFontsToCss. Do not edit. */");
235
715
  lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
236
716
  lines.push("");
237
- for (const source of sources) {
717
+ const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
718
+ const faceSources = sources.filter((s) => s.kind === "font-face" && s.cssText);
719
+ for (const source of urlSources) {
238
720
  const familyNames = source.families.map((f) => f.name).join(", ");
239
721
  const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
240
722
  lines.push(`/* ${label} */`);
241
- if (source.kind === "font-face") {
242
- if (source.cssText) lines.push(source.cssText);
243
- } else if (source.url) {
244
- lines.push(`@import url('${source.url}');`);
245
- }
723
+ lines.push(`@import url('${source.url}');`);
724
+ lines.push("");
725
+ }
726
+ for (const source of faceSources) {
727
+ const familyNames = source.families.map((f) => f.name).join(", ");
728
+ const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
729
+ lines.push(`/* ${label} */`);
730
+ lines.push(source.cssText);
246
731
  lines.push("");
247
732
  }
248
733
  const content = lines.join("\n");
@@ -324,25 +809,90 @@ ${newVars}
324
809
  r.ensureMeta();
325
810
  }
326
811
  }
327
- function ensurePresetsDir() {
328
- presetsResource.ensureDir();
329
- const defaultPath = path2.join(PRESETS_DIR, "default.json");
812
+ function ensureManifestsDir() {
813
+ if (!fs2.existsSync(MANIFESTS_DIR) && fs2.existsSync(LEGACY_PRESETS_DIR)) {
814
+ fs2.renameSync(LEGACY_PRESETS_DIR, MANIFESTS_DIR);
815
+ const legacyProd = path2.join(MANIFESTS_DIR, "_production.json");
816
+ if (fs2.existsSync(legacyProd)) fs2.unlinkSync(legacyProd);
817
+ }
818
+ manifestsResource.ensureDir();
819
+ const defaultPath = path2.join(MANIFESTS_DIR, "default.json");
330
820
  if (!fs2.existsSync(defaultPath)) {
331
821
  const componentConfigs = {};
332
822
  for (const comp of listComponentNames()) {
333
823
  componentConfigs[comp] = componentResource(comp).getActiveName();
334
824
  }
335
825
  const now = (/* @__PURE__ */ new Date()).toISOString();
336
- const defaultPreset = {
337
- name: "Default Preset",
826
+ const defaultManifest = {
827
+ name: "Default",
338
828
  createdAt: now,
339
829
  updatedAt: now,
340
830
  theme: themesResource.getActiveName(),
341
831
  componentConfigs
342
832
  };
343
- fs2.writeFileSync(defaultPath, JSON.stringify(defaultPreset, null, 2));
833
+ fs2.writeFileSync(defaultPath, JSON.stringify(defaultManifest, null, 2));
344
834
  }
345
- presetsResource.ensureMeta();
835
+ if (!fs2.existsSync(manifestsResource.activePath)) {
836
+ fs2.writeFileSync(
837
+ manifestsResource.activePath,
838
+ JSON.stringify({ activeFile: "default" })
839
+ );
840
+ } else {
841
+ const activeName = manifestsResource.getActiveName();
842
+ if (!fs2.existsSync(manifestsResource.filePath(activeName))) {
843
+ manifestsResource.setActiveName("default");
844
+ }
845
+ }
846
+ const stragglerProd = path2.join(MANIFESTS_DIR, "_production.json");
847
+ if (fs2.existsSync(stragglerProd)) fs2.unlinkSync(stragglerProd);
848
+ }
849
+ function patchActiveManifest(field, comp, fileName) {
850
+ const activeFile = manifestsResource.getActiveName();
851
+ if (activeFile === "default") return false;
852
+ const manifestPath = manifestsResource.filePath(activeFile);
853
+ if (!fs2.existsSync(manifestPath)) return false;
854
+ let manifest;
855
+ try {
856
+ manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
857
+ } catch {
858
+ return false;
859
+ }
860
+ if (field === "theme") {
861
+ manifest.theme = fileName;
862
+ } else if (field === "component" && comp) {
863
+ manifest.componentConfigs = manifest.componentConfigs ?? {};
864
+ manifest.componentConfigs[comp] = fileName;
865
+ }
866
+ manifest.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
867
+ fs2.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
868
+ return true;
869
+ }
870
+ function formatAliasGradient(v) {
871
+ const stopColor = (s) => {
872
+ const base = s.color.startsWith("--") ? `var(${s.color})` : s.color;
873
+ const opacity = s.opacity ?? 100;
874
+ return opacity >= 100 ? base : `color-mix(in srgb, ${base} ${opacity}%, transparent)`;
875
+ };
876
+ if (v.type === "none") return "transparent";
877
+ if (v.type === "solid") {
878
+ const first = v.stops[0];
879
+ if (!first) return "transparent";
880
+ return stopColor(first);
881
+ }
882
+ const stops = v.stops.map((s) => `${stopColor(s)} ${s.position}%`).join(", ");
883
+ if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
884
+ const radial = v.radius && v.radius > 0 ? `circle ${v.radius}px at center` : "circle";
885
+ return `radial-gradient(${radial}, ${stops})`;
886
+ }
887
+ function aliasValueToCss(v) {
888
+ if (typeof v === "string") return v.startsWith("--") ? `var(${v})` : v;
889
+ return formatAliasGradient(v.value);
890
+ }
891
+ function aliasValuesEqual(a, b) {
892
+ if (a === void 0 || b === void 0) return a === b;
893
+ if (typeof a === "string" && typeof b === "string") return a === b;
894
+ if (typeof a !== typeof b) return false;
895
+ return JSON.stringify(a) === JSON.stringify(b);
346
896
  }
347
897
  function readComponentConfig(comp, name) {
348
898
  const filePath = componentResource(comp).filePath(name);
@@ -353,70 +903,25 @@ ${newVars}
353
903
  return null;
354
904
  }
355
905
  }
356
- const COMPONENT_OVERRIDES_START = "/* component-aliases:start */";
357
- const COMPONENT_OVERRIDES_END = "/* component-aliases:end */";
358
906
  function syncComponentsToCss() {
359
- if (!fs2.existsSync(CSS_PATH)) return;
360
- if (!fs2.existsSync(COMPONENT_CONFIGS_DIR)) return;
361
- const lines = [];
362
- const components = listComponentNames();
363
- for (const comp of components) {
364
- const prod = componentResource(comp).getProductionName();
365
- if (prod === "default") continue;
366
- const prodCfg = readComponentConfig(comp, prod);
367
- const defaultCfg = readComponentConfig(comp, "default");
368
- if (!prodCfg || !defaultCfg) continue;
369
- const overrides = [];
370
- for (const [varName, semanticName] of Object.entries(prodCfg.aliases ?? {})) {
371
- if ((defaultCfg.aliases ?? {})[varName] !== semanticName) {
372
- overrides.push([varName, semanticName]);
373
- }
374
- }
375
- if (overrides.length === 0) continue;
376
- lines.push(` /* ${comp} (${prod}) */`);
377
- for (const [varName, semanticName] of overrides) {
378
- lines.push(` ${varName}: var(${semanticName});`);
379
- }
380
- }
381
- const block = lines.length > 0 ? `
382
-
383
- ${COMPONENT_OVERRIDES_START}
384
- :root:root {
385
- ${lines.join("\n")}
386
- }
387
- ${COMPONENT_OVERRIDES_END}
388
- ` : "";
389
- let cssContent = fs2.readFileSync(CSS_PATH, "utf-8");
390
- const startIdx = cssContent.indexOf(COMPONENT_OVERRIDES_START);
391
- const endIdx = cssContent.indexOf(COMPONENT_OVERRIDES_END);
392
- if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
393
- let stripStart = startIdx;
394
- while (stripStart > 0 && cssContent[stripStart - 1] === "\n") stripStart--;
395
- const after = cssContent.slice(endIdx + COMPONENT_OVERRIDES_END.length);
396
- cssContent = cssContent.slice(0, stripStart) + (block || "\n") + after.replace(/^\n+/, "");
397
- } else if (block) {
398
- cssContent = cssContent.replace(/\n*$/, "") + block;
399
- }
400
- fs2.writeFileSync(CSS_PATH, cssContent);
401
- console.log(
402
- `[syncComponentsToCss] Wrote ${lines.filter((l) => !l.trim().startsWith("/*")).length} alias override(s) to tokens.css`
403
- );
907
+ regenerateTokensCss();
404
908
  }
405
909
  const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
406
910
  const THEMES_ROUTE = `${API_BASE}/themes`;
407
911
  const THEMES_ACTIVE_ROUTE = `${API_BASE}/themes/active`;
408
912
  const THEMES_PRODUCTION_ROUTE = `${API_BASE}/themes/production`;
409
913
  const COMPONENT_CONFIGS_ROUTE = `${API_BASE}/component-configs`;
410
- const PRESETS_ROUTE = `${API_BASE}/presets`;
411
- const PRESETS_ACTIVE_ROUTE = `${API_BASE}/presets/active`;
412
- const PRESETS_PRODUCTION_ROUTE = `${API_BASE}/presets/production`;
914
+ const MANIFESTS_ROUTE = `${API_BASE}/manifests`;
915
+ const MANIFESTS_ACTIVE_ROUTE = `${API_BASE}/manifests/active`;
413
916
  const THEME_BY_NAME_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)$`);
414
917
  const COMP_LIST_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)$`);
415
918
  const COMP_ACTIVE_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/active$`);
416
919
  const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
417
920
  const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
418
- const PRESET_APPLY_REGEX = new RegExp(`^${escapedBase}/presets/([a-z0-9\\-_]+)/apply$`);
419
- const PRESET_BY_NAME_REGEX = new RegExp(`^${escapedBase}/presets/([a-z0-9\\-_]+)$`);
921
+ const MANIFEST_APPLY_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/apply$`);
922
+ const MANIFEST_EXPORT_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/export$`);
923
+ const MANIFEST_IMPORT_ROUTE = `${API_BASE}/manifests/import`;
924
+ const MANIFEST_BY_NAME_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)$`);
420
925
  async function handleListThemes(_ctx) {
421
926
  const activeFile = themesResource.getActiveName();
422
927
  const files = fs2.readdirSync(THEMES_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
@@ -439,7 +944,7 @@ ${COMPONENT_OVERRIDES_END}
439
944
  jsonResponse(res, 404, { error: "Active theme not found" });
440
945
  return;
441
946
  }
442
- const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
947
+ const data = normalizeTheme(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
443
948
  data._fileName = activeFile;
444
949
  jsonResponse(res, 200, data);
445
950
  }
@@ -460,7 +965,7 @@ ${COMPONENT_OVERRIDES_END}
460
965
  jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
461
966
  return;
462
967
  }
463
- const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
968
+ const data = normalizeTheme(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
464
969
  jsonResponse(res, 200, {
465
970
  fileName: prodFile,
466
971
  name: data.name || prodFile,
@@ -475,10 +980,18 @@ ${COMPONENT_OVERRIDES_END}
475
980
  jsonResponse(res, 404, { error: "Theme not found" });
476
981
  return;
477
982
  }
983
+ if (manifestsResource.getActiveName() === "default") {
984
+ jsonResponse(res, 409, {
985
+ error: "Active manifest is protected. Save As first.",
986
+ code: "ACTIVE_IS_PROTECTED"
987
+ });
988
+ return;
989
+ }
478
990
  themesResource.setProductionName(fileName);
479
991
  syncTokensToCss(fileName);
480
992
  syncFontsToCss(fileName);
481
993
  syncComponentsToCss();
994
+ patchActiveManifest("theme", null, fileName);
482
995
  const data = JSON.parse(fs2.readFileSync(themesResource.filePath(fileName), "utf-8"));
483
996
  jsonResponse(res, 200, {
484
997
  ok: true,
@@ -495,7 +1008,7 @@ ${COMPONENT_OVERRIDES_END}
495
1008
  jsonResponse(res, 404, { error: "Not found" });
496
1009
  return;
497
1010
  }
498
- const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
1011
+ const data = normalizeTheme(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
499
1012
  data._fileName = fileName;
500
1013
  jsonResponse(res, 200, data);
501
1014
  return;
@@ -525,6 +1038,13 @@ ${COMPONENT_OVERRIDES_END}
525
1038
  jsonResponse(res, 403, { error: "Cannot delete the default theme" });
526
1039
  return;
527
1040
  }
1041
+ if (themesResource.getProductionName() === fileName) {
1042
+ jsonResponse(res, 403, {
1043
+ error: "Cannot delete the production theme. Adopt a different theme first.",
1044
+ code: "PRODUCTION_THEME"
1045
+ });
1046
+ return;
1047
+ }
528
1048
  if (fs2.existsSync(filePath)) {
529
1049
  fs2.unlinkSync(filePath);
530
1050
  if (themesResource.getActiveName() === fileName) {
@@ -592,9 +1112,17 @@ ${COMPONENT_OVERRIDES_END}
592
1112
  jsonResponse(res, 404, { error: "Config not found" });
593
1113
  return;
594
1114
  }
1115
+ if (manifestsResource.getActiveName() === "default") {
1116
+ jsonResponse(res, 409, {
1117
+ error: "Active manifest is protected. Save As first.",
1118
+ code: "ACTIVE_IS_PROTECTED"
1119
+ });
1120
+ return;
1121
+ }
595
1122
  r.ensureDir();
596
1123
  r.setProductionName(fileName);
597
1124
  syncComponentsToCss();
1125
+ patchActiveManifest("component", comp, fileName);
598
1126
  const cfg = readComponentConfig(comp, fileName);
599
1127
  jsonResponse(res, 200, {
600
1128
  ok: true,
@@ -684,112 +1212,46 @@ ${COMPONENT_OVERRIDES_END}
684
1212
  });
685
1213
  jsonResponse(res, 200, { component: comp, files, activeFile, productionFile });
686
1214
  }
687
- async function handleListPresets({ res }) {
688
- const activeFile = presetsResource.getActiveName();
689
- const files = fs2.readdirSync(PRESETS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
690
- const filePath = path2.join(PRESETS_DIR, f);
1215
+ async function handleListManifests({ res }) {
1216
+ const activeFile = manifestsResource.getActiveName();
1217
+ const files = fs2.readdirSync(MANIFESTS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
1218
+ const filePath = path2.join(MANIFESTS_DIR, f);
691
1219
  const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
692
1220
  const fileName = f.replace(".json", "");
693
1221
  return {
694
1222
  name: data.name || fileName,
695
1223
  fileName,
696
1224
  updatedAt: data.updatedAt || "",
697
- isActive: fileName === activeFile
1225
+ isActive: fileName === activeFile,
1226
+ isProtected: fileName === "default"
698
1227
  };
699
1228
  });
700
1229
  jsonResponse(res, 200, { files, activeFile });
701
1230
  }
702
- async function handleGetActivePreset({ res }) {
703
- const activeFile = presetsResource.getActiveName();
704
- const filePath = presetsResource.filePath(activeFile);
1231
+ async function handleGetActiveManifest({ res }) {
1232
+ const activeFile = manifestsResource.getActiveName();
1233
+ const filePath = manifestsResource.filePath(activeFile);
705
1234
  if (!fs2.existsSync(filePath)) {
706
- jsonResponse(res, 404, { error: "Active preset not found" });
1235
+ jsonResponse(res, 404, { error: "Active manifest not found" });
707
1236
  return;
708
1237
  }
709
1238
  const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
710
1239
  data._fileName = activeFile;
711
1240
  jsonResponse(res, 200, data);
712
1241
  }
713
- async function handleSetActivePreset({ req, res }) {
1242
+ async function handleSetActiveManifest({ req, res }) {
714
1243
  const body = JSON.parse(await readBody(req));
715
1244
  const fileName = sanitizeFileName(body.name || "default");
716
- if (!fs2.existsSync(presetsResource.filePath(fileName))) {
717
- jsonResponse(res, 404, { error: "Preset not found" });
1245
+ if (!fs2.existsSync(manifestsResource.filePath(fileName))) {
1246
+ jsonResponse(res, 404, { error: "Manifest not found" });
718
1247
  return;
719
1248
  }
720
- presetsResource.setActiveName(fileName);
1249
+ manifestsResource.setActiveName(fileName);
721
1250
  jsonResponse(res, 200, { ok: true, activeFile: fileName });
722
1251
  }
723
- async function handleGetProductionPreset({ res }) {
724
- const prodFile = presetsResource.getProductionName();
725
- const filePath = presetsResource.filePath(prodFile);
726
- if (!fs2.existsSync(filePath)) {
727
- jsonResponse(res, 200, {
728
- fileName: prodFile,
729
- name: prodFile,
730
- theme: "default",
731
- componentConfigs: {},
732
- updatedAt: ""
733
- });
734
- return;
735
- }
736
- const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
737
- jsonResponse(res, 200, {
738
- fileName: prodFile,
739
- name: data.name || prodFile,
740
- theme: data.theme || "default",
741
- componentConfigs: data.componentConfigs || {},
742
- updatedAt: data.updatedAt || ""
743
- });
744
- }
745
- async function handleSetProductionPreset({ req, res }) {
746
- const body = JSON.parse(await readBody(req));
747
- const fileName = sanitizeFileName(body.name || "default");
748
- const presetPath = presetsResource.filePath(fileName);
749
- if (!fs2.existsSync(presetPath)) {
750
- jsonResponse(res, 404, { error: "Preset not found" });
751
- return;
752
- }
753
- const preset = JSON.parse(fs2.readFileSync(presetPath, "utf-8"));
754
- const themeName = sanitizeFileName(preset.theme || "default");
755
- if (!fs2.existsSync(themesResource.filePath(themeName))) {
756
- jsonResponse(res, 422, { error: `Preset references missing theme: ${themeName}` });
757
- return;
758
- }
759
- const knownComponents = new Set(listComponentNames());
760
- const componentConfigs = preset.componentConfigs ?? {};
761
- const apply = [];
762
- for (const [comp, configFile] of Object.entries(componentConfigs)) {
763
- if (!knownComponents.has(comp)) continue;
764
- const sanitized = sanitizeFileName(String(configFile) || "default");
765
- if (!fs2.existsSync(componentResource(comp).filePath(sanitized))) {
766
- jsonResponse(res, 422, {
767
- error: `Preset references missing config: ${comp}/${sanitized}`
768
- });
769
- return;
770
- }
771
- apply.push([comp, sanitized]);
772
- }
773
- themesResource.setProductionName(themeName);
774
- for (const [comp, configFile] of apply) {
775
- componentResource(comp).setProductionName(configFile);
776
- }
777
- presetsResource.setProductionName(fileName);
778
- syncTokensToCss(themeName);
779
- syncFontsToCss(themeName);
780
- syncComponentsToCss();
781
- jsonResponse(res, 200, {
782
- ok: true,
783
- fileName,
784
- name: preset.name || fileName,
785
- theme: themeName,
786
- componentConfigs: Object.fromEntries(apply),
787
- updatedAt: preset.updatedAt || ""
788
- });
789
- }
790
- async function handlePresetByName({ params, req, res }) {
1252
+ async function handleManifestByName({ params, req, res }) {
791
1253
  const [fileName] = params;
792
- const filePath = presetsResource.filePath(fileName);
1254
+ const filePath = manifestsResource.filePath(fileName);
793
1255
  if (req.method === "GET") {
794
1256
  if (!fs2.existsSync(filePath)) {
795
1257
  jsonResponse(res, 404, { error: "Not found" });
@@ -802,7 +1264,7 @@ ${COMPONENT_OVERRIDES_END}
802
1264
  }
803
1265
  if (req.method === "PUT") {
804
1266
  if (fileName === "default") {
805
- jsonResponse(res, 403, { error: "Cannot overwrite the default preset" });
1267
+ jsonResponse(res, 403, { error: "Cannot overwrite the default manifest" });
806
1268
  return;
807
1269
  }
808
1270
  const body = JSON.parse(await readBody(req));
@@ -821,35 +1283,35 @@ ${COMPONENT_OVERRIDES_END}
821
1283
  }
822
1284
  if (req.method === "DELETE") {
823
1285
  if (fileName === "default") {
824
- jsonResponse(res, 403, { error: "Cannot delete the default preset" });
1286
+ jsonResponse(res, 403, { error: "Cannot delete the default manifest" });
825
1287
  return;
826
1288
  }
827
1289
  if (fs2.existsSync(filePath)) {
828
1290
  fs2.unlinkSync(filePath);
829
- if (presetsResource.getActiveName() === fileName) {
830
- presetsResource.setActiveName("default");
1291
+ if (manifestsResource.getActiveName() === fileName) {
1292
+ manifestsResource.setActiveName("default");
831
1293
  }
832
1294
  }
833
1295
  jsonResponse(res, 200, { ok: true });
834
1296
  return;
835
1297
  }
836
1298
  }
837
- async function handleApplyPreset({ params, res }) {
1299
+ async function handleApplyManifest({ params, res }) {
838
1300
  const [fileName] = params;
839
- const presetPath = presetsResource.filePath(fileName);
840
- if (!fs2.existsSync(presetPath)) {
841
- jsonResponse(res, 404, { error: "Preset not found" });
1301
+ const manifestPath = manifestsResource.filePath(fileName);
1302
+ if (!fs2.existsSync(manifestPath)) {
1303
+ jsonResponse(res, 404, { error: "Manifest not found" });
842
1304
  return;
843
1305
  }
844
- const preset = JSON.parse(fs2.readFileSync(presetPath, "utf-8"));
845
- const themeName = sanitizeFileName(preset.theme || "default");
1306
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1307
+ const themeName = sanitizeFileName(manifest.theme || "default");
846
1308
  const themePath = themesResource.filePath(themeName);
847
1309
  if (!fs2.existsSync(themePath)) {
848
- jsonResponse(res, 422, { error: `Preset references missing theme: ${themeName}` });
1310
+ jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
849
1311
  return;
850
1312
  }
851
1313
  const knownComponents = new Set(listComponentNames());
852
- const componentConfigs = preset.componentConfigs ?? {};
1314
+ const componentConfigs = manifest.componentConfigs ?? {};
853
1315
  const resolvedConfigs = {};
854
1316
  const apply = [];
855
1317
  for (const [comp, configFile] of Object.entries(componentConfigs)) {
@@ -859,18 +1321,22 @@ ${COMPONENT_OVERRIDES_END}
859
1321
  const cfgPath = r.filePath(sanitized);
860
1322
  if (!fs2.existsSync(cfgPath)) {
861
1323
  jsonResponse(res, 422, {
862
- error: `Preset references missing config: ${comp}/${sanitized}`
1324
+ error: `Manifest references missing config: ${comp}/${sanitized}`
863
1325
  });
864
1326
  return;
865
1327
  }
866
1328
  apply.push([comp, sanitized]);
867
1329
  }
868
1330
  themesResource.setActiveName(themeName);
869
- const themeData = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
1331
+ themesResource.setProductionName(themeName);
1332
+ syncTokensToCss(themeName);
1333
+ syncFontsToCss(themeName);
1334
+ const themeData = normalizeTheme(JSON.parse(fs2.readFileSync(themePath, "utf-8")));
870
1335
  themeData._fileName = themeName;
871
1336
  for (const [comp, configFile] of apply) {
872
1337
  const r = componentResource(comp);
873
1338
  r.setActiveName(configFile);
1339
+ r.setProductionName(configFile);
874
1340
  const cfg = readComponentConfig(comp, configFile);
875
1341
  if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
876
1342
  }
@@ -880,14 +1346,166 @@ ${COMPONENT_OVERRIDES_END}
880
1346
  const cfg = readComponentConfig(comp, activeName);
881
1347
  if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
882
1348
  }
883
- presetsResource.setActiveName(fileName);
1349
+ syncComponentsToCss();
1350
+ manifestsResource.setActiveName(fileName);
884
1351
  jsonResponse(res, 200, {
885
1352
  ok: true,
886
- preset: { ...preset, _fileName: fileName },
1353
+ manifest: { ...manifest, _fileName: fileName },
887
1354
  theme: themeData,
888
1355
  componentConfigs: resolvedConfigs
889
1356
  });
890
1357
  }
1358
+ async function handleExportManifest({ params, res }) {
1359
+ const [fileName] = params;
1360
+ const manifestPath = manifestsResource.filePath(fileName);
1361
+ if (!fs2.existsSync(manifestPath)) {
1362
+ jsonResponse(res, 404, { error: "Manifest not found" });
1363
+ return;
1364
+ }
1365
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1366
+ const themeName = sanitizeFileName(manifest.theme || "default");
1367
+ const themePath = themesResource.filePath(themeName);
1368
+ if (!fs2.existsSync(themePath)) {
1369
+ jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
1370
+ return;
1371
+ }
1372
+ const theme = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
1373
+ const knownComponents = new Set(listComponentNames());
1374
+ const componentConfigs = {};
1375
+ for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
1376
+ if (!knownComponents.has(comp)) continue;
1377
+ const sanitized = sanitizeFileName(String(configFile) || "default");
1378
+ if (sanitized === "default") continue;
1379
+ const cfgPath = componentResource(comp).filePath(sanitized);
1380
+ if (!fs2.existsSync(cfgPath)) {
1381
+ jsonResponse(res, 422, {
1382
+ error: `Manifest references missing config: ${comp}/${sanitized}`
1383
+ });
1384
+ return;
1385
+ }
1386
+ const cfg = JSON.parse(fs2.readFileSync(cfgPath, "utf-8"));
1387
+ componentConfigs[`${comp}/${sanitized}`] = cfg;
1388
+ }
1389
+ const bundle = {
1390
+ kind: "manifest-bundle",
1391
+ schemaVersion: 1,
1392
+ liveTokensVersion: PKG_VERSION,
1393
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1394
+ manifest,
1395
+ theme,
1396
+ componentConfigs
1397
+ };
1398
+ res.statusCode = 200;
1399
+ res.setHeader("Content-Type", "application/json");
1400
+ res.setHeader(
1401
+ "Content-Disposition",
1402
+ `attachment; filename="${fileName}.bundle.json"`
1403
+ );
1404
+ res.end(JSON.stringify(bundle, null, 2));
1405
+ }
1406
+ function nextAvailableName2(resourceFilePath, baseName) {
1407
+ return nextAvailableName(
1408
+ (n) => fs2.existsSync(resourceFilePath(n)),
1409
+ sanitizeFileName(baseName)
1410
+ );
1411
+ }
1412
+ async function handleImportManifest({ req, res }) {
1413
+ let bundle;
1414
+ try {
1415
+ bundle = JSON.parse(await readBody(req));
1416
+ } catch {
1417
+ jsonResponse(res, 400, { error: "Body is not valid JSON" });
1418
+ return;
1419
+ }
1420
+ if (!bundle || bundle.kind !== "manifest-bundle") {
1421
+ jsonResponse(res, 400, {
1422
+ error: "Not a manifest bundle (kind discriminator missing or wrong)"
1423
+ });
1424
+ return;
1425
+ }
1426
+ if (bundle.schemaVersion !== 1) {
1427
+ jsonResponse(res, 400, {
1428
+ error: `Unsupported bundle schemaVersion: ${bundle.schemaVersion}`
1429
+ });
1430
+ return;
1431
+ }
1432
+ if (!bundle.manifest || !bundle.theme || !bundle.componentConfigs) {
1433
+ jsonResponse(res, 400, { error: "Bundle missing manifest / theme / componentConfigs" });
1434
+ return;
1435
+ }
1436
+ const renames = {};
1437
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1438
+ const originalThemeName = sanitizeFileName(bundle.manifest.theme || "default");
1439
+ const finalThemeName = nextAvailableName2(
1440
+ (n) => themesResource.filePath(n),
1441
+ originalThemeName
1442
+ );
1443
+ if (finalThemeName !== originalThemeName) {
1444
+ renames[`theme:${originalThemeName}`] = finalThemeName;
1445
+ }
1446
+ const themeBody = { ...bundle.theme };
1447
+ themeBody.updatedAt = now;
1448
+ if (!themeBody.createdAt) themeBody.createdAt = now;
1449
+ themesResource.ensureDir();
1450
+ fs2.writeFileSync(themesResource.filePath(finalThemeName), JSON.stringify(themeBody, null, 2));
1451
+ const knownComponents = new Set(listComponentNames());
1452
+ const componentRenames = {};
1453
+ for (const [key, cfgValue] of Object.entries(bundle.componentConfigs)) {
1454
+ const [comp, originalName] = key.split("/");
1455
+ if (!comp || !originalName) continue;
1456
+ if (!knownComponents.has(comp)) continue;
1457
+ const r = componentResource(comp);
1458
+ const finalName = nextAvailableName2(
1459
+ (n) => r.filePath(n),
1460
+ originalName
1461
+ );
1462
+ if (finalName !== originalName) {
1463
+ renames[`componentConfig:${comp}/${originalName}`] = finalName;
1464
+ }
1465
+ componentRenames[`${comp}/${originalName}`] = finalName;
1466
+ const cfgBody = { ...cfgValue };
1467
+ cfgBody.component = comp;
1468
+ cfgBody.name = finalName;
1469
+ cfgBody.updatedAt = now;
1470
+ if (!cfgBody.createdAt) cfgBody.createdAt = now;
1471
+ r.ensureDir();
1472
+ fs2.writeFileSync(r.filePath(finalName), JSON.stringify(cfgBody, null, 2));
1473
+ }
1474
+ const rewrittenManifest = {
1475
+ ...bundle.manifest,
1476
+ theme: finalThemeName,
1477
+ componentConfigs: {}
1478
+ };
1479
+ for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
1480
+ const original = String(configName);
1481
+ if (original === "default") {
1482
+ rewrittenManifest.componentConfigs[comp] = "default";
1483
+ continue;
1484
+ }
1485
+ const finalName = componentRenames[`${comp}/${original}`] ?? original;
1486
+ rewrittenManifest.componentConfigs[comp] = finalName;
1487
+ }
1488
+ rewrittenManifest.updatedAt = now;
1489
+ if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
1490
+ const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
1491
+ const finalManifestName = nextAvailableName2(
1492
+ (n) => manifestsResource.filePath(n),
1493
+ originalManifestName
1494
+ );
1495
+ if (finalManifestName !== originalManifestName) {
1496
+ renames[`manifest:${originalManifestName}`] = finalManifestName;
1497
+ }
1498
+ manifestsResource.ensureDir();
1499
+ fs2.writeFileSync(
1500
+ manifestsResource.filePath(finalManifestName),
1501
+ JSON.stringify(rewrittenManifest, null, 2)
1502
+ );
1503
+ jsonResponse(res, 200, {
1504
+ ok: true,
1505
+ manifest: finalManifestName,
1506
+ renames
1507
+ });
1508
+ }
891
1509
  function methodNotAllowed({ res }) {
892
1510
  jsonResponse(res, 405, { error: "Method not allowed" });
893
1511
  }
@@ -921,21 +1539,29 @@ ${COMPONENT_OVERRIDES_END}
921
1539
  { method: "GET", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
922
1540
  { method: "PUT", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
923
1541
  { method: "DELETE", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
924
- // Presets — list / active / production are exact strings, must run before regexes
925
- { method: "GET", pattern: PRESETS_ROUTE, handler: handleListPresets },
926
- { method: "GET", pattern: PRESETS_ACTIVE_ROUTE, handler: handleGetActivePreset },
927
- { method: "PUT", pattern: PRESETS_ACTIVE_ROUTE, handler: handleSetActivePreset },
928
- { method: "GET", pattern: PRESETS_PRODUCTION_ROUTE, handler: handleGetProductionPreset },
929
- { method: "PUT", pattern: PRESETS_PRODUCTION_ROUTE, handler: handleSetProductionPreset },
930
- // Presets :name/apply (more specific than :name)
931
- { method: "PUT", pattern: PRESET_APPLY_REGEX, handler: handleApplyPreset },
932
- { method: "POST", pattern: PRESET_APPLY_REGEX, handler: methodNotAllowed },
933
- { method: "GET", pattern: PRESET_APPLY_REGEX, handler: methodNotAllowed },
934
- { method: "DELETE", pattern: PRESET_APPLY_REGEX, handler: methodNotAllowed },
935
- // Presets :name CRUD (broadest preset route, runs last)
936
- { method: "GET", pattern: PRESET_BY_NAME_REGEX, handler: handlePresetByName },
937
- { method: "PUT", pattern: PRESET_BY_NAME_REGEX, handler: handlePresetByName },
938
- { method: "DELETE", pattern: PRESET_BY_NAME_REGEX, handler: handlePresetByName }
1542
+ // Manifests — list / active are exact strings, must run before regexes
1543
+ { method: "GET", pattern: MANIFESTS_ROUTE, handler: handleListManifests },
1544
+ { method: "GET", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleGetActiveManifest },
1545
+ { method: "PUT", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleSetActiveManifest },
1546
+ // Manifests exact import route runs before :name regexes
1547
+ { method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
1548
+ { method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
1549
+ { method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
1550
+ { method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
1551
+ // Manifests :name/apply (more specific than :name)
1552
+ { method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
1553
+ { method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
1554
+ { method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
1555
+ { method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
1556
+ // Manifests :name/export (more specific than :name)
1557
+ { method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
1558
+ { method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
1559
+ { method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
1560
+ { method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
1561
+ // Manifests — :name CRUD (broadest manifest route, runs last)
1562
+ { method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
1563
+ { method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
1564
+ { method: "DELETE", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName }
939
1565
  ];
940
1566
  return {
941
1567
  name: "theme-file-api",
@@ -950,7 +1576,8 @@ ${COMPONENT_OVERRIDES_END}
950
1576
  configureServer(server) {
951
1577
  ensureThemesDir();
952
1578
  ensureComponentConfigsDir();
953
- ensurePresetsDir();
1579
+ ensureManifestsDir();
1580
+ regenerateTokensCss();
954
1581
  server.middlewares.use(async (req, res, next) => {
955
1582
  const handled = await dispatch(req, res, routes);
956
1583
  if (!handled) next();