@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
@@ -6,16 +6,19 @@
6
6
  * to gradient state, so we don't have to refactor UIPaletteSelector itself.
7
7
  */
8
8
  import UIPaletteSelector from './UIPaletteSelector.svelte';
9
- import { setCssVar, removeCssVar } from '../lib/cssVarSync';
9
+ import { setCssVar, removeCssVar } from '../core/cssVarSync';
10
10
 
11
11
  interface Props {
12
12
  stopId: string; // unique key (e.g. gradient-var + stop index)
13
13
  color: string; // token name like '--color-brand-500'
14
14
  opacity?: number; // 0–100
15
+ /** Forwarded to UIPaletteSelector to scope picks to a color family.
16
+ * See UIPaletteSelector's `familyFilter` for accepted values. */
17
+ familyFilter?: string | null;
15
18
  onchange?: (payload: { color: string; opacity: number }) => void;
16
19
  }
17
20
 
18
- let { stopId, color, opacity = 100, onchange }: Props = $props();
21
+ let { stopId, color, opacity = 100, familyFilter = null, onchange }: Props = $props();
19
22
 
20
23
  /** Scratch var the embedded picker reads/writes; isolated per stop. */
21
24
  let scratchVar = $derived(`--__grad-stop-${stopId}`);
@@ -26,10 +29,15 @@
26
29
  return `color-mix(in srgb, ${base} ${Math.round(o)}%, transparent)`;
27
30
  }
28
31
 
29
- /** Parse a scratch var write back into structured stop fields. */
32
+ /** Parse a scratch var write back into structured stop fields. UIPaletteSelector's
33
+ * "None" choice writes the literal `transparent`; we round-trip it as a stop
34
+ * whose color is the keyword itself — `formatGradientStopColor` already passes
35
+ * non-`--` colors through verbatim, so the gradient (or solid first-stop) ends
36
+ * up painting `transparent`. */
30
37
  function parseScratch(raw: string): { color: string; opacity: number } | null {
31
38
  const trimmed = raw.trim();
32
39
  if (!trimmed) return null;
40
+ if (trimmed === 'transparent') return { color: 'transparent', opacity: 100 };
33
41
  const mixMatch = trimmed.match(/^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+(?:\.\d+)?)%,\s*transparent\)$/i);
34
42
  if (mixMatch) {
35
43
  return { color: mixMatch[1], opacity: parseFloat(mixMatch[2]) };
@@ -60,4 +68,4 @@
60
68
  }
61
69
  </script>
62
70
 
63
- <UIPaletteSelector variable={scratchVar} onchange={handleChange} />
71
+ <UIPaletteSelector variable={scratchVar} {familyFilter} onchange={handleChange} />
@@ -0,0 +1,438 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import type { ManifestMeta } from '../core/themes/themeTypes';
4
+ import {
5
+ listManifests,
6
+ deleteManifest,
7
+ getActiveManifest,
8
+ applyManifest,
9
+ saveAsManifest,
10
+ saveActiveManifest,
11
+ exportManifest,
12
+ importManifest,
13
+ } from '../core/manifests/manifestService';
14
+ import { dirty, componentDirty } from '../core/store/editorStore';
15
+ import { productionRevision, activeManifest } from '../core/productionPulse';
16
+ import { flashStatus } from '../core/flashStatus';
17
+ import UIInfoPopover from './UIInfoPopover.svelte';
18
+ import FileLoadList from './FileLoadList.svelte';
19
+ import FilePill from './FilePill.svelte';
20
+ import SaveAsDialog from '../component-editor/scaffolding/SaveAsDialog.svelte';
21
+
22
+ let files: ManifestMeta[] = $state([]);
23
+ let showFileList = $state(false);
24
+ let saveAsDialog = $state(false);
25
+ let saveStatus: 'idle' | 'saving' | 'saved' | 'error' = $state('idle');
26
+
27
+ let activeFileName = $state('default');
28
+ let currentDisplayName = $state('Default');
29
+ let activeIsProtected = $derived(activeFileName === 'default');
30
+
31
+ type SaveState = 'idle' | 'saving' | 'saved' | 'error';
32
+ const setSaveStatus = (s: SaveState) => (saveStatus = s);
33
+
34
+ let dirtyComponentCount = $derived(
35
+ Object.values($componentDirty).filter(Boolean).length,
36
+ );
37
+ let editorDirty = $derived($dirty || dirtyComponentCount > 0);
38
+
39
+ async function refreshFiles() {
40
+ try {
41
+ files = await listManifests();
42
+ } catch {
43
+ // silent — empty list
44
+ }
45
+ }
46
+
47
+ async function refreshActive() {
48
+ try {
49
+ const active = await getActiveManifest();
50
+ if (active) {
51
+ activeFileName = active._fileName ?? 'default';
52
+ // Default is always labeled "Default" regardless of what the on-disk
53
+ // name field says (older default.json files may have "Default Preset").
54
+ currentDisplayName = activeFileName === 'default'
55
+ ? 'Default'
56
+ : (active.name ?? activeFileName);
57
+ const meta = (await listManifests()).find((f) => f.fileName === activeFileName) ?? null;
58
+ activeManifest.set(meta);
59
+ }
60
+ } catch {
61
+ // silent
62
+ }
63
+ }
64
+
65
+ onMount(async () => {
66
+ await refreshFiles();
67
+ await refreshActive();
68
+ });
69
+
70
+ // Re-read active manifest whenever a sibling Adopt fires — the server
71
+ // patches our active file in those moments, so the timestamp + refs
72
+ // displayed here need to track. Skip the first tick (refreshActive ran
73
+ // already on mount).
74
+ let pulseInitialised = false;
75
+ $effect(() => {
76
+ void $productionRevision;
77
+ if (!pulseInitialised) {
78
+ pulseInitialised = true;
79
+ return;
80
+ }
81
+ refreshActive();
82
+ });
83
+
84
+ async function handleSave() {
85
+ if (activeIsProtected) return;
86
+ saveStatus = 'saving';
87
+ try {
88
+ await saveActiveManifest(currentDisplayName);
89
+ await refreshActive();
90
+ flashStatus(setSaveStatus, 'saved');
91
+ } catch {
92
+ flashStatus(setSaveStatus, 'error');
93
+ }
94
+ }
95
+
96
+ function openSaveAs() {
97
+ showFileList = false;
98
+ saveAsDialog = true;
99
+ }
100
+
101
+ async function confirmSaveAs(detail: { displayName: string; fileName: string }) {
102
+ saveStatus = 'saving';
103
+ try {
104
+ await saveAsManifest(detail.fileName, detail.displayName);
105
+ await refreshFiles();
106
+ await refreshActive();
107
+ flashStatus(setSaveStatus, 'saved');
108
+ } catch {
109
+ flashStatus(setSaveStatus, 'error');
110
+ }
111
+ }
112
+
113
+ async function handleApply(file: ManifestMeta) {
114
+ if (editorDirty) {
115
+ const ok = window.confirm(
116
+ 'Loading a manifest will reload the editor and discard unsaved changes. Continue?',
117
+ );
118
+ if (!ok) return;
119
+ }
120
+ showFileList = false;
121
+ try {
122
+ await applyManifest(file.fileName);
123
+ // applyManifest atomically flips active + production pointers and
124
+ // syncs tokens.css; reload to rehydrate the editor from the
125
+ // now-active theme + component configs.
126
+ window.location.reload();
127
+ } catch (err) {
128
+ window.alert(`Failed to apply manifest: ${(err as Error).message}`);
129
+ }
130
+ }
131
+
132
+ async function handleExport(file: ManifestMeta) {
133
+ try {
134
+ await exportManifest(file.fileName);
135
+ } catch (err) {
136
+ window.alert(`Failed to export: ${(err as Error).message}`);
137
+ }
138
+ }
139
+
140
+ let importInput: HTMLInputElement | null = $state(null);
141
+
142
+ function openImport() {
143
+ importInput?.click();
144
+ }
145
+
146
+ async function handleImportFile(event: Event) {
147
+ const input = event.target as HTMLInputElement;
148
+ const file = input.files?.[0];
149
+ input.value = ''; // allow re-picking the same file later
150
+ if (!file) return;
151
+ let bundle: any;
152
+ try {
153
+ bundle = JSON.parse(await file.text());
154
+ } catch {
155
+ window.alert('Selected file is not valid JSON.');
156
+ return;
157
+ }
158
+ if (bundle?.kind !== 'manifest-bundle') {
159
+ window.alert('Not a manifest bundle (missing kind discriminator).');
160
+ return;
161
+ }
162
+ try {
163
+ const result = await importManifest(bundle);
164
+ await refreshFiles();
165
+ const renameCount = Object.keys(result.renames).length;
166
+ if (renameCount > 0) {
167
+ const summary = Object.entries(result.renames)
168
+ .map(([k, v]) => `${k} → ${v}`)
169
+ .join('\n');
170
+ window.alert(
171
+ `Imported as "${result.manifest}". ${renameCount} file(s) renamed to avoid collisions:\n\n${summary}`,
172
+ );
173
+ }
174
+ } catch (err) {
175
+ window.alert(`Failed to import: ${(err as Error).message}`);
176
+ }
177
+ }
178
+
179
+ async function handleDelete(file: ManifestMeta) {
180
+ if (file.isProtected) return;
181
+ if (file.fileName === activeFileName) {
182
+ window.alert('Cannot delete the active manifest. Load another manifest first.');
183
+ return;
184
+ }
185
+ const ok = window.confirm(`Delete manifest "${file.name}"?`);
186
+ if (!ok) return;
187
+ try {
188
+ await deleteManifest(file.fileName);
189
+ await refreshFiles();
190
+ } catch (err) {
191
+ window.alert(`Failed to delete: ${(err as Error).message}`);
192
+ }
193
+ }
194
+
195
+ function toggleFileList() {
196
+ showFileList = !showFileList;
197
+ if (showFileList) refreshFiles();
198
+ }
199
+ </script>
200
+
201
+ <div class="manifest-file-manager">
202
+ <div class="mfm-header">
203
+ <span class="mfm-header-label">Manifest</span>
204
+ <UIInfoPopover title="Manifests" ariaLabel="About manifests">
205
+ <p>
206
+ A <strong>manifest</strong> pins one theme plus one config file per component.
207
+ </p>
208
+ <p>
209
+ The <strong>active</strong> manifest is what the editor reads and what production runs. Theme and component <strong>Adopt</strong> actions auto-update its file.
210
+ </p>
211
+ <p>
212
+ <strong>Default</strong> is protected. To start customizing, <strong>Save As</strong> a new manifest first.
213
+ </p>
214
+ </UIInfoPopover>
215
+ </div>
216
+
217
+ <div class="mfm-card" class:protected={activeIsProtected}>
218
+ <span class="mfm-rail" aria-hidden="true"></span>
219
+ <div class="mfm-card-head">
220
+ <span class="mfm-card-label">Active</span>
221
+ {#if activeIsProtected}
222
+ <span class="mfm-badge protected" title="The default manifest is read-only">
223
+ <i class="fas fa-lock" aria-hidden="true"></i>
224
+ <span>protected</span>
225
+ </span>
226
+ {/if}
227
+ </div>
228
+ <FilePill
229
+ name={currentDisplayName}
230
+ isProtected={activeIsProtected}
231
+ protectedTitle="Protected default manifest"
232
+ title={currentDisplayName}
233
+ style="display: flex;"
234
+ />
235
+ <div class="mfm-card-actions">
236
+ <button
237
+ class="mfm-btn mfm-btn-row save-btn"
238
+ class:saving={saveStatus === 'saving'}
239
+ class:saved={saveStatus === 'saved'}
240
+ class:error={saveStatus === 'error'}
241
+ onclick={handleSave}
242
+ disabled={activeIsProtected || saveStatus === 'saving'}
243
+ title={activeIsProtected
244
+ ? 'Default is read-only — use Save As to capture under a new name'
245
+ : 'Re-stamp the active manifest with the current editor state'}
246
+ >
247
+ <i
248
+ class="fas"
249
+ class:fa-save={saveStatus === 'idle'}
250
+ class:fa-spinner={saveStatus === 'saving'}
251
+ class:fa-check={saveStatus === 'saved'}
252
+ class:fa-times={saveStatus === 'error'}
253
+ ></i>
254
+ <span>
255
+ {#if saveStatus === 'idle'}Save{:else if saveStatus === 'saving'}Saving{:else if saveStatus === 'saved'}Saved{:else}Error{/if}
256
+ </span>
257
+ </button>
258
+ <button class="mfm-btn mfm-btn-row" onclick={openSaveAs} title="Save current state as a new manifest">
259
+ <i class="fas fa-copy"></i>
260
+ <span>Save As…</span>
261
+ </button>
262
+ <button
263
+ class="mfm-btn mfm-btn-row"
264
+ class:active={showFileList}
265
+ onclick={toggleFileList}
266
+ title="Load a manifest"
267
+ >
268
+ <i class="fas fa-folder-open"></i>
269
+ <span>Load…</span>
270
+ </button>
271
+ <button
272
+ class="mfm-btn mfm-btn-row"
273
+ onclick={openImport}
274
+ title="Import a shared manifest bundle"
275
+ >
276
+ <i class="fas fa-file-import"></i>
277
+ <span>Import…</span>
278
+ </button>
279
+ </div>
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Hidden file input for Import; clicked via openImport(). -->
284
+ <input
285
+ bind:this={importInput}
286
+ type="file"
287
+ accept=".json,application/json"
288
+ onchange={handleImportFile}
289
+ style="display: none;"
290
+ />
291
+
292
+ <FileLoadList
293
+ bind:show={showFileList}
294
+ title="Load Manifest"
295
+ {files}
296
+ {activeFileName}
297
+ onload={handleApply}
298
+ ondelete={handleDelete}
299
+ onexport={handleExport}
300
+ exportTitle={(f) => `Export "${f.name}" as a shareable bundle`}
301
+ />
302
+
303
+ <SaveAsDialog
304
+ bind:show={saveAsDialog}
305
+ {currentDisplayName}
306
+ {files}
307
+ title="Save Manifest As"
308
+ placeholder="Manifest name…"
309
+ reservedNameMessage='The name "default" is reserved for the protected baseline.'
310
+ branchFromDefaultName="my-manifest"
311
+ onsave={confirmSaveAs}
312
+ />
313
+
314
+ <style>
315
+ .manifest-file-manager {
316
+ --mfm-active: #5aa85e;
317
+ --mfm-rail-neutral: var(--ui-border);
318
+ --mfm-rail-active: var(--mfm-active);
319
+
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: var(--ui-space-8);
323
+ }
324
+
325
+ .mfm-header {
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: space-between;
329
+ gap: var(--ui-space-4);
330
+ padding: 0 var(--ui-space-4);
331
+ }
332
+
333
+ .mfm-header-label {
334
+ font-size: var(--ui-font-size-xs);
335
+ color: var(--ui-text-secondary);
336
+ text-transform: uppercase;
337
+ }
338
+
339
+ .mfm-card {
340
+ position: relative;
341
+ display: flex;
342
+ flex-direction: column;
343
+ gap: var(--ui-space-6);
344
+ padding: var(--ui-space-8) var(--ui-space-10) var(--ui-space-10) var(--ui-space-16);
345
+ background: var(--ui-surface-lower);
346
+ border: 1px solid var(--ui-border-low);
347
+ border-radius: var(--ui-radius-md);
348
+ }
349
+
350
+ .mfm-rail {
351
+ position: absolute;
352
+ left: 0;
353
+ top: 0;
354
+ bottom: 0;
355
+ width: 3px;
356
+ border-radius: var(--ui-radius-md) 0 0 var(--ui-radius-md);
357
+ background: var(--mfm-rail-active);
358
+ transition: background var(--ui-transition-base);
359
+ }
360
+
361
+ .mfm-card.protected .mfm-rail {
362
+ background: var(--mfm-rail-neutral);
363
+ }
364
+
365
+ .mfm-card-head {
366
+ display: flex;
367
+ align-items: baseline;
368
+ justify-content: space-between;
369
+ gap: var(--ui-space-8);
370
+ }
371
+
372
+ .mfm-card-label {
373
+ font-size: 10px;
374
+ font-weight: var(--ui-font-weight-semibold);
375
+ text-transform: uppercase;
376
+ color: var(--ui-text-tertiary);
377
+ }
378
+
379
+ .mfm-badge {
380
+ display: inline-flex;
381
+ align-items: center;
382
+ gap: var(--ui-space-4);
383
+ font-size: var(--ui-font-size-xs);
384
+ color: var(--ui-text-tertiary);
385
+ }
386
+
387
+ .mfm-badge.protected i {
388
+ font-size: 0.8em;
389
+ }
390
+
391
+ .mfm-card-actions {
392
+ display: flex;
393
+ flex-direction: column;
394
+ gap: var(--ui-space-4);
395
+ }
396
+
397
+ .mfm-btn {
398
+ display: inline-flex;
399
+ align-items: center;
400
+ justify-content: flex-start;
401
+ gap: var(--ui-space-8);
402
+ padding: var(--ui-space-6) var(--ui-space-10);
403
+ background: var(--ui-surface-high);
404
+ border: 1px solid var(--ui-border-low);
405
+ border-radius: var(--ui-radius-sm);
406
+ color: var(--ui-text-primary);
407
+ font-family: inherit;
408
+ font-size: var(--ui-font-size-sm);
409
+ line-height: 1;
410
+ cursor: pointer;
411
+ transition: background var(--ui-transition-fast), color var(--ui-transition-fast);
412
+ }
413
+
414
+ .mfm-btn:hover:not(:disabled) {
415
+ background: var(--ui-hover);
416
+ }
417
+
418
+ .mfm-btn:disabled {
419
+ cursor: not-allowed;
420
+ opacity: 0.5;
421
+ }
422
+
423
+ .mfm-btn.active {
424
+ background: var(--ui-active);
425
+ }
426
+
427
+ .mfm-btn.saved { color: var(--mfm-active); }
428
+ .mfm-btn.error { color: var(--ui-error, #c0392b); }
429
+
430
+ .mfm-btn .fa-spinner {
431
+ animation: mfm-spin 0.8s linear infinite;
432
+ }
433
+
434
+ @keyframes mfm-spin {
435
+ from { transform: rotate(0deg); }
436
+ to { transform: rotate(360deg); }
437
+ }
438
+ </style>