@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
@@ -7,6 +7,7 @@ export const TYPE_FONT_PROPS = [
7
7
  { key: 'sizeVariable', label: 'font size', defaultGroupKey: 'font-size' },
8
8
  { key: 'weightVariable', label: 'font weight', defaultGroupKey: 'font-weight' },
9
9
  { key: 'lineHeightVariable', label: 'line height', defaultGroupKey: 'line-height' },
10
+ { key: 'letterSpacingVariable', label: 'letter spacing', defaultGroupKey: 'letter-spacing' },
10
11
  ] as const satisfies ReadonlyArray<{
11
12
  key: keyof TypeGroupConfig;
12
13
  label: string;
@@ -3,37 +3,47 @@ import { writable, type Writable, type Readable } from 'svelte/store';
3
3
 
4
4
  const KEY = Symbol('editor-context');
5
5
 
6
+ export type VariantOption = { value: string; label: string };
7
+
6
8
  export type EditorContext = {
7
- /** Per-variable rank used by TokenLayout to align linked rows; null when no linked block. */
9
+ /** Per-variable rank for TokenLayout row alignment; null when no linked block. */
8
10
  linkedOrder: Readable<Map<string, number> | null>;
9
- /** Variant currently focused in the preview when multiple sibling variants exist. */
11
+ /** Focused preview variant when siblings exist. */
10
12
  focusedVariant: Writable<string | null>;
11
- /** Cross-group hint for which state tab to activate. VariantGroups whose `stateNames`
12
- contain the value adopt it as their `activeTab`; others ignore it. Used to forward
13
- LinkageChart row clicks to the state tab strip when the chart spans states. */
13
+ /** Variants for this editor; empty when single-variant. Focused VariantGroup renders the tab strip itself. */
14
+ variants: Readable<VariantOption[]>;
15
+ /** Cross-group state-tab hint; VariantGroups whose stateNames contain it adopt as activeTab. */
14
16
  focusedState: Writable<string | null>;
15
- /** Variable currently hovered in either the per-state Properties grid or the
16
- Linked-properties block. Bidirectional cue: a hover in one surface lights up
17
- the matching row in the other so the user can see the linkage at a glance. */
17
+ /** Hovered variable in Properties grid or Linked block; bidirectional highlight. */
18
18
  hoveredLinkedVariable: Writable<string | null>;
19
+ /** Copy-from "Preserve color families" toggle, scoped to this editor. Lives
20
+ on context (not local to CopyFromMenu) so it survives VariantGroup remounts
21
+ when the user changes which variant is focused. */
22
+ preserveColorFamily: Writable<boolean>;
19
23
  };
20
24
 
21
- /** Internal mutable handle used by ComponentEditorBase. */
25
+ /** Mutable handle for ComponentEditorBase. */
22
26
  export type EditorContextInternal = EditorContext & {
23
27
  _linkedOrder: Writable<Map<string, number> | null>;
28
+ _variants: Writable<VariantOption[]>;
24
29
  };
25
30
 
26
31
  export function createEditorContext(): EditorContextInternal {
27
32
  const _linkedOrder = writable<Map<string, number> | null>(null);
33
+ const _variants = writable<VariantOption[]>([]);
28
34
  const focusedVariant = writable<string | null>(null);
29
35
  const focusedState = writable<string | null>(null);
30
36
  const hoveredLinkedVariable = writable<string | null>(null);
37
+ const preserveColorFamily = writable<boolean>(false);
31
38
  const ctx: EditorContextInternal = {
32
39
  linkedOrder: _linkedOrder,
33
40
  focusedVariant,
41
+ variants: _variants,
34
42
  focusedState,
35
43
  hoveredLinkedVariable,
44
+ preserveColorFamily,
36
45
  _linkedOrder,
46
+ _variants,
37
47
  };
38
48
  setContext(KEY, ctx);
39
49
  return ctx;
@@ -3,8 +3,8 @@ import {
3
3
  isComponentPropertyLinked,
4
4
  getComponentPropertySiblings,
5
5
  editorState,
6
- } from '../../lib/editorStore';
7
- import type { CssVarRef } from '../../lib/editorTypes';
6
+ } from '../../core/store/editorStore';
7
+ import type { CssVarRef } from '../../core/store/editorTypes';
8
8
  import type { Token } from './types';
9
9
 
10
10
  function aliasKey(ref: CssVarRef | undefined): string {
@@ -13,6 +13,26 @@ export type Token = {
13
13
  /** When the linked block collapses several same-label same-value rows into one,
14
14
  the surviving row carries the other groupKey leads here so writes co-propagate. */
15
15
  mergeVariables?: string[];
16
+ /** When false, a padding-shaped token renders only the single-value control
17
+ (no split-to-sides affordance). For tokens consumed via a one-axis CSS
18
+ property like `padding-bottom: var(--x)`, splitting yields side values
19
+ that have nowhere to render — hide the toggle so users can't get into
20
+ that state. Defaults to true for padding-shaped tokens. */
21
+ splittable?: boolean;
22
+ /** Optional element grouping (e.g. 'frame', 'header', 'body'). When a state
23
+ has tokens or type-groups tagged with two or more distinct elements,
24
+ StateBlock partitions the panel into labeled subsections — typography
25
+ and properties for each element render together. */
26
+ element?: string;
27
+ /** Hint to the editor that this token's alias is a structured payload
28
+ (currently only `kind: 'gradient'`). Drives Copy-from's per-kind
29
+ branch — gradient aliases need family-swap of in-family stop colors
30
+ rather than a verbatim ref copy. */
31
+ kind?: 'gradient';
32
+ /** Color-family slug for this token's owning variant (e.g. `brand`,
33
+ `accent`). Set on gradient-kind tokens so Copy-from's family-swap
34
+ can compute the src→dst family substitution. */
35
+ family?: string;
16
36
  };
17
37
 
18
38
  /** Editor type-group: a fieldset containing a coordinated set of typography tokens
@@ -32,8 +52,13 @@ export type TypeGroupConfig = {
32
52
  weightLabel?: string;
33
53
  lineHeightVariable?: string;
34
54
  lineHeightLabel?: string;
55
+ letterSpacingVariable?: string;
56
+ letterSpacingLabel?: string;
35
57
  outlineWidthVariable?: string;
36
58
  outlineWidthLabel?: string;
37
59
  outlineColorVariable?: string;
38
60
  outlineColorLabel?: string;
61
+ /** See `Token.element` — when present, StateBlock groups this fieldset under
62
+ the matching element subsection. */
63
+ element?: string;
39
64
  };
@@ -16,4 +16,12 @@
16
16
  export const KNOWN_COMPONENT_CONFIG_KEYS: ReadonlySet<string> = new Set([
17
17
  '--dialog-confirm-variant',
18
18
  '--dialog-cancel-variant',
19
+ // SectionDivider per-variant `color-family` is editor metadata that drives
20
+ // the family-swap rewrite on aliases. It is not a runtime CSS value, so it
21
+ // stays in the config bucket. The other intrinsics (align, hairline,
22
+ // eyebrow/description visibility, eyebrow text-transform) now flow through
23
+ // the alias bucket as cascading CSS vars — see the 2026-05-22 migration.
24
+ '--sectiondivider-lg-color-family',
25
+ '--sectiondivider-md-color-family',
26
+ '--sectiondivider-sm-color-family',
19
27
  ]);
@@ -1,5 +1,5 @@
1
- import type { ComponentConfig, ComponentConfigMeta } from './themeTypes';
2
- import { versionedFileResource } from './files/versionedFileResource';
1
+ import type { AliasDiskValue, ComponentConfig, ComponentConfigMeta } from '../themes/themeTypes';
2
+ import { versionedFileResource } from '../storage/files/versionedFileResourceClient';
3
3
 
4
4
  /**
5
5
  * REST client for per-component config files. Parallel to `themeService.ts`
@@ -23,7 +23,7 @@ export interface ComponentSummary {
23
23
  export interface ComponentProductionInfo {
24
24
  fileName: string;
25
25
  name: string;
26
- aliases: Record<string, string>;
26
+ aliases: Record<string, AliasDiskValue>;
27
27
  }
28
28
 
29
29
  export interface ComponentConfigList {
@@ -1,8 +1,8 @@
1
1
  import { get } from 'svelte/store';
2
- import type { ComponentConfig } from './themeTypes';
3
- import { editorState, markComponentSaved } from './editorStore';
4
- import type { CssVarRef } from './editorTypes';
5
- import { CURRENT_COMPONENT_SCHEMA_VERSION } from './migrations';
2
+ import type { AliasDiskValue, ComponentConfig } from '../themes/themeTypes';
3
+ import { editorState, markComponentSaved } from '../store/editorStore';
4
+ import type { CssVarRef } from '../store/editorTypes';
5
+ import { CURRENT_COMPONENT_SCHEMA_VERSION } from '../themes/migrations';
6
6
  import {
7
7
  listComponentConfigs,
8
8
  saveComponentConfig,
@@ -12,7 +12,7 @@ import {
12
12
  /**
13
13
  * Save the current in-memory state of a component to its active file. Mirrors
14
14
  * the `persist` flow inside `ComponentFileManager.svelte` so callers without a
15
- * file-manager instance (e.g. the unsaved-components dialog in PresetFileManager)
15
+ * file-manager instance (e.g. an unsaved-components dialog upstream)
16
16
  * can save a dirty component without duplicating the schema-version + aliases
17
17
  * stringification logic.
18
18
  *
@@ -24,8 +24,10 @@ export type SaveActiveComponentResult =
24
24
  | { ok: true; fileName: string; displayName: string }
25
25
  | { ok: false; reason: 'default' | 'no-state' | 'error'; error?: unknown };
26
26
 
27
- function refToString(ref: CssVarRef): string {
28
- return ref.kind === 'token' ? ref.name : ref.value;
27
+ function refToDiskValue(ref: CssVarRef): AliasDiskValue {
28
+ if (ref.kind === 'token') return ref.name;
29
+ if (ref.kind === 'literal') return ref.value;
30
+ return { kind: 'gradient', value: ref.value };
29
31
  }
30
32
 
31
33
  export async function saveActiveComponentConfig(
@@ -43,8 +45,8 @@ export async function saveActiveComponentConfig(
43
45
  const displayName = active?.name ?? fileName;
44
46
 
45
47
  const now = new Date().toISOString();
46
- const aliases: Record<string, string> = {};
47
- for (const [k, ref] of Object.entries(slice.aliases)) aliases[k] = refToString(ref);
48
+ const aliases: Record<string, AliasDiskValue> = {};
49
+ for (const [k, ref] of Object.entries(slice.aliases)) aliases[k] = refToDiskValue(ref);
48
50
 
49
51
  const data: ComponentConfig = {
50
52
  name: displayName,
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Centralised "flash a transient status, then revert to idle" helper for the
3
+ * file managers (theme / component / manifest). Each pulse — saved, error,
4
+ * adopted — shares the same timing so the UI stays in sync.
5
+ */
6
+
7
+ export const FLASH_DURATION_MS = 2000;
8
+
9
+ export interface FlashOptions<S extends string> {
10
+ /** State to revert to after the pulse. Defaults to `'idle'`. */
11
+ idleState?: S;
12
+ /** How long the transient state is shown before reverting. */
13
+ durationMs?: number;
14
+ /** Side effect fired alongside the revert (e.g. clear an inline message). */
15
+ onIdle?: () => void;
16
+ }
17
+
18
+ export function flashStatus<S extends string>(
19
+ set: (state: S) => void,
20
+ transientState: S,
21
+ options: FlashOptions<S> = {},
22
+ ): void {
23
+ const idleState = options.idleState ?? ('idle' as S);
24
+ const durationMs = options.durationMs ?? FLASH_DURATION_MS;
25
+ set(transientState);
26
+ setTimeout(() => {
27
+ set(idleState);
28
+ options.onIdle?.();
29
+ }, durationMs);
30
+ }
@@ -5,8 +5,8 @@ import type {
5
5
  FontStackSlot,
6
6
  FontStackVariable,
7
7
  SystemCascadePreset,
8
- } from './themeTypes';
9
- import { setCssVar, getSyncedDocuments } from './cssVarSync';
8
+ } from '../themes/themeTypes';
9
+ import { setCssVar, getSyncedDocuments } from '../cssVarSync';
10
10
 
11
11
  export const SYSTEM_CASCADES: Record<SystemCascadePreset, string> = {
12
12
  'system-ui-sans':
@@ -1,7 +1,7 @@
1
- import type { FontFamily, FontSource, FontStack, Theme } from './themeTypes';
2
- import frauncesRomanLatin from '../styles/fonts/Fraunces/Fraunces-roman-latin.woff2?url';
3
- import frauncesItalicLatin from '../styles/fonts/Fraunces/Fraunces-italic-latin.woff2?url';
4
- import manropeLatin from '../styles/fonts/Manrope/Manrope-latin.woff2?url';
1
+ import type { FontFamily, FontSource, FontStack, Theme } from '../themes/themeTypes';
2
+ import frauncesRomanLatin from '../../../system/styles/fonts/Fraunces/Fraunces-roman-latin.woff2?url';
3
+ import frauncesItalicLatin from '../../../system/styles/fonts/Fraunces/Fraunces-italic-latin.woff2?url';
4
+ import manropeLatin from '../../../system/styles/fonts/Manrope/Manrope-latin.woff2?url';
5
5
 
6
6
  function makeId(prefix: string): string {
7
7
  return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
@@ -1,4 +1,4 @@
1
- import type { FontFamily, FontSource, FontSourceKind } from './themeTypes';
1
+ import type { FontFamily, FontSource, FontSourceKind } from '../themes/themeTypes';
2
2
 
3
3
  export interface ParsedFamily {
4
4
  name: string;
@@ -0,0 +1,171 @@
1
+ import type { Manifest, ManifestMeta, ManifestBundle, Theme, ComponentConfig } from '../themes/themeTypes';
2
+ import { versionedFileResource } from '../storage/files/versionedFileResourceClient';
3
+ import { listComponents } from '../components/componentConfigService';
4
+ import { getActiveTheme } from '../themes/themeService';
5
+
6
+ /**
7
+ * REST client for manifest files. A manifest references one theme file +
8
+ * one config file per component (by basename). The active manifest is the
9
+ * single live snapshot: theme and component Adopts patch its refs server-side.
10
+ *
11
+ * `default` is the protected baseline — cannot be overwritten or deleted, and
12
+ * Adopts return 409 ACTIVE_IS_PROTECTED while it is active.
13
+ */
14
+
15
+ const manifestsResource = versionedFileResource<Manifest, ManifestMeta, never>({
16
+ baseUrl: '/api/manifests',
17
+ });
18
+
19
+ export const listManifests = async (): Promise<ManifestMeta[]> => {
20
+ const data = await manifestsResource.list();
21
+ return data.files;
22
+ };
23
+
24
+ export const loadManifest = (fileName: string): Promise<Manifest> =>
25
+ manifestsResource.load(fileName);
26
+ export const saveManifest = (fileName: string, data: Manifest): Promise<void> =>
27
+ manifestsResource.save(fileName, data);
28
+ export const deleteManifest = (fileName: string): Promise<void> =>
29
+ manifestsResource.remove(fileName);
30
+ export const getActiveManifest = (): Promise<Manifest | null> => manifestsResource.getActive();
31
+ export const setActiveManifest = (fileName: string): Promise<void> =>
32
+ manifestsResource.setActive(fileName);
33
+
34
+ export interface ApplyManifestResult {
35
+ ok: boolean;
36
+ manifest: Manifest;
37
+ theme: Theme;
38
+ componentConfigs: Record<string, ComponentConfig>;
39
+ }
40
+
41
+ /**
42
+ * Server-side atomic apply: validate every referenced file exists, flip the
43
+ * theme + each component's `_active.json` and `_production.json` pointers,
44
+ * sync tokens.css/fonts.css from the new theme, mark the manifest active, and
45
+ * return the resolved theme + component configs in one payload. Clients
46
+ * usually follow with a full page reload — manifest load is a "blow up the
47
+ * world" action.
48
+ */
49
+ export async function applyManifest(fileName: string): Promise<ApplyManifestResult> {
50
+ const res = await fetch(`/api/manifests/${encodeURIComponent(fileName)}/apply`, {
51
+ method: 'PUT',
52
+ });
53
+ if (!res.ok) {
54
+ const err = await res.json().catch(() => ({ error: 'Apply failed' }));
55
+ throw new Error(err.error || 'Apply failed');
56
+ }
57
+ return res.json();
58
+ }
59
+
60
+ /**
61
+ * Snapshot the currently-active theme + component-config file pointers into
62
+ * a new manifest file and set it active. Used by the manifest panel's Save As
63
+ * action and by the SaveAs-then-Adopt recovery flow when active is `default`.
64
+ */
65
+ export async function saveAsManifest(
66
+ fileName: string,
67
+ displayName: string,
68
+ ): Promise<void> {
69
+ const activeTheme = await getActiveTheme();
70
+ if (!activeTheme || !activeTheme._fileName) {
71
+ throw new Error('No active theme on disk to capture');
72
+ }
73
+ const components = await listComponents();
74
+ const componentConfigs: Record<string, string> = {};
75
+ for (const c of components) {
76
+ componentConfigs[c.name] = c.activeFile || 'default';
77
+ }
78
+ const now = new Date().toISOString();
79
+ const manifest: Manifest = {
80
+ name: displayName,
81
+ createdAt: now,
82
+ updatedAt: now,
83
+ theme: activeTheme._fileName,
84
+ componentConfigs,
85
+ };
86
+ await saveManifest(fileName, manifest);
87
+ await setActiveManifest(fileName);
88
+ }
89
+
90
+ /**
91
+ * Re-snapshot the editor's current active pointers into the *currently active*
92
+ * manifest file. Used by the manifest panel's Save action. Server rejects with
93
+ * 403 if active is `default` (protected).
94
+ */
95
+ export async function saveActiveManifest(displayName?: string): Promise<void> {
96
+ const active = await getActiveManifest();
97
+ if (!active || !active._fileName) {
98
+ throw new Error('No active manifest');
99
+ }
100
+ const activeTheme = await getActiveTheme();
101
+ if (!activeTheme || !activeTheme._fileName) {
102
+ throw new Error('No active theme on disk');
103
+ }
104
+ const components = await listComponents();
105
+ const componentConfigs: Record<string, string> = {};
106
+ for (const c of components) {
107
+ componentConfigs[c.name] = c.activeFile || 'default';
108
+ }
109
+ const manifest: Manifest = {
110
+ name: displayName ?? active.name,
111
+ createdAt: active.createdAt,
112
+ updatedAt: new Date().toISOString(),
113
+ theme: activeTheme._fileName,
114
+ componentConfigs,
115
+ };
116
+ await saveManifest(active._fileName, manifest);
117
+ }
118
+
119
+ export interface ImportManifestResult {
120
+ ok: boolean;
121
+ /** Final manifest filename (may be renamed if it collided with an existing one). */
122
+ manifest: string;
123
+ /** Keyed `theme:<orig>` / `componentConfig:<comp>/<orig>` / `manifest:<orig>` → final name. */
124
+ renames: Record<string, string>;
125
+ }
126
+
127
+ /**
128
+ * Fetch the manifest as a self-contained `ManifestBundle` and trigger a
129
+ * browser download. Hidden-anchor trick — no infrastructure beyond the
130
+ * existing GET `/api/manifests/:name/export` endpoint.
131
+ *
132
+ * See temp/manifest-robustness-plan.md §11.
133
+ */
134
+ export async function exportManifest(fileName: string): Promise<void> {
135
+ const res = await fetch(`/api/manifests/${encodeURIComponent(fileName)}/export`);
136
+ if (!res.ok) {
137
+ const err = await res.json().catch(() => ({ error: 'Export failed' }));
138
+ throw new Error(err.error || 'Export failed');
139
+ }
140
+ const blob = await res.blob();
141
+ const url = URL.createObjectURL(blob);
142
+ try {
143
+ const a = document.createElement('a');
144
+ a.href = url;
145
+ a.download = `${fileName}.bundle.json`;
146
+ document.body.appendChild(a);
147
+ a.click();
148
+ a.remove();
149
+ } finally {
150
+ URL.revokeObjectURL(url);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * POST a `ManifestBundle` to the import endpoint. Server materialises the
156
+ * inlined theme + component configs as fresh files (renaming on collision),
157
+ * rewrites the manifest's pointers, and returns the final manifest name plus
158
+ * the rename map so the UI can surface what got renamed.
159
+ */
160
+ export async function importManifest(bundle: ManifestBundle): Promise<ImportManifestResult> {
161
+ const res = await fetch('/api/manifests/import', {
162
+ method: 'POST',
163
+ headers: { 'Content-Type': 'application/json' },
164
+ body: JSON.stringify(bundle),
165
+ });
166
+ if (!res.ok) {
167
+ const err = await res.json().catch(() => ({ error: 'Import failed' }));
168
+ throw new Error(err.error || 'Import failed');
169
+ }
170
+ return res.json();
171
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Family-aware token rewrites.
3
+ *
4
+ * Color tokens encode their palette family as a hyphen segment in the slug
5
+ * (e.g. `--border-canvas-strong` → `canvas`, `--surface-accent-low` →
6
+ * `accent`). Text tokens are special-cased: `--text-primary` carries no
7
+ * family, `--text-{family}` and `--text-{family}-{step}` do, and `--text-{step}`
8
+ * resolves to the neutral text scale.
9
+ *
10
+ * These helpers let editor flows (variant family swap, per-stop monochrome
11
+ * snap) rewrite a token slug without re-implementing the parser each time.
12
+ */
13
+
14
+ export const KNOWN_FAMILIES = [
15
+ 'neutral', 'alternate', 'canvas', 'brand',
16
+ 'accent', 'special', 'success', 'warning', 'info', 'danger',
17
+ ] as const;
18
+
19
+ export type FamilyName = (typeof KNOWN_FAMILIES)[number];
20
+
21
+ export const TEXT_STEPS = ['primary', 'secondary', 'tertiary', 'muted', 'disabled'] as const;
22
+
23
+ export type TextStep = (typeof TEXT_STEPS)[number];
24
+
25
+ /** Parse a text-scale token name into family + step. Returns null for any
26
+ * non-`--text-*` slug or one whose parts don't resolve to a known step. */
27
+ export function parseTextToken(colorRef: string): { family: FamilyName; step: TextStep } | null {
28
+ if (!colorRef.startsWith('--text-')) return null;
29
+ const rest = colorRef.slice('--text-'.length);
30
+ const parts = rest.split('-');
31
+ if (parts.length === 1) {
32
+ const p = parts[0];
33
+ if ((TEXT_STEPS as readonly string[]).includes(p)) return { family: 'neutral', step: p as TextStep };
34
+ if ((KNOWN_FAMILIES as readonly string[]).includes(p)) return { family: p as FamilyName, step: 'primary' };
35
+ return null;
36
+ }
37
+ if (parts.length === 2) {
38
+ const [fam, step] = parts;
39
+ if (
40
+ (KNOWN_FAMILIES as readonly string[]).includes(fam)
41
+ && (TEXT_STEPS as readonly string[]).includes(step)
42
+ ) {
43
+ return { family: fam as FamilyName, step: step as TextStep };
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /** Inverse of parseTextToken — assemble a `--text-*` slug from family + step.
50
+ * `neutral` family flattens to `--text-{step}`; `primary` step on a named
51
+ * family flattens to `--text-{family}` (matches authoring conventions). */
52
+ export function buildTextToken(family: FamilyName | string, step: TextStep | string): string {
53
+ if (family === 'neutral') return `--text-${step}`;
54
+ return step === 'primary' ? `--text-${family}` : `--text-${family}-${step}`;
55
+ }
56
+
57
+ /** Pull the family marker out of a token slug, or null if none of the
58
+ * hyphen-delimited parts is a known family. */
59
+ export function detectFamily(colorRef: string): FamilyName | null {
60
+ if (!colorRef.startsWith('--')) return null;
61
+ const parts = colorRef.slice(2).split('-');
62
+ for (const p of parts) {
63
+ if ((KNOWN_FAMILIES as readonly string[]).includes(p)) return p as FamilyName;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /** Rewrite the family marker in a slug from `oldFamily` to `newFamily`.
69
+ * Returns the input unchanged when the slug doesn't reference `oldFamily`
70
+ * (so unrelated tokens, literals, and out-of-family stops fall through). */
71
+ export function swapTokenFamily(name: string, oldFamily: string, newFamily: string): string {
72
+ if (!name.startsWith('--')) return name;
73
+ const text = parseTextToken(name);
74
+ if (text) {
75
+ if (text.family !== oldFamily) return name;
76
+ return buildTextToken(newFamily, text.step);
77
+ }
78
+ const parts = name.slice(2).split('-');
79
+ const idx = parts.indexOf(oldFamily);
80
+ if (idx < 0) return name;
81
+ parts[idx] = newFamily;
82
+ return '--' + parts.join('-');
83
+ }
84
+
85
+ /** Coerce a slug to the given target family. Unlike swapTokenFamily this
86
+ * doesn't need to know the source family — it autodetects, then rewrites.
87
+ * Returns the input unchanged if no family is present (e.g. literal colors,
88
+ * `transparent`, or a `--text-primary` neutral). */
89
+ export function snapTokenToFamily(name: string, targetFamily: string): string {
90
+ if (!name.startsWith('--')) return name;
91
+ const text = parseTextToken(name);
92
+ if (text) {
93
+ if (text.family === targetFamily) return name;
94
+ return buildTextToken(targetFamily, text.step);
95
+ }
96
+ const fromFamily = detectFamily(name);
97
+ if (!fromFamily || fromFamily === targetFamily) return name;
98
+ return swapTokenFamily(name, fromFamily, targetFamily);
99
+ }
@@ -14,8 +14,8 @@
14
14
  */
15
15
 
16
16
  import { hexToOklch, oklchToHex, gamutClamp } from './oklch';
17
- import { type CurveAnchor, sampleCurve, makeAnchor } from '../ui/curveEngine';
18
- import type { PaletteConfig } from './themeTypes';
17
+ import { type CurveAnchor, sampleCurve, makeAnchor } from '../../ui/curveEngine';
18
+ import type { PaletteConfig } from '../themes/themeTypes';
19
19
 
20
20
  export type PaletteMode = 'chromatic' | 'gray';
21
21
 
@@ -298,3 +298,72 @@ export function palettesToVars(palettes: Record<string, PaletteConfig>): Record<
298
298
  }
299
299
  return out;
300
300
  }
301
+
302
+ const HEX_RE = /^#[0-9a-f]{6}$/i;
303
+
304
+ /**
305
+ * Reconcile palette typed state against the catch-all `cssVariables` bag and
306
+ * report the set of variable names the typed slice now owns. Two operations:
307
+ *
308
+ * - **Snap** (gated by `_imported`): for any palette whose `_imported` flag
309
+ * is true, the imported `--color-{ns}-500` value is treated as the
310
+ * authoritative anchor. Chromatic palettes get `baseColor` snapped to it;
311
+ * gray palettes (Neutral/Alternate) get `tintHue` + `tintChroma` derived
312
+ * from it via OKLCH. The flag is then cleared. Editor-authored palettes
313
+ * (no `_imported`) are left untouched — see `temp/manifest-robustness-plan.md`
314
+ * §9 for why "snap on any divergence" was wrong: it would have flipped
315
+ * `themes/default.json`'s accent from teal to olive on first read.
316
+ *
317
+ * - **Consume** (always): every variable the palette's derivation produces
318
+ * is reported in `consumed` so the caller can strip it from
319
+ * `cssVariables`. The renderer (`editorRenderer.ts:42`) overlays
320
+ * `palettesToVars(palettes)` on top of `cssVariables` regardless, so
321
+ * stripped keys were dead data anyway. Stripping makes the file
322
+ * invariant explicit: catch-all carries only tokens no typed slice owns.
323
+ *
324
+ * Returns the updated palette map plus the two sets. Pure: no DOM, no I/O,
325
+ * no module state. Idempotent on second call with the same input (no anchor
326
+ * to snap to after first call's strip).
327
+ */
328
+ export function reconcilePalettesFromCssVars(
329
+ palettes: Record<string, PaletteConfig>,
330
+ cssVars: Record<string, string>,
331
+ ): {
332
+ palettes: Record<string, PaletteConfig>;
333
+ consumed: ReadonlySet<string>;
334
+ snapped: ReadonlySet<string>;
335
+ } {
336
+ const next: Record<string, PaletteConfig> = structuredClone(palettes);
337
+ const consumed = new Set<string>();
338
+ const snapped = new Set<string>();
339
+
340
+ for (const spec of PALETTE_SPECS) {
341
+ const current = next[spec.label];
342
+ if (current === undefined) continue;
343
+
344
+ if (current._imported === true) {
345
+ const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
346
+ if (anchorHex && HEX_RE.test(anchorHex.trim())) {
347
+ const hex = anchorHex.trim();
348
+ if (spec.mode === 'gray') {
349
+ const { c, h } = hexToOklch(hex);
350
+ next[spec.label] = { ...current, tintHue: h, tintChroma: c, _imported: false };
351
+ } else {
352
+ next[spec.label] = { ...current, baseColor: hex, _imported: false };
353
+ }
354
+ snapped.add(spec.label);
355
+ } else {
356
+ // No anchor in cssVariables to snap to — flag has nothing to do; clear
357
+ // it so subsequent calls don't keep checking. Safe because the renderer
358
+ // is going to overlay palettesToVars anyway.
359
+ next[spec.label] = { ...current, _imported: false };
360
+ }
361
+ }
362
+
363
+ for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
364
+ consumed.add(k);
365
+ }
366
+ }
367
+
368
+ return { palettes: next, consumed, snapped };
369
+ }
@@ -19,17 +19,18 @@
19
19
  */
20
20
 
21
21
  import { derived, type Readable } from 'svelte/store';
22
- import tokensCss from '../styles/tokens.css?raw';
23
- import { editorState } from './editorStore';
24
- import type { EditorState } from './editorTypes';
25
- import { extractGlobalRootBody } from './parsers/globalRootBlock';
22
+ import tokensCss from '../../../system/styles/tokens.css?raw';
23
+ import { editorState } from '../store/editorStore';
24
+ import type { EditorState } from '../store/editorTypes';
25
+ import { extractGlobalRootBody } from '../themes/parsers/globalRootBlock';
26
+ import { formatGradientValue } from '../themes/slices/gradients';
26
27
 
27
28
  // Re-exported for tests and downstream consumers that previously imported it
28
29
  // from this module. The canonical implementation lives in `./parsers/globalRootBlock`
29
30
  // so the dev-server vite plugin can share it.
30
31
  export { extractGlobalRootBody };
31
32
 
32
- const componentSources = import.meta.glob('../components/*.svelte', {
33
+ const componentSources = import.meta.glob('../../../system/components/*.svelte', {
33
34
  query: '?raw',
34
35
  import: 'default',
35
36
  eager: true,
@@ -111,7 +112,9 @@ function buildOverlayRegistry(
111
112
  const overrides = new Map<string, string>();
112
113
  for (const slice of Object.values(components)) {
113
114
  for (const [varName, ref] of Object.entries(slice.aliases)) {
114
- overrides.set(varName, ref.kind === 'token' ? `var(${ref.name})` : ref.value);
115
+ if (ref.kind === 'token') overrides.set(varName, `var(${ref.name})`);
116
+ else if (ref.kind === 'literal') overrides.set(varName, ref.value);
117
+ else overrides.set(varName, formatGradientValue(ref.value));
115
118
  }
116
119
  }
117
120
  const getDeclared = (v: string): string | null =>