@motion-proto/live-tokens 0.1.1 → 0.3.2

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 (224) hide show
  1. package/README.md +168 -21
  2. package/dist-plugin/index.cjs +823 -336
  3. package/dist-plugin/index.d.cts +9 -7
  4. package/dist-plugin/index.d.ts +9 -7
  5. package/dist-plugin/index.js +822 -335
  6. package/package.json +46 -20
  7. package/src/assets/newspaper.webp +0 -0
  8. package/src/assets/offering.webp +0 -0
  9. package/src/component-editor/BadgeEditor.svelte +170 -0
  10. package/src/component-editor/CalloutEditor.svelte +103 -0
  11. package/src/component-editor/CardEditor.svelte +184 -0
  12. package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
  13. package/src/component-editor/CornerBadgeEditor.svelte +207 -0
  14. package/src/component-editor/DialogEditor.svelte +172 -0
  15. package/src/component-editor/ImageEditor.svelte +72 -0
  16. package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
  17. package/src/component-editor/NotificationEditor.svelte +160 -0
  18. package/src/component-editor/ProgressBarEditor.svelte +124 -0
  19. package/src/component-editor/RadioButtonEditor.svelte +140 -0
  20. package/src/component-editor/SectionDividerEditor.svelte +263 -0
  21. package/src/component-editor/SegmentedControlEditor.svelte +154 -0
  22. package/src/component-editor/StandardButtonsEditor.svelte +178 -0
  23. package/src/component-editor/TabBarEditor.svelte +137 -0
  24. package/src/component-editor/TableEditor.svelte +128 -0
  25. package/src/component-editor/TooltipEditor.svelte +122 -0
  26. package/src/component-editor/editorTokens.test.ts +93 -0
  27. package/src/component-editor/groupKeySlots.test.ts +67 -0
  28. package/src/component-editor/groupKeySnapshot.test.ts +52 -0
  29. package/src/component-editor/index.ts +5 -0
  30. package/src/component-editor/registry.ts +246 -0
  31. package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
  32. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
  33. package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
  34. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
  35. package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
  36. package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
  37. package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
  38. package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
  39. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
  40. package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
  41. package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
  42. package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
  43. package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
  44. package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
  45. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
  46. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
  47. package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
  48. package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
  49. package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
  50. package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
  51. package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
  52. package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
  53. package/src/component-editor/scaffolding/componentSources.ts +9 -0
  54. package/src/component-editor/scaffolding/defaultSections.ts +16 -0
  55. package/src/component-editor/scaffolding/editorContext.ts +44 -0
  56. package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
  57. package/src/component-editor/scaffolding/siblings.ts +33 -0
  58. package/src/component-editor/scaffolding/types.ts +39 -0
  59. package/src/components/Badge.svelte +231 -42
  60. package/src/components/Button.svelte +324 -124
  61. package/src/components/Callout.svelte +145 -0
  62. package/src/components/Card.svelte +123 -25
  63. package/src/components/CollapsibleSection.svelte +213 -35
  64. package/src/components/CornerBadge.svelte +224 -0
  65. package/src/components/Dialog.svelte +137 -114
  66. package/src/components/Image.svelte +43 -0
  67. package/src/components/InlineEditActions.svelte +74 -14
  68. package/src/components/Notification.svelte +184 -163
  69. package/src/components/ProgressBar.svelte +216 -22
  70. package/src/components/RadioButton.svelte +110 -40
  71. package/src/components/SectionDivider.svelte +428 -74
  72. package/src/components/SegmentedControl.svelte +203 -0
  73. package/src/components/TabBar.svelte +146 -21
  74. package/src/components/Table.svelte +102 -0
  75. package/src/components/Tooltip.svelte +45 -19
  76. package/src/components/types.ts +51 -0
  77. package/src/data/google-fonts.json +75 -0
  78. package/src/lib/ColumnsOverlay.svelte +20 -7
  79. package/src/lib/LiveEditorOverlay.svelte +257 -78
  80. package/src/lib/columnsOverlay.ts +21 -17
  81. package/src/lib/componentConfig.test.ts +204 -0
  82. package/src/lib/componentConfigKeys.ts +19 -0
  83. package/src/lib/componentConfigService.ts +88 -0
  84. package/src/lib/copyPopover.ts +30 -0
  85. package/src/lib/cssVarSync.ts +59 -7
  86. package/src/lib/editorConfigStore.ts +0 -10
  87. package/src/lib/editorCore.ts +402 -0
  88. package/src/lib/editorKeybindings.ts +52 -0
  89. package/src/lib/editorPersistence.ts +106 -0
  90. package/src/lib/editorRenderer.ts +74 -0
  91. package/src/lib/editorStore.test.ts +328 -0
  92. package/src/lib/editorStore.ts +412 -0
  93. package/src/lib/editorTypes.ts +100 -0
  94. package/src/lib/editorViewStore.ts +55 -0
  95. package/src/lib/files/versionedFileResource.ts +140 -0
  96. package/src/lib/fontLoader.ts +130 -0
  97. package/src/lib/fontMigration.ts +140 -0
  98. package/src/lib/fontParse.ts +168 -0
  99. package/src/lib/index.ts +48 -30
  100. package/src/lib/lazyConfig.test.ts +54 -0
  101. package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
  102. package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
  103. package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
  104. package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
  105. package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
  106. package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
  107. package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
  108. package/src/lib/migrations/index.ts +93 -0
  109. package/src/lib/migrations/migrations.test.ts +341 -0
  110. package/src/lib/navLinkTypes.ts +1 -0
  111. package/src/lib/overlayState.ts +3 -0
  112. package/src/lib/paletteDerivation.ts +300 -0
  113. package/src/lib/parentRouteStore.ts +42 -0
  114. package/src/lib/parsers/globalRootBlock.ts +32 -0
  115. package/src/lib/presetService.ts +94 -0
  116. package/src/lib/router.ts +42 -10
  117. package/src/lib/scrollSection.ts +45 -0
  118. package/src/lib/slices/columns.ts +59 -0
  119. package/src/lib/slices/components.ts +362 -0
  120. package/src/lib/slices/domainVars.ts +15 -0
  121. package/src/lib/slices/fonts.ts +30 -0
  122. package/src/lib/slices/gradients.ts +153 -0
  123. package/src/lib/slices/overlays.ts +132 -0
  124. package/src/lib/slices/palettes.ts +26 -0
  125. package/src/lib/slices/shadows.ts +123 -0
  126. package/src/lib/storage.ts +88 -0
  127. package/src/lib/themeInit.ts +74 -0
  128. package/src/lib/themeService.ts +101 -0
  129. package/src/lib/themeTypes.ts +146 -0
  130. package/src/lib/tokenRegistry.ts +148 -0
  131. package/src/pages/ComponentEditorPage.svelte +384 -0
  132. package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
  133. package/src/pages/Editor.svelte +98 -0
  134. package/src/pages/Editor.svelte.d.ts +2 -0
  135. package/src/pages/EditorShell.svelte +348 -0
  136. package/src/styles/_padding.scss +34 -0
  137. package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  138. package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  139. package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  140. package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  141. package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  142. package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
  143. package/src/styles/fonts.css +22 -10
  144. package/src/styles/form-controls.css +14 -16
  145. package/src/styles/tokens.css +1322 -0
  146. package/src/styles/ui-editor.css +126 -0
  147. package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
  148. package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
  149. package/src/ui/EditorViewSwitcher.svelte +180 -0
  150. package/src/ui/FontStackEditor.svelte +360 -0
  151. package/src/ui/GradientEditor.svelte +461 -0
  152. package/src/ui/GradientStopPicker.svelte +74 -0
  153. package/src/ui/PaletteEditor.svelte +1590 -0
  154. package/src/ui/PaletteEditor.test.ts +108 -0
  155. package/src/ui/PresetFileManager.svelte +567 -0
  156. package/src/ui/ProjectFontsSection.svelte +645 -0
  157. package/src/{showcase → ui}/SurfacesTab.svelte +39 -39
  158. package/src/{showcase → ui}/TextTab.svelte +27 -27
  159. package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
  160. package/src/ui/Toggle.svelte +108 -0
  161. package/src/ui/UICopyPopover.svelte +78 -0
  162. package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
  163. package/src/ui/UIFontFamilySelector.svelte +309 -0
  164. package/src/ui/UIFontSizeSelector.svelte +165 -0
  165. package/src/ui/UIFontWeightSelector.svelte +52 -0
  166. package/src/ui/UILineHeightSelector.svelte +47 -0
  167. package/src/ui/UILinkToggle.svelte +60 -0
  168. package/src/ui/UIOptionItem.svelte +74 -0
  169. package/src/ui/UIOptionList.svelte +27 -0
  170. package/src/ui/UIPaddingSelector.svelte +661 -0
  171. package/src/ui/UIPaletteSelector.svelte +1084 -0
  172. package/src/ui/UIRadio.svelte +72 -0
  173. package/src/ui/UIRadioGroup.svelte +59 -0
  174. package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
  175. package/src/ui/UITokenSelector.svelte +509 -0
  176. package/src/ui/UIVariantSelector.svelte +145 -0
  177. package/src/ui/VariablesTab.svelte +252 -0
  178. package/src/ui/index.ts +31 -0
  179. package/src/ui/keepInViewport.ts +84 -0
  180. package/src/ui/palette/GradientStopEditor.svelte +482 -0
  181. package/src/ui/palette/OverridesPanel.svelte +526 -0
  182. package/src/ui/palette/PaletteBase.svelte +165 -0
  183. package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
  184. package/src/ui/palette/paletteEditorState.ts +89 -0
  185. package/src/ui/sections/ColumnsSection.svelte +273 -0
  186. package/src/ui/sections/GradientsSection.svelte +147 -0
  187. package/src/ui/sections/OverlaysSection.svelte +670 -0
  188. package/src/ui/sections/ShadowsSection.svelte +1250 -0
  189. package/src/ui/sections/TokenScaleTable.svelte +332 -0
  190. package/src/ui/sections/tokenScales.ts +81 -0
  191. package/src/ui/variantScales.ts +108 -0
  192. package/src/components/DetailNav.svelte +0 -78
  193. package/src/components/Toggle.svelte +0 -86
  194. package/src/lib/tokenInit.ts +0 -29
  195. package/src/lib/tokenService.ts +0 -144
  196. package/src/lib/tokenTypes.ts +0 -45
  197. package/src/pages/Admin.svelte +0 -100
  198. package/src/pages/ShowcasePage.svelte +0 -144
  199. package/src/showcase/BackupBrowser.svelte +0 -617
  200. package/src/showcase/ComponentsTab.svelte +0 -105
  201. package/src/showcase/PaletteEditor.svelte +0 -2579
  202. package/src/showcase/PaletteSelector.svelte +0 -627
  203. package/src/showcase/TokenMap.svelte +0 -54
  204. package/src/showcase/VariablesTab.svelte +0 -2655
  205. package/src/showcase/VisualsTab.svelte +0 -231
  206. package/src/showcase/demos/BadgeDemo.svelte +0 -56
  207. package/src/showcase/demos/CardDemo.svelte +0 -50
  208. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
  209. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
  210. package/src/showcase/demos/DialogDemo.svelte +0 -42
  211. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
  212. package/src/showcase/demos/NotificationDemo.svelte +0 -147
  213. package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
  214. package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
  215. package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
  216. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
  217. package/src/showcase/demos/TabBarDemo.svelte +0 -58
  218. package/src/showcase/demos/TooltipDemo.svelte +0 -52
  219. package/src/showcase/editor.css +0 -93
  220. package/src/showcase/index.ts +0 -17
  221. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  222. package/src/styles/fonts/Domine/OFL.txt +0 -97
  223. package/src/styles/fonts/Domine/README.txt +0 -66
  224. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -0,0 +1,328 @@
1
+ // @vitest-environment happy-dom
2
+ import { beforeEach, describe, expect, it } from 'vitest';
3
+ import { get } from 'svelte/store';
4
+ import type { PaletteConfig } from './themeTypes';
5
+ import {
6
+ editorState,
7
+ mutate,
8
+ beginScope,
9
+ commitScope,
10
+ cancelScope,
11
+ beginSliderGesture,
12
+ transaction,
13
+ undo,
14
+ redo,
15
+ setPaletteConfig,
16
+ __resetForTests,
17
+ __getHistoryLengths,
18
+ __getPastAt,
19
+ } from './editorStore';
20
+
21
+ function makePaletteConfig(baseColor: string): PaletteConfig {
22
+ return {
23
+ baseColor,
24
+ tintHue: 0,
25
+ tintChroma: 0.04,
26
+ lightnessCurve: [],
27
+ saturationCurve: [],
28
+ grayLightnessCurve: [],
29
+ graySaturationCurve: [],
30
+ scaleCurves: {},
31
+ curveOffset: {},
32
+ overrides: {},
33
+ snappedScales: [],
34
+ };
35
+ }
36
+
37
+ const txOpts = { label: 'tx', collapseToOne: true, clipUndoFloor: false } as const;
38
+ const sessionOpts = { label: 'palette session', collapseToOne: true, clipUndoFloor: true } as const;
39
+
40
+ beforeEach(() => {
41
+ __resetForTests();
42
+ });
43
+
44
+ describe('editorStore — mutate() outside a scope', () => {
45
+ it('pushes exactly one past[] entry per call and undo restores', () => {
46
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
47
+ expect(__getHistoryLengths().past).toBe(1);
48
+
49
+ setPaletteConfig('Background', makePaletteConfig('#222222'));
50
+ expect(__getHistoryLengths().past).toBe(2);
51
+
52
+ expect(get(editorState).palettes.Background.baseColor).toBe('#222222');
53
+ expect(undo()).toBe(true);
54
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
55
+ expect(undo()).toBe(true);
56
+ expect(get(editorState).palettes.Background).toBeUndefined();
57
+ expect(undo()).toBe(false);
58
+ });
59
+ });
60
+
61
+ describe('editorStore — non-clipping scopes group gestures', () => {
62
+ it('beginScope + multiple mutate() + commitScope → one past entry equal to pre-gesture snapshot', () => {
63
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
64
+ const baselinePast = __getHistoryLengths().past;
65
+ const preGesture = structuredClone(get(editorState));
66
+
67
+ const scope = beginScope({ label: 'drag hue', collapseToOne: true, clipUndoFloor: false });
68
+ mutate('hue step 1', (s) => { s.palettes.Background.baseColor = '#222222'; });
69
+ mutate('hue step 2', (s) => { s.palettes.Background.baseColor = '#333333'; });
70
+ mutate('hue step 3', (s) => { s.palettes.Background.baseColor = '#444444'; });
71
+ commitScope(scope);
72
+
73
+ expect(__getHistoryLengths().past).toBe(baselinePast + 1);
74
+ const lastEntry = __getPastAt(__getHistoryLengths().past - 1)!;
75
+ expect(lastEntry.palettes.Background.baseColor).toBe(preGesture.palettes.Background.baseColor);
76
+ expect(get(editorState).palettes.Background.baseColor).toBe('#444444');
77
+
78
+ // One undo rolls the whole gesture back
79
+ undo();
80
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
81
+ });
82
+
83
+ it('beginSliderGesture opens a scope that groups updates into one entry', () => {
84
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
85
+ const baselinePast = __getHistoryLengths().past;
86
+
87
+ beginSliderGesture('drag');
88
+ mutate('step', (s) => { s.palettes.Background.baseColor = '#222222'; });
89
+ mutate('step', (s) => { s.palettes.Background.baseColor = '#333333'; });
90
+ // Simulate pointerup
91
+ window.dispatchEvent(new Event('pointerup'));
92
+
93
+ expect(__getHistoryLengths().past).toBe(baselinePast + 1);
94
+ expect(get(editorState).palettes.Background.baseColor).toBe('#333333');
95
+ });
96
+
97
+ it('empty scope (no mutate calls) does not push history', () => {
98
+ const baselinePast = __getHistoryLengths().past;
99
+ const scope = beginScope({ ...txOpts, label: 'unused' });
100
+ commitScope(scope);
101
+ expect(__getHistoryLengths().past).toBe(baselinePast);
102
+ });
103
+
104
+ it('cancelScope on a non-clipping scope restores pre-gesture state and does not push history', () => {
105
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
106
+ const baselinePast = __getHistoryLengths().past;
107
+
108
+ const scope = beginScope({ ...txOpts, label: 'drag' });
109
+ mutate('step', (s) => { s.palettes.Background.baseColor = '#999999'; });
110
+ cancelScope(scope, { silent: true });
111
+
112
+ expect(__getHistoryLengths().past).toBe(baselinePast);
113
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
114
+ });
115
+ });
116
+
117
+ describe('editorStore — clipping scopes (palette edit sessions)', () => {
118
+ it('beginScope with clipUndoFloor does not push history', () => {
119
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
120
+ const before = __getHistoryLengths().past;
121
+ beginScope({ ...sessionOpts });
122
+ expect(__getHistoryLengths().past).toBe(before);
123
+ });
124
+
125
+ it('undo is clipped to the scope floor while open', () => {
126
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
127
+ setPaletteConfig('Background', makePaletteConfig('#222222'));
128
+ const floor = __getHistoryLengths().past;
129
+
130
+ beginScope({ ...sessionOpts });
131
+ setPaletteConfig('Background', makePaletteConfig('#333333'));
132
+ setPaletteConfig('Background', makePaletteConfig('#444444'));
133
+ expect(__getHistoryLengths().past).toBe(floor + 2);
134
+
135
+ expect(undo()).toBe(true);
136
+ expect(get(editorState).palettes.Background.baseColor).toBe('#333333');
137
+ expect(undo()).toBe(true);
138
+ expect(get(editorState).palettes.Background.baseColor).toBe('#222222');
139
+
140
+ // Floor reached — further undo returns false, state unchanged
141
+ expect(undo()).toBe(false);
142
+ expect(get(editorState).palettes.Background.baseColor).toBe('#222222');
143
+ });
144
+
145
+ it('commitScope on a clipping scope collapses intra-scope history into one entry equal to the snapshot', () => {
146
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
147
+ const preSessionPastLen = __getHistoryLengths().past;
148
+
149
+ const session = beginScope({ ...sessionOpts });
150
+ setPaletteConfig('Background', makePaletteConfig('#222222'));
151
+ setPaletteConfig('Background', makePaletteConfig('#333333'));
152
+ setPaletteConfig('Background', makePaletteConfig('#444444'));
153
+ commitScope(session);
154
+
155
+ expect(__getHistoryLengths().past).toBe(preSessionPastLen + 1);
156
+
157
+ const committedEntry = __getPastAt(__getHistoryLengths().past - 1)!;
158
+ expect(committedEntry.palettes.Background.baseColor).toBe('#111111');
159
+ expect(get(editorState).palettes.Background.baseColor).toBe('#444444');
160
+
161
+ // One undo restores pre-scope state
162
+ undo();
163
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
164
+ });
165
+
166
+ it('commitScope on a clipping scope with no net change pushes nothing', () => {
167
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
168
+ const preSessionPastLen = __getHistoryLengths().past;
169
+
170
+ const session = beginScope({ ...sessionOpts });
171
+ // Mutate and revert to snapshot value
172
+ setPaletteConfig('Background', makePaletteConfig('#222222'));
173
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
174
+ commitScope(session);
175
+
176
+ expect(__getHistoryLengths().past).toBe(preSessionPastLen);
177
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
178
+ });
179
+
180
+ it('cancelScope on a clipping scope restores snapshot, drops intra-scope entries, clears future', () => {
181
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
182
+ const preSessionPastLen = __getHistoryLengths().past;
183
+
184
+ const session = beginScope({ ...sessionOpts });
185
+ setPaletteConfig('Background', makePaletteConfig('#222222'));
186
+ setPaletteConfig('Background', makePaletteConfig('#333333'));
187
+ expect(__getHistoryLengths().past).toBe(preSessionPastLen + 2);
188
+
189
+ cancelScope(session);
190
+
191
+ expect(__getHistoryLengths().past).toBe(preSessionPastLen);
192
+ expect(__getHistoryLengths().future).toBe(0);
193
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
194
+ });
195
+
196
+ it('nested clipping beginScope auto-commits the prior scope', () => {
197
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
198
+ setPaletteConfig('Accent', makePaletteConfig('#aaaaaa'));
199
+ const preSessionPastLen = __getHistoryLengths().past;
200
+
201
+ beginScope({ ...sessionOpts });
202
+ setPaletteConfig('Background', makePaletteConfig('#222222'));
203
+ const second = beginScope({ ...sessionOpts }); // auto-commits prior
204
+ expect(__getHistoryLengths().past).toBe(preSessionPastLen + 1);
205
+
206
+ setPaletteConfig('Accent', makePaletteConfig('#bbbbbb'));
207
+ commitScope(second);
208
+
209
+ // Two collapsed entries: prior Background scope, then Accent scope
210
+ expect(__getHistoryLengths().past).toBe(preSessionPastLen + 2);
211
+ // One undo: revert Accent to pre-scope value
212
+ undo();
213
+ expect(get(editorState).palettes.Accent.baseColor).toBe('#aaaaaa');
214
+ expect(get(editorState).palettes.Background.baseColor).toBe('#222222');
215
+ // Another undo: revert Background to pre-scope value
216
+ undo();
217
+ expect(get(editorState).palettes.Background.baseColor).toBe('#111111');
218
+ });
219
+
220
+ it('undo() with a pending non-clipping scope cancels it first (drag-in-flight is discarded)', () => {
221
+ setPaletteConfig('Background', makePaletteConfig('#111111'));
222
+ const pastLenBefore = __getHistoryLengths().past;
223
+
224
+ beginScope({ ...txOpts, label: 'drag' });
225
+ mutate('step', (s) => { s.palettes.Background.baseColor = '#ffffff'; });
226
+ // An in-flight drag holds pre-drag state in the scope's snapshot;
227
+ // `undo()` cancels it (restoring that snapshot) before consulting history.
228
+ undo();
229
+ // The cancelled in-flight change is gone; history count unchanged by the cancel.
230
+ // (Current impl also consumes one history step after cancelling — the
231
+ // cross-boundary behavior is a separate concern tracked in the plan.)
232
+ expect(__getHistoryLengths().past).toBe(pastLenBefore - 1);
233
+ // The pending mutation did not survive: '#ffffff' is not current.
234
+ expect(get(editorState).palettes.Background?.baseColor).not.toBe('#ffffff');
235
+ });
236
+ });
237
+
238
+ describe('editorStore — apply + undo matches spec end-to-end', () => {
239
+ it('after Apply, one Cmd+Z restores to pre-session state', () => {
240
+ setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
241
+ const preSessionState = structuredClone(get(editorState));
242
+
243
+ const session = beginScope({ ...sessionOpts });
244
+ // Simulate three slider drags during the session
245
+ for (const hex of ['#702030', '#503090', '#205090']) {
246
+ const drag = beginScope({ ...txOpts, label: `drag ${hex}` });
247
+ setPaletteConfig('Background', makePaletteConfig(hex));
248
+ commitScope(drag);
249
+ }
250
+ commitScope(session);
251
+
252
+ expect(get(editorState).palettes.Background.baseColor).toBe('#205090');
253
+
254
+ const undone = undo();
255
+ expect(undone).toBe(true);
256
+ expect(get(editorState).palettes.Background.baseColor).toBe(preSessionState.palettes.Background.baseColor);
257
+ expect(JSON.stringify(get(editorState))).toBe(JSON.stringify(preSessionState));
258
+ });
259
+
260
+ it('after Cancel, Cmd+Z does not resurrect discarded drags', () => {
261
+ setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
262
+ const preSessionState = structuredClone(get(editorState));
263
+
264
+ const session = beginScope({ ...sessionOpts });
265
+ for (const hex of ['#702030', '#503090', '#205090']) {
266
+ const drag = beginScope({ ...txOpts, label: `drag ${hex}` });
267
+ setPaletteConfig('Background', makePaletteConfig(hex));
268
+ commitScope(drag);
269
+ }
270
+ cancelScope(session);
271
+
272
+ // State is pre-session; no new history entry
273
+ expect(JSON.stringify(get(editorState))).toBe(JSON.stringify(preSessionState));
274
+ // One undo walks back to before the palette existed (setPaletteConfig before scope)
275
+ undo();
276
+ expect(get(editorState).palettes.Background).toBeUndefined();
277
+ });
278
+ });
279
+
280
+ describe('editorStore — intra-session slider-drag tracking', () => {
281
+ // Regression guard for the two-writer feedback loop fixed in the
282
+ // PaletteEditor single-source-of-truth refactor: during a drag inside a
283
+ // palette edit session, every per-tick mutation must be visible in the
284
+ // store immediately (not pulled back to a stale pre-session value).
285
+ it('store reflects every per-tick mutation during a slider-drag session', () => {
286
+ setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
287
+
288
+ const session = beginScope({ ...sessionOpts });
289
+ beginSliderGesture('drag base');
290
+
291
+ const tickHexes = ['#8c7f73', '#8b7f72', '#8a7f71', '#897f70', '#887f6f'];
292
+ for (const hex of tickHexes) {
293
+ mutate('drag tick', (s) => { s.palettes.Background.baseColor = hex; });
294
+ // Each tick must be visible on read — no stale pre-session value
295
+ expect(get(editorState).palettes.Background.baseColor).toBe(hex);
296
+ }
297
+
298
+ window.dispatchEvent(new Event('pointerup'));
299
+ commitScope(session);
300
+
301
+ expect(get(editorState).palettes.Background.baseColor).toBe('#887f6f');
302
+
303
+ // One undo after Apply restores to pre-session
304
+ undo();
305
+ expect(get(editorState).palettes.Background.baseColor).toBe('#8d7f74');
306
+ });
307
+
308
+ it('Cmd+Z during a session walks one tick back per press', () => {
309
+ setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
310
+
311
+ beginScope({ ...sessionOpts });
312
+ for (const hex of ['#702030', '#503090', '#205090']) {
313
+ const drag = beginScope({ ...txOpts, label: `drag ${hex}` });
314
+ mutate('tick', (s) => { s.palettes.Background.baseColor = hex; });
315
+ commitScope(drag);
316
+ }
317
+
318
+ expect(get(editorState).palettes.Background.baseColor).toBe('#205090');
319
+ undo();
320
+ expect(get(editorState).palettes.Background.baseColor).toBe('#503090');
321
+ undo();
322
+ expect(get(editorState).palettes.Background.baseColor).toBe('#702030');
323
+ undo();
324
+ expect(get(editorState).palettes.Background.baseColor).toBe('#8d7f74');
325
+ // Session floor reached — further undo no-ops
326
+ expect(undo()).toBe(false);
327
+ });
328
+ });