@weasel-js/labkit 1.1.0 → 1.3.0-pre.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 (226) hide show
  1. package/README.md +27 -0
  2. package/dist/_dts/{DrawCommand-BkZztJsW.d.ts → DrawCommand-DBB45NfN.d.ts} +244 -58
  3. package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
  4. package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
  5. package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
  6. package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
  7. package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
  8. package/dist/_dts/{useTrialState-DYe2vUwN.d.ts → useTrialState-CsGMjhu9.d.ts} +6 -92
  9. package/dist/canvas/index.d.ts +8 -3
  10. package/dist/canvas/index.js +2 -1
  11. package/dist/chrome/index.d.ts +86 -0
  12. package/dist/chrome/index.js +8 -0
  13. package/dist/chrome/index.js.map +1 -0
  14. package/dist/{chunk-3TYUJR7Z.js → chunk-4TMMVIDM.js} +9 -3
  15. package/dist/chunk-4TMMVIDM.js.map +1 -0
  16. package/dist/chunk-HFDVGF4X.js +170 -0
  17. package/dist/chunk-HFDVGF4X.js.map +1 -0
  18. package/dist/chunk-LN6JDUGB.js +106 -0
  19. package/dist/chunk-LN6JDUGB.js.map +1 -0
  20. package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
  21. package/dist/chunk-NS54R52R.js.map +1 -0
  22. package/dist/{chunk-NRKWVTVT.js → chunk-O2BULFHX.js} +5 -14
  23. package/dist/chunk-O2BULFHX.js.map +1 -0
  24. package/dist/chunk-O7NVSCLN.js +266 -0
  25. package/dist/chunk-O7NVSCLN.js.map +1 -0
  26. package/dist/chunk-PO6L3NSH.js +366 -0
  27. package/dist/chunk-PO6L3NSH.js.map +1 -0
  28. package/dist/chunk-PQJ5B2U2.js +27 -0
  29. package/dist/chunk-PQJ5B2U2.js.map +1 -0
  30. package/dist/chunk-RVNN4CHQ.js +212 -0
  31. package/dist/chunk-RVNN4CHQ.js.map +1 -0
  32. package/dist/chunk-TLGRYALP.js +6151 -0
  33. package/dist/chunk-TLGRYALP.js.map +1 -0
  34. package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
  35. package/dist/chunk-V7PRSIRQ.js.map +1 -0
  36. package/dist/{chunk-DJLDIRFN.js → chunk-Z6HJ5FEM.js} +9626 -6050
  37. package/dist/chunk-Z6HJ5FEM.js.map +1 -0
  38. package/dist/chunk-ZZAYVX4X.js +505 -0
  39. package/dist/chunk-ZZAYVX4X.js.map +1 -0
  40. package/dist/controls/index.d.ts +19 -5
  41. package/dist/controls/index.js +3 -1
  42. package/dist/dragdrop/index.d.ts +5 -2
  43. package/dist/index.d.ts +307 -107
  44. package/dist/index.js +797 -540
  45. package/dist/index.js.map +1 -1
  46. package/dist/job/index.d.ts +13 -0
  47. package/dist/job/index.js +3 -0
  48. package/dist/job/index.js.map +1 -0
  49. package/dist/layers/index.d.ts +6 -3
  50. package/dist/layers/index.js +2 -1
  51. package/dist/passthrough/weasel-canvas.d.ts +69 -13
  52. package/dist/passthrough/weasel-canvas.js +1 -1
  53. package/dist/passthrough/weasel-ui.d.ts +3804 -3661
  54. package/dist/passthrough/weasel-ui.js +2 -2
  55. package/dist/primitives/index.d.ts +102 -8
  56. package/dist/primitives/index.js +4 -1
  57. package/dist/state/index.d.ts +3 -2
  58. package/dist/state/index.js +2 -2
  59. package/dist/styles.css +543 -144
  60. package/dist/surface/index.d.ts +77 -0
  61. package/dist/surface/index.js +4 -0
  62. package/dist/surface/index.js.map +1 -0
  63. package/dist/ui/layers/index.js +4 -3
  64. package/dist/undo/index.d.ts +5 -2
  65. package/package.json +24 -7
  66. package/src/canvas/AGENTS.md +12 -0
  67. package/src/canvas/CanvasStack.tsx +6 -2
  68. package/src/canvas/useLayerScheduler.ts +22 -15
  69. package/src/canvas/useOrbit.test.ts +71 -0
  70. package/src/canvas/useOrbit.ts +149 -0
  71. package/src/canvas/usePanZoom.test.ts +80 -0
  72. package/src/canvas/usePanZoom.ts +16 -2
  73. package/src/chrome/ChromeRegions.stories.tsx +106 -0
  74. package/src/chrome/builtins.test.ts +153 -0
  75. package/src/chrome/builtins.tsx +205 -0
  76. package/src/chrome/index.ts +25 -0
  77. package/src/chrome/merge.test.ts +51 -0
  78. package/src/chrome/merge.ts +52 -0
  79. package/src/chrome/regions/PaletteRegion.less +14 -0
  80. package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
  81. package/src/chrome/regions/PaletteRegion.tsx +41 -0
  82. package/src/chrome/regions/SidebarRegion.tsx +56 -0
  83. package/src/chrome/regions/StatusRegion.tsx +31 -0
  84. package/src/chrome/regions/TitleBarRegion.tsx +41 -0
  85. package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
  86. package/src/chrome/regions/ToolbarRegion.tsx +71 -0
  87. package/src/chrome/regions/ViewportRegion.less +42 -0
  88. package/src/chrome/regions/ViewportRegion.tsx +37 -0
  89. package/src/chrome/regions/regions.test.tsx +81 -0
  90. package/src/chrome/types.ts +127 -0
  91. package/src/config/builder.test.ts +87 -0
  92. package/src/config/builder.ts +224 -0
  93. package/src/config/fromConfigField.test.ts +93 -0
  94. package/src/config/fromConfigField.ts +74 -0
  95. package/src/config/index.ts +22 -0
  96. package/src/config/resolve.test.ts +120 -0
  97. package/src/config/resolve.ts +67 -0
  98. package/src/config/rules.ts +68 -0
  99. package/src/config/types.ts +108 -0
  100. package/src/config/useConfigSchema.ts +30 -0
  101. package/src/config/visible.ts +20 -0
  102. package/src/controls/ControlPanel.less +4 -58
  103. package/src/controls/ControlPanel.stories.tsx +86 -0
  104. package/src/controls/ControlPanel.test.tsx +223 -10
  105. package/src/controls/ControlPanel.tsx +202 -203
  106. package/src/dragdrop/DragDropRuntime.tsx +36 -18
  107. package/src/dragdrop/DragGhost.less +1 -1
  108. package/src/dragdrop/Palette.less +1 -1
  109. package/src/index.test.ts +18 -0
  110. package/src/index.ts +38 -3
  111. package/src/instrument/SineWave.smoke.test.tsx +9 -1
  112. package/src/instrument/defineInstrument.test.ts +38 -1
  113. package/src/instrument/defineInstrument.ts +31 -4
  114. package/src/instrument/index.ts +1 -0
  115. package/src/instrument/types.ts +38 -3
  116. package/src/instrument/validateConfigSchema.test.ts +13 -3
  117. package/src/instrument/validateConfigSchema.ts +7 -10
  118. package/src/job/index.ts +3 -0
  119. package/src/job/types.ts +47 -0
  120. package/src/job/useJob.test.tsx +210 -0
  121. package/src/job/useJob.ts +134 -0
  122. package/src/lab/Lab.chrome.test.tsx +65 -0
  123. package/src/lab/Lab.stories.tsx +1 -2
  124. package/src/lab/Lab.tsx +58 -15
  125. package/src/lab/LabContext.ts +6 -0
  126. package/src/lab/LabFullChrome.stories.tsx +308 -0
  127. package/src/lab/LabHeader.test.tsx +42 -0
  128. package/src/lab/LabHeader.tsx +57 -0
  129. package/src/lab/LabPalette.test.tsx +35 -0
  130. package/src/lab/LabPalette.tsx +34 -0
  131. package/src/lab/LabShell.less +50 -3
  132. package/src/lab/Workspace.less +1 -30
  133. package/src/lab/Workspace.surface.test.tsx +49 -0
  134. package/src/lab/Workspace.test.tsx +20 -10
  135. package/src/lab/Workspace.tsx +19 -9
  136. package/src/lab/index.ts +2 -0
  137. package/src/layers/AGENTS.md +1 -1
  138. package/src/layers/LayerList.less +31 -7
  139. package/src/layers/LayerList.tsx +17 -4
  140. package/src/passthrough/weasel-ui.ts +23 -0
  141. package/src/primitives/DragHandleGlyph.tsx +29 -0
  142. package/src/primitives/FloatingPanel.less +28 -0
  143. package/src/primitives/FloatingPanel.stories.tsx +46 -0
  144. package/src/primitives/FloatingPanel.test.tsx +240 -0
  145. package/src/primitives/FloatingPanel.tsx +190 -0
  146. package/src/primitives/FpsMeter.test.tsx +44 -0
  147. package/src/primitives/FpsMeter.tsx +20 -9
  148. package/src/primitives/JobProgress.less +69 -0
  149. package/src/primitives/JobProgress.stories.tsx +48 -0
  150. package/src/primitives/JobProgress.tsx +65 -0
  151. package/src/primitives/Legend.less +47 -0
  152. package/src/primitives/Legend.stories.tsx +32 -0
  153. package/src/primitives/Legend.test.tsx +57 -0
  154. package/src/primitives/Legend.tsx +40 -0
  155. package/src/primitives/Sidebar.less +32 -2
  156. package/src/primitives/StatusBar.less +7 -0
  157. package/src/primitives/StatusBar.tsx +10 -3
  158. package/src/primitives/Toolbar.less +50 -3
  159. package/src/primitives/Toolbar.test.tsx +67 -1
  160. package/src/primitives/Toolbar.tsx +72 -6
  161. package/src/primitives/ZoomControl.less +31 -0
  162. package/src/primitives/ZoomControl.tsx +76 -0
  163. package/src/primitives/floating.entry.test.tsx +14 -0
  164. package/src/primitives/index.ts +10 -2
  165. package/src/primitives/useRovingTabIndex.test.ts +27 -0
  166. package/src/primitives/useRovingTabIndex.ts +79 -0
  167. package/src/state/store.ts +16 -2
  168. package/src/state/toolSlot.test.ts +36 -0
  169. package/src/state/types.ts +8 -2
  170. package/src/state/view.test.ts +127 -0
  171. package/src/state/view.ts +18 -0
  172. package/src/styles.less +6 -0
  173. package/src/surface/AGENTS.md +64 -0
  174. package/src/surface/SurfaceContext.ts +5 -0
  175. package/src/surface/composeRects.test.ts +50 -0
  176. package/src/surface/composeRects.ts +19 -0
  177. package/src/surface/deviceRect.test.ts +40 -0
  178. package/src/surface/deviceRect.ts +19 -0
  179. package/src/surface/index.ts +7 -0
  180. package/src/surface/rect.ts +16 -0
  181. package/src/surface/useSurfaceTile.test.tsx +67 -0
  182. package/src/surface/useSurfaceTile.ts +32 -0
  183. package/src/surface/useTiledSurface.test.tsx +231 -0
  184. package/src/surface/useTiledSurface.ts +161 -0
  185. package/src/test-setup.ts +26 -0
  186. package/src/theme/base.less +19 -2
  187. package/src/theme/interstellar.tokens.json +6 -2
  188. package/src/tools/types.ts +24 -0
  189. package/src/trial/Trial.config.test.tsx +113 -0
  190. package/src/trial/Trial.job.test.tsx +73 -0
  191. package/src/trial/Trial.less +121 -4
  192. package/src/trial/Trial.stories.tsx +9 -1
  193. package/src/trial/Trial.test.tsx +67 -96
  194. package/src/trial/Trial.tsx +112 -45
  195. package/src/trial/TrialChrome.tsx +110 -64
  196. package/src/trial/TrialDragContext.ts +10 -0
  197. package/src/trial/TrialTitleBar.tsx +41 -0
  198. package/src/trial/index.ts +1 -15
  199. package/src/trial/trialChrome.borders.test.ts +38 -0
  200. package/src/ui/layers/LayerStack.less +9 -9
  201. package/src/ui/layers/LayerStack.tsx +1 -13
  202. package/src/ui/properties/CurveField.less +2 -2
  203. package/src/ui/properties/CurveField.test.tsx +11 -11
  204. package/src/ui/properties/PropertyGroup.less +2 -2
  205. package/src/ui/properties/PropertyPanel.less +55 -41
  206. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
  207. package/src/ui/properties/storyLayouts.tsx +2 -2
  208. package/dist/_dts/index-iAP6XbH3.d.ts +0 -132
  209. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  210. package/dist/chunk-3TYUJR7Z.js.map +0 -1
  211. package/dist/chunk-574LJAV4.js.map +0 -1
  212. package/dist/chunk-5R2ATYPJ.js +0 -201
  213. package/dist/chunk-5R2ATYPJ.js.map +0 -1
  214. package/dist/chunk-C6GJKPUI.js +0 -5986
  215. package/dist/chunk-C6GJKPUI.js.map +0 -1
  216. package/dist/chunk-DJLDIRFN.js.map +0 -1
  217. package/dist/chunk-NRKWVTVT.js.map +0 -1
  218. package/dist/chunk-PMAU3SEE.js +0 -126
  219. package/dist/chunk-PMAU3SEE.js.map +0 -1
  220. package/dist/chunk-SFL7NFKN.js.map +0 -1
  221. package/src/instrument/capabilityDetector.test.ts +0 -64
  222. package/src/instrument/capabilityDetector.ts +0 -20
  223. package/src/trial/DefaultSidebar.tsx +0 -29
  224. package/src/trial/DefaultStatusBar.tsx +0 -19
  225. package/src/trial/DefaultToolbar.tsx +0 -92
  226. package/src/trial/slotTypes.ts +0 -53
@@ -0,0 +1,162 @@
1
+ import { P as PrefRenderer, T as ToolPrefLeaf, a as ToolPrefGroup } from './PrefsForm-BYa6cWnO.js';
2
+
3
+ /**
4
+ * Renders the control cell for one config leaf. Identical to weasel-ui's
5
+ * `PrefRenderer` on purpose — aliased rather than redeclared so the two
6
+ * cannot drift.
7
+ */
8
+ type ControlRenderer = PrefRenderer;
9
+ /** One labeled choice in an `enum` leaf. */
10
+ interface ConfigOption {
11
+ value: string;
12
+ label: string;
13
+ }
14
+ /** Everything a leaf can carry beyond its kind and default. A flat union of
15
+ * every `Pref*` leaf's extras, plus labkit's own `debounceMs`. */
16
+ interface Annotations {
17
+ name?: string;
18
+ description?: string;
19
+ hidden?: boolean;
20
+ block?: boolean;
21
+ pair?: string;
22
+ min?: number;
23
+ max?: number;
24
+ step?: number;
25
+ control?: string;
26
+ options?: readonly ConfigOption[];
27
+ placeholder?: string;
28
+ maxLength?: number;
29
+ /** Milliseconds to debounce a string leaf's live writes. Default 150. */
30
+ debounceMs?: number;
31
+ }
32
+ /** What a rule contributes. `kind` is honored only while it is still unset,
33
+ * which is what makes `f.value` claimable and a kinded factory final. */
34
+ type LeafPatch = Annotations & {
35
+ kind?: string;
36
+ };
37
+ /** What a rule is given for the leaf it is deciding about. */
38
+ interface ConfigRuleContext {
39
+ /** The config key this leaf writes. */
40
+ key: string;
41
+ /** Dotted path within the schema. Equal to `key` while schemas are flat. */
42
+ path: string;
43
+ /** The leaf's default value. A rule may read it but never change it. */
44
+ default: unknown;
45
+ /** What earlier rules and the author's own annotations have settled. */
46
+ leaf: Readonly<LeafPatch>;
47
+ }
48
+ /**
49
+ * Decides part of how a leaf is presented. Returns a patch, or null to
50
+ * abstain. Merging is gap-filling: a property already settled is never
51
+ * overwritten, so the author's annotations beat every rule and a consumer's
52
+ * rules beat labkit's built-ins.
53
+ */
54
+ type ConfigRule = (ctx: ConfigRuleContext) => LeafPatch | null;
55
+ /** A presentational bucket of leaves, rendered under one heading. */
56
+ interface SectionSpec {
57
+ label: string;
58
+ paths: readonly string[];
59
+ }
60
+ /** A schema resolved against a set of rules: the vocabulary weasel-ui renders,
61
+ * plus the three things labkit keeps on the side because `PrefLeaf` has no
62
+ * field for them. */
63
+ interface ResolvedConfig {
64
+ /** Flat: every leaf is a direct child, so a leaf's path is its config key. */
65
+ group: ToolPrefGroup;
66
+ sections: readonly SectionSpec[];
67
+ showIf: ReadonlyMap<string, (config: Record<string, unknown>) => boolean>;
68
+ /** Node-level `.render` overrides, keyed by path. */
69
+ renderers: Readonly<Record<string, ControlRenderer>>;
70
+ }
71
+ /** Per-node extras that do not belong on a `PrefLeaf`. */
72
+ interface NodeOptions {
73
+ section?: string;
74
+ showIf?: (config: Record<string, unknown>) => boolean;
75
+ render?: ControlRenderer;
76
+ validate?: (leaf: ToolPrefLeaf) => string[];
77
+ }
78
+ /** The builder's leaf: a kind (or null, to be decided by rules), a default,
79
+ * an annotation bag, and the extras above. */
80
+ interface ConfigNode<T = unknown> {
81
+ readonly kind: string | null;
82
+ readonly default: T;
83
+ readonly annotations: Readonly<Annotations>;
84
+ readonly options: Readonly<NodeOptions>;
85
+ }
86
+ /** The value type a node produces. */
87
+ type NodeValue<N> = N extends ConfigNode<infer T> ? T : never;
88
+ /** The config type a builder shape produces. */
89
+ type InferConfig<S> = {
90
+ [K in keyof S]: NodeValue<S[K]>;
91
+ };
92
+ /** An instrument's config, declared once. */
93
+ interface ConfigSchema<TC> {
94
+ readonly nodes: Readonly<Record<string, ConfigNode>>;
95
+ /** The starting config — what `defaultConfig()` would have returned. */
96
+ defaults(): TC;
97
+ }
98
+ /** The config type behind a schema: `ConfigOf<typeof sceneConfig>`. */
99
+ type ConfigOf<S> = S extends ConfigSchema<infer TC> ? TC : never;
100
+
101
+ /** Which control a config field is edited with. */
102
+ type ConfigFieldType = 'slider' | 'checkbox' | 'select' | 'number' | 'text' | 'color';
103
+ /** What every config field carries: the config key it writes, the label shown
104
+ * beside it, and which control renders it. */
105
+ interface ConfigFieldBase {
106
+ key: string;
107
+ label: string;
108
+ type: ConfigFieldType;
109
+ }
110
+ /** A bounded number edited by dragging. */
111
+ interface SliderField extends ConfigFieldBase {
112
+ type: 'slider';
113
+ default: number;
114
+ min: number;
115
+ max: number;
116
+ step?: number;
117
+ }
118
+ /** A boolean. */
119
+ interface CheckboxField extends ConfigFieldBase {
120
+ type: 'checkbox';
121
+ default: boolean;
122
+ }
123
+ /** One choice in a select field. */
124
+ interface SelectOption {
125
+ value: string;
126
+ label: string;
127
+ }
128
+ /** A fixed set of labeled choices. */
129
+ interface SelectField extends ConfigFieldBase {
130
+ type: 'select';
131
+ default: string;
132
+ options: SelectOption[];
133
+ }
134
+ /** A number typed directly, optionally bounded. Use a slider field instead
135
+ * when the range matters more than the exact value. */
136
+ interface NumberField extends ConfigFieldBase {
137
+ type: 'number';
138
+ default: number;
139
+ min?: number;
140
+ max?: number;
141
+ step?: number;
142
+ }
143
+ /** A free-text string. Writes are debounced so typing does not re-run the
144
+ * instrument on every keystroke. */
145
+ interface TextField extends ConfigFieldBase {
146
+ type: 'text';
147
+ default: string;
148
+ placeholder?: string;
149
+ maxLength?: number;
150
+ /** Milliseconds to debounce live setConfig calls. Default 150 ms. Set to 0 to disable. */
151
+ debounceMs?: number;
152
+ }
153
+ /** A color, as a CSS color string. */
154
+ interface ColorField extends ConfigFieldBase {
155
+ type: 'color';
156
+ default: string;
157
+ }
158
+ /** One field of an instrument's config schema. The schema is what the control
159
+ * panel renders, and what `validateConfigSchema` checks. */
160
+ type ConfigField = SliderField | CheckboxField | SelectField | NumberField | TextField | ColorField;
161
+
162
+ export type { Annotations as A, ConfigNode as C, InferConfig as I, LeafPatch as L, NodeOptions as N, ResolvedConfig as R, SectionSpec as S, TextField as T, ControlRenderer as a, ConfigOption as b, ConfigSchema as c, ConfigField as d, ConfigRule as e, ConfigRuleContext as f, CheckboxField as g, ColorField as h, ConfigFieldBase as i, ConfigFieldType as j, ConfigOf as k, NodeValue as l, NumberField as m, SelectField as n, SelectOption as o, SliderField as p };
@@ -0,0 +1,56 @@
1
+ /** What a running job reports as it goes. `total` may arrive at any point and may
2
+ * arrive more than once; a job that cannot count up front simply never sends it.
3
+ *
4
+ * `failed` is a first-class event rather than a thrown error because these
5
+ * failures are per item: a run with two failed items is a partial success, and
6
+ * its other items are worth showing. */
7
+ type JobEvent<T> = {
8
+ kind: 'total';
9
+ total: number;
10
+ } | {
11
+ kind: 'item';
12
+ item: T;
13
+ } | {
14
+ kind: 'failed';
15
+ index: number;
16
+ error: string;
17
+ };
18
+ /** Where a job is. `idle` before its first run and after a cancel; `done` when the
19
+ * iterable finished, whether or not items failed along the way. */
20
+ type JobStatus = 'idle' | 'running' | 'done' | 'error';
21
+ /** Declares that an instrument has work too slow to do during a render: what to
22
+ * run, when to re-run it, and how each result folds into state. */
23
+ interface JobCapability<TS = unknown, TC = unknown, TItem = unknown> {
24
+ /** Re-run whenever this value changes, compared element-wise. A job with no
25
+ * `key` runs only when something calls `start()`. */
26
+ key?: (config: TC, state: TS) => readonly unknown[];
27
+ /** Start on mount and on every `key` change. Default false. */
28
+ auto?: boolean;
29
+ run: (args: {
30
+ config: TC;
31
+ state: TS;
32
+ signal: AbortSignal;
33
+ }) => AsyncIterable<JobEvent<TItem>>;
34
+ /** Fold one result into state. Called once per `item` event, in arrival order. */
35
+ onItem: (item: TItem, state: TS) => TS;
36
+ }
37
+ /** One item that failed, and why. */
38
+ interface JobFailure {
39
+ index: number;
40
+ error: string;
41
+ }
42
+ /** What `RenderContext.job` exposes. Present only when the instrument declares the
43
+ * capability; `undefined` otherwise. */
44
+ interface JobHandle {
45
+ status: JobStatus;
46
+ done: number;
47
+ /** Null until the job reports a total, and forever if it never does. */
48
+ total: number | null;
49
+ failures: readonly JobFailure[];
50
+ /** The error that ended the run, when `status` is `'error'`. */
51
+ error: string | null;
52
+ start: () => void;
53
+ cancel: () => void;
54
+ }
55
+
56
+ export type { JobHandle as J, JobCapability as a, JobEvent as b, JobFailure as c, JobStatus as d };
@@ -1,96 +1,8 @@
1
+ import { a as StorageAdapter, b as LabStoreState, T as TrialRecord, c as SavedSnapshot, e as LabMode, I as InstrumentSerializers, C as CreateLabStoreOptions, d as TrialStateHandle } from './types-1Sdxy_Pv.js';
1
2
  import * as react from 'react';
2
3
  import { ReactNode, ReactElement } from 'react';
3
4
  import { StoreApi } from 'zustand/vanilla';
4
5
 
5
- /** A trial's undo history, as snapshots of its state either side of the
6
- * present. */
7
- interface UndoStack {
8
- past: unknown[];
9
- future: unknown[];
10
- }
11
- /** One trial as the store holds it: which instrument it runs, that
12
- * instrument's config and state, the camera, and the undo history. */
13
- interface TrialRecord<TS = unknown, TC = unknown> {
14
- id: string;
15
- instrumentName: string;
16
- config: TC;
17
- state: TS;
18
- view: {
19
- zoom: number;
20
- pan: {
21
- x: number;
22
- y: number;
23
- };
24
- };
25
- undoStack: UndoStack;
26
- }
27
- /** A named, saved copy of a trial's config and state, restorable into any
28
- * trial running the same instrument. */
29
- interface SavedSnapshot {
30
- id: string;
31
- name: string;
32
- trialId: string;
33
- instrumentName: string;
34
- config: unknown;
35
- state: unknown;
36
- savedAt: number;
37
- }
38
- /** `auto` follows the OS; the other two are an explicit choice. */
39
- type LabMode = 'auto' | 'light' | 'dark';
40
- /** Everything a lab persists: its trials, its saved snapshots, and the
41
- * chosen color mode. */
42
- interface LabStoreState {
43
- trials: TrialRecord[];
44
- savedSnapshots: SavedSnapshot[];
45
- mode: LabMode;
46
- /** Per-trial tile extents, keyed by trial id. Opaque here — the
47
- * shape belongs to whatever lays the trials out. */
48
- layout: Record<string, unknown>;
49
- }
50
- /** Where a lab persists itself. Implementations are keyed string storage and
51
- * nothing more, so the same store works against localStorage, the URL hash,
52
- * or memory. */
53
- interface StorageAdapter {
54
- read(key: string): string | null;
55
- write(key: string, value: string): void;
56
- delete?(key: string): void;
57
- }
58
- /** What `useTrialState` hands an instrument: its state and config, with
59
- * a setter for each. */
60
- interface TrialStateHandle<TS, TC> {
61
- state: TS;
62
- setState: (next: TS | ((prev: TS) => TS)) => void;
63
- config: TC;
64
- setConfig: (key: keyof TC, value: TC[keyof TC]) => void;
65
- }
66
- /** Options for `createLabStore`. `storageKey` namespaces the keys written, so
67
- * two labs on one origin do not collide. */
68
- interface CreateLabStoreOptions {
69
- storageKey: string;
70
- storage: StorageAdapter;
71
- initialMode?: LabMode;
72
- }
73
- /** Per-instrument serialize/deserialize hooks, keyed by instrument name. An
74
- * instrument whose state is already JSON-safe needs no entry. */
75
- type InstrumentSerializers = Record<string, {
76
- serialize?: (state: unknown) => unknown;
77
- deserialize?: (data: unknown) => unknown;
78
- } | undefined>;
79
- /** A trial as it is persisted: everything but the undo history, which is
80
- * session-only. */
81
- type SerializedTrial = Omit<TrialRecord, 'undoStack'>;
82
- /** Everything a lab persists, under one key, at a known version. */
83
- interface LabDocument {
84
- version: number;
85
- trials: SerializedTrial[];
86
- saves: SavedSnapshot[];
87
- layout: Record<string, unknown>;
88
- mode: LabMode;
89
- }
90
- /** Migrates a document one version forward. Index `i` in the chain takes a
91
- * version-`i` document to version `i + 1`. */
92
- type Migration = (doc: Record<string, unknown>) => Record<string, unknown>;
93
-
94
6
  /** Persist to `localStorage` — state survives a reload and a new tab. Falls
95
7
  * back to no-ops when storage is unavailable. */
96
8
  declare const localStorageAdapter: StorageAdapter;
@@ -112,7 +24,7 @@ interface LabStoreActions {
112
24
  removeTrial: (id: string) => void;
113
25
  updateTrialState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;
114
26
  updateTrialConfig: <TC>(id: string, key: keyof TC, value: TC[keyof TC]) => void;
115
- updateTrialView: (id: string, view: TrialRecord['view']) => void;
27
+ updateTrialView: (id: string, view: unknown) => void;
116
28
  updateTrialUndoStack: (id: string, next: TrialRecord['undoStack'] | ((prev: TrialRecord['undoStack']) => TrialRecord['undoStack'])) => void;
117
29
  setTrialInstrument: (id: string, instrumentName: string) => void;
118
30
  saveSnapshot: (trialId: string, name: string) => void;
@@ -120,6 +32,8 @@ interface LabStoreActions {
120
32
  deleteSnapshot: (snapshotId: string) => void;
121
33
  listSnapshots: (trialId?: string) => SavedSnapshot[];
122
34
  setMode: (mode: LabMode) => void;
35
+ setLabTool: (id: string | null) => void;
36
+ setTrialTool: (trialId: string, id: string | null) => void;
123
37
  setLayout: (layout: Record<string, unknown>) => void;
124
38
  }
125
39
  /** A lab's store: its state and actions, plus the hook instruments use to
@@ -171,5 +85,5 @@ declare function quarantineKey(storageKey: string): string;
171
85
  * running in. Throws outside a lab store and trial. */
172
86
  declare function useTrialState<TS = unknown, TC = unknown>(): TrialStateHandle<TS, TC>;
173
87
 
174
- export { CURRENT_DOCUMENT_VERSION as C, LabStoreContext as e, LabStoreProvider as f, TrialIdContext as i, TrialIdProvider as j, createLabStore as l, createMemoryAdapter as m, labDocumentKey as n, localStorageAdapter as o, noneAdapter as p, quarantineKey as q, useLabStore as r, sessionStorageAdapter as s, useTrialId as t, urlHashAdapter as u, useTrialState as v };
175
- export type { InstrumentSerializers as I, LabDocument as L, Migration as M, SerializedTrial as S, TrialRecord as T, UndoStack as U, StorageAdapter as a, CreateLabStoreOptions as b, LabStore as c, LabStoreActions as d, LabStoreState as g, SavedSnapshot as h, TrialStateHandle as k, LabMode as w };
88
+ export { CURRENT_DOCUMENT_VERSION as C, TrialIdContext as T, LabStoreContext as b, LabStoreProvider as c, TrialIdProvider as d, createLabStore as e, createMemoryAdapter as f, localStorageAdapter as g, useLabStore as h, useTrialId as i, useTrialState as j, labDocumentKey as l, noneAdapter as n, quarantineKey as q, sessionStorageAdapter as s, urlHashAdapter as u };
89
+ export type { LabStore as L, LabStoreActions as a };
@@ -1,8 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { V as ViewTransform, e as Point } from '../_dts/index-iAP6XbH3.js';
5
- import '../_dts/types-x92Kfeme.js';
4
+ import { V as ViewTransform, g as Point } from '../_dts/index-DgcNdEdh.js';
5
+ import '../_dts/types-AHlQxBNN.js';
6
+ import '../_dts/PrefsForm-BYa6cWnO.js';
7
+ import '../_dts/types-1Sdxy_Pv.js';
8
+ import '../_dts/types-DJ79Tg5J.js';
6
9
 
7
10
  /** One layer of a canvas stack: its id, whether it is currently shown, and how
8
11
  * it paints itself. */
@@ -17,6 +20,8 @@ interface CanvasStackProps {
17
20
  layers: CanvasLayerDescriptor[];
18
21
  view: ViewTransform;
19
22
  onViewChange: (v: ViewTransform) => void;
23
+ minZoom?: number;
24
+ maxZoom?: number;
20
25
  width?: number | string;
21
26
  height?: number | string;
22
27
  className?: string;
@@ -26,7 +31,7 @@ interface CanvasStackProps {
26
31
  /** Stacks one `<canvas>` per layer and drives them from a shared view, so a
27
32
  * layer that changes rarely is not redrawn with one that changes every frame.
28
33
  * Handles sizing, device pixel ratio, and pan/zoom. */
29
- declare function CanvasStack({ layers, view, onViewChange, width, height, className, onHitTest, children, }: CanvasStackProps): react_jsx_runtime.JSX.Element;
34
+ declare function CanvasStack({ layers, view, onViewChange, minZoom, maxZoom, width, height, className, onHitTest, children, }: CanvasStackProps): react_jsx_runtime.JSX.Element;
30
35
 
31
36
  /** What a canvas stack publishes to its children — currently the view, so
32
37
  * DOM overlays can position themselves in the same coordinates. */
@@ -1,4 +1,5 @@
1
- export { CanvasStack, screenToWorld, worldToScreen } from '../chunk-574LJAV4.js';
1
+ export { CanvasStack, screenToWorld, worldToScreen } from '../chunk-NS54R52R.js';
2
2
  export { CanvasStackContext } from '../chunk-CPUJ3QXL.js';
3
+ import '../chunk-Z6HJ5FEM.js';
3
4
  //# sourceMappingURL=index.js.map
4
5
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,86 @@
1
+ import { a as ControlRenderer } from '../_dts/types-AHlQxBNN.js';
2
+ import { I as Instrument, m as TrialChromeContext, T as TrialContribution } from '../_dts/index-DgcNdEdh.js';
3
+ export { e as IconComponent, S as SidebarSection, h as StatusReadout, k as ToolItem, l as ToolbarItem, n as TrialRegion, o as ViewportControl } from '../_dts/index-DgcNdEdh.js';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import '../_dts/PrefsForm-BYa6cWnO.js';
6
+ import 'react';
7
+ import '../_dts/types-1Sdxy_Pv.js';
8
+ import '../_dts/types-DJ79Tg5J.js';
9
+
10
+ /**
11
+ * The contributions a trial gets from what its instrument declared. This is
12
+ * the whole "declaring a capability provides the chrome" rule — it replaces
13
+ * the presence checks that used to be spread across the runtime.
14
+ */
15
+ declare function builtinContributions(instrument: Instrument, ctx: TrialChromeContext, controls?: Record<string, ControlRenderer>): TrialContribution[];
16
+
17
+ /**
18
+ * Concatenate contribution bundles into one list, preserving order — order
19
+ * decides how a region lays its contributions out, so it is part of the
20
+ * result, not an accident of it.
21
+ *
22
+ * Throws on a duplicate id rather than dropping one: a contribution silently
23
+ * losing to a later bundle is the failure a registry exists to prevent.
24
+ */
25
+ declare function mergeContributions(...bundles: readonly TrialContribution[][]): TrialContribution[];
26
+ /**
27
+ * Drop contributions by id. Throws when an id is not present: a typo that
28
+ * silently suppresses nothing is the same class of bug as a duplicate id
29
+ * silently winning.
30
+ */
31
+ declare function suppressContributions(bundle: readonly TrialContribution[], ids: readonly string[]): TrialContribution[];
32
+
33
+ /** Props for `<PaletteRegion>`. */
34
+ interface PaletteRegionProps {
35
+ contributions: readonly TrialContribution[];
36
+ ctx: TrialChromeContext;
37
+ }
38
+ /** A trial's tool strip. Selection lives in the trial's or the lab's tool
39
+ * slot; this region only reflects it. */
40
+ declare function PaletteRegion({ contributions, ctx }: PaletteRegionProps): react_jsx_runtime.JSX.Element | null;
41
+
42
+ /** Props for `<SidebarRegion>`. */
43
+ interface SidebarRegionProps {
44
+ contributions: readonly TrialContribution[];
45
+ ctx: TrialChromeContext;
46
+ }
47
+ /** Lays a trial's `sidebar` contributions out as titled, collapsible sections. */
48
+ declare function SidebarRegion({ contributions, ctx }: SidebarRegionProps): react_jsx_runtime.JSX.Element | null;
49
+
50
+ /** Props for `<StatusRegion>`. */
51
+ interface StatusRegionProps {
52
+ contributions: readonly TrialContribution[];
53
+ ctx: TrialChromeContext;
54
+ }
55
+ /** Lays a trial's `status` contributions out as readouts. */
56
+ declare function StatusRegion({ contributions, ctx }: StatusRegionProps): react_jsx_runtime.JSX.Element | null;
57
+
58
+ /** Props for `<TitleBarRegion>`. */
59
+ interface TitleBarRegionProps {
60
+ contributions: readonly TrialContribution[];
61
+ ctx: TrialChromeContext;
62
+ }
63
+ /** Lays a trial's `titlebar` contributions out, at the far end of the bar. */
64
+ declare function TitleBarRegion({ contributions, ctx }: TitleBarRegionProps): react_jsx_runtime.JSX.Element | null;
65
+
66
+ /** Props for `<ToolbarRegion>`. */
67
+ interface ToolbarRegionProps {
68
+ contributions: readonly TrialContribution[];
69
+ ctx: TrialChromeContext;
70
+ }
71
+ /** Lays a trial's `toolbar` contributions out, grouped by their `group`. */
72
+ declare function ToolbarRegion({ contributions, ctx }: ToolbarRegionProps): react_jsx_runtime.JSX.Element | null;
73
+
74
+ /** Props for `<ViewportRegion>`. */
75
+ interface ViewportRegionProps {
76
+ contributions: readonly TrialContribution[];
77
+ ctx: TrialChromeContext;
78
+ }
79
+ /**
80
+ * Controls acting on the view of a trial. Anchored inside the content well
81
+ * rather than in the toolbar, which acts on the trial itself.
82
+ */
83
+ declare function ViewportRegion({ contributions, ctx }: ViewportRegionProps): react_jsx_runtime.JSX.Element | null;
84
+
85
+ export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, TrialChromeContext, TrialContribution, ViewportRegion, builtinContributions, mergeContributions, suppressContributions };
86
+ export type { PaletteRegionProps, SidebarRegionProps, StatusRegionProps, TitleBarRegionProps, ToolbarRegionProps, ViewportRegionProps };
@@ -0,0 +1,8 @@
1
+ export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, mergeContributions, suppressContributions } from '../chunk-PO6L3NSH.js';
2
+ import '../chunk-O7NVSCLN.js';
3
+ import '../chunk-ZZAYVX4X.js';
4
+ import '../chunk-TLGRYALP.js';
5
+ import '../chunk-CPUJ3QXL.js';
6
+ import '../chunk-Z6HJ5FEM.js';
7
+ //# sourceMappingURL=index.js.map
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -278,6 +278,7 @@ function createLabStore(options) {
278
278
  savedSnapshots: hydratedSnapshots,
279
279
  mode: hydratedMode,
280
280
  layout: hydratedLayout,
281
+ activeToolId: null,
281
282
  addTrial: (record) => {
282
283
  set((s) => ({
283
284
  trials: [...s.trials, { ...record, undoStack: emptyUndoStack() }]
@@ -293,6 +294,7 @@ function createLabStore(options) {
293
294
  trials: s.trials.map((w) => {
294
295
  if (w.id !== id) return w;
295
296
  const nextState = typeof next === "function" ? next(w.state) : next;
297
+ if (Object.is(nextState, w.state)) return w;
296
298
  return { ...w, state: nextState };
297
299
  })
298
300
  }));
@@ -312,7 +314,7 @@ function createLabStore(options) {
312
314
  },
313
315
  updateTrialView: (id, view) => {
314
316
  set((s) => ({
315
- trials: s.trials.map((w) => w.id === id ? { ...w, view } : w)
317
+ trials: s.trials.map((w) => w.id === id && !Object.is(view, w.view) ? { ...w, view } : w)
316
318
  }));
317
319
  scheduleFlush();
318
320
  },
@@ -386,6 +388,10 @@ function createLabStore(options) {
386
388
  set({ mode });
387
389
  scheduleFlush();
388
390
  },
391
+ setLabTool: (id) => set({ activeToolId: id }),
392
+ setTrialTool: (trialId, id) => set((s) => ({
393
+ trials: s.trials.map((t) => t.id === trialId ? { ...t, activeToolId: id } : t)
394
+ })),
389
395
  setLayout: (layout) => {
390
396
  set({ layout });
391
397
  scheduleFlush();
@@ -484,5 +490,5 @@ function useTrialState() {
484
490
  }
485
491
 
486
492
  export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeTrials, emptyUndoStack, encodeUrlHash, labDocumentKey, labStorageKey, localStorageAdapter, noneAdapter, quarantineKey, serializeTrials, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState };
487
- //# sourceMappingURL=chunk-3TYUJR7Z.js.map
488
- //# sourceMappingURL=chunk-3TYUJR7Z.js.map
493
+ //# sourceMappingURL=chunk-4TMMVIDM.js.map
494
+ //# sourceMappingURL=chunk-4TMMVIDM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/state/helpers.ts","../src/state/adapters.ts","../src/state/context.tsx","../src/state/document.ts","../src/state/store.ts","../src/state/useTrialState.ts"],"names":["useContext","useStore"],"mappings":"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","file":"chunk-4TMMVIDM.js","sourcesContent":["import type { InstrumentSerializers, SerializedTrial, TrialRecord, UndoStack } from './types';\n\n/** The storage key a lab writes one of its buckets under. Namespaced by\n * `storageKey` so two labs sharing an origin do not collide. */\nexport function labStorageKey(\n storageKey: string,\n bucket: 'workspaces' | 'saves' | 'theme' | 'layout',\n): string {\n return `lk:${storageKey}:${bucket}`;\n}\n\n/** Encode a string for the URL fragment. */\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\n/** Decode a URL fragment written by `encodeUrlHash`, or `null` if it is\n * malformed. */\nexport function decodeUrlHash(hash: string): string | null {\n if (!hash) return null;\n try {\n return decodeURIComponent(atob(hash));\n } catch {\n return null;\n }\n}\n\n/** A fresh, empty undo history. */\nexport function emptyUndoStack(): UndoStack {\n return { past: [], future: [] };\n}\n\n/** Serialize trials for storage, running each instrument's own serializer\n * over its state. Undo history is deliberately dropped — it does not survive\n * a reload. */\nexport function serializeTrials(\n trials: TrialRecord[],\n serializers: InstrumentSerializers,\n): SerializedTrial[] {\n return trials.map(({ undoStack: _undo, ...w }) => {\n const s = serializers[w.instrumentName];\n return { ...w, state: s?.serialize ? s.serialize(w.state) : w.state };\n });\n}\n\n/** Rebuild trials from storage, running each instrument's deserializer\n * over its state and starting each with an empty undo history. Returns an\n * empty list rather than throwing on malformed input. */\nexport function deserializeTrials(\n records: SerializedTrial[],\n deserializers: InstrumentSerializers,\n): TrialRecord[] {\n if (!Array.isArray(records)) {\n console.warn('[labkit] deserializeTrials: not an array, returning empty list');\n return [];\n }\n return records.map((r) => {\n const d = deserializers[r.instrumentName];\n return {\n ...r,\n state: d?.deserialize ? d.deserialize(r.state) : r.state,\n undoStack: emptyUndoStack(),\n };\n });\n}\n","import { decodeUrlHash, encodeUrlHash } from './helpers';\nimport type { StorageAdapter } from './types';\n\n/** Persist to `localStorage` — state survives a reload and a new tab. Falls\n * back to no-ops when storage is unavailable. */\nexport const localStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] localStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\n/** Persist to `sessionStorage` — state survives a reload but not a new tab. */\nexport const sessionStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n sessionStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] sessionStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n sessionStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\nconst URL_HASH_GUARD = typeof window !== 'undefined';\n\nfunction readHashMap(): Record<string, string> {\n if (!URL_HASH_GUARD) return {};\n const raw = decodeUrlHash(window.location.hash.replace(/^#/, ''));\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, string>;\n } catch {\n return {};\n }\n}\n\nfunction writeHashMap(map: Record<string, string>): void {\n if (!URL_HASH_GUARD) return;\n const encoded = encodeUrlHash(JSON.stringify(map));\n window.history.replaceState(null, '', `#${encoded}`);\n}\n\n/** Persist into the URL fragment, so the page's link carries its state and\n * can be shared or bookmarked. */\nexport const urlHashAdapter: StorageAdapter = {\n read: (key) => readHashMap()[key] ?? null,\n write: (key, value) => {\n const map = readHashMap();\n map[key] = value;\n writeHashMap(map);\n },\n delete: (key) => {\n const map = readHashMap();\n delete map[key];\n writeHashMap(map);\n },\n};\n\n/** An in-memory store, discarded on reload. For tests, and for labs that\n * should start fresh every time. */\nexport function createMemoryAdapter(): StorageAdapter {\n const store = new Map<string, string>();\n return {\n read: (key) => store.get(key) ?? null,\n write: (key, value) => {\n store.set(key, value);\n },\n delete: (key) => {\n store.delete(key);\n },\n };\n}\n\n/** Persists nothing and reads back nothing. */\nexport const noneAdapter: StorageAdapter = {\n read: () => null,\n write: () => {},\n delete: () => {},\n};\n","import { createContext, type ReactElement, type ReactNode, useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport type { LabStore } from './store';\nimport type { LabStoreState } from './types';\n\ntype LabStoreCtx = { store: LabStore } | null;\n\n/** Context carrying the lab store. Prefer `useLabStore`; this is exported for\n * code that needs to read the context without subscribing. */\nexport const LabStoreContext = createContext<LabStoreCtx>(null);\n\n/** Provides a lab store to its subtree. */\nexport function LabStoreProvider({\n store,\n children,\n}: {\n store: LabStore;\n children: ReactNode;\n}): ReactElement {\n return <LabStoreContext.Provider value={{ store }}>{children}</LabStoreContext.Provider>;\n}\n\n/** Subscribe to the whole lab store. Throws outside a `<LabStoreProvider>`. */\nexport function useLabStore(): LabStoreState & ReturnType<LabStore['getState']> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useLabStore must be used inside <LabStoreProvider>');\n return useStore(ctx.store);\n}\n\n/** Context carrying which trial the subtree belongs to. */\nexport const TrialIdContext = createContext<string | null>(null);\n\n/** Names the trial its subtree belongs to, so an instrument's hooks can\n * find their own record in the store without being passed an id. */\nexport function TrialIdProvider({\n trialId,\n children,\n}: {\n trialId: string;\n children: ReactNode;\n}): ReactElement {\n return <TrialIdContext.Provider value={trialId}>{children}</TrialIdContext.Provider>;\n}\n\n/** The id of the trial this component is inside. Throws outside a\n * `<TrialIdProvider>`. */\nexport function useTrialId(): string {\n const id = useContext(TrialIdContext);\n if (!id) throw new Error('[labkit] useTrialId must be used inside <TrialIdProvider>');\n return id;\n}\n","import { labStorageKey } from './helpers';\nimport type {\n LabDocument,\n LabMode,\n Migration,\n SavedSnapshot,\n SerializedTrial,\n StorageAdapter,\n} from './types';\n\n/** Bumped whenever the persisted shape changes; every bump needs a migration. */\nexport const CURRENT_DOCUMENT_VERSION = 2;\n\n/** The one key a lab persists under. The `:doc` suffix keeps it out of the\n * legacy bucket namespace, where a lab named `a:saves` would otherwise write\n * its document over lab `a`'s saves bucket. */\nexport function labDocumentKey(storageKey: string): string {\n return `lk:${storageKey}:doc`;\n}\n\n/** Where a document that failed to migrate is set aside, so a bad migration\n * loses state loudly rather than silently. */\nexport function quarantineKey(storageKey: string): string {\n return `lk:${storageKey}:quarantine`;\n}\n\n/** A fresh document at the current version. */\nexport function emptyDocument(mode: LabMode): LabDocument {\n return {\n version: CURRENT_DOCUMENT_VERSION,\n trials: [],\n saves: [],\n layout: {},\n mode,\n };\n}\n\n/** What a migration run produced: the migrated document, or why it refused. */\nexport type MigrationOutcome =\n | { ok: true; doc: Record<string, unknown>; migrated: boolean }\n | { ok: false; reason: 'future' | 'failed'; error?: unknown };\n\n/** Walk a document forward to `target`, one migration at a time. A version\n * above `target` is refused rather than parsed under a shape it may not\n * have. */\nexport function runMigrations(\n raw: Record<string, unknown>,\n migrations: Migration[],\n target: number,\n): MigrationOutcome {\n const from = typeof raw.version === 'number' ? raw.version : 0;\n if (from > target) return { ok: false, reason: 'future' };\n if (from === target) return { ok: true, doc: raw, migrated: false };\n\n let doc = raw;\n try {\n for (let v = from; v < target; v++) {\n const migration = migrations[v];\n if (!migration) throw new Error(`[labkit] no migration from version ${v}`);\n doc = migration(doc);\n }\n } catch (error) {\n return { ok: false, reason: 'failed', error };\n }\n return { ok: true, doc, migrated: true };\n}\n\nconst LEGACY_BUCKETS = ['workspaces', 'saves', 'layout', 'theme'] as const;\n\nfunction parseOr<T>(raw: string | null, fallback: T, bucket: string): T {\n if (raw === null) return fallback;\n try {\n return JSON.parse(raw) as T;\n } catch {\n console.warn(`[labkit] unparseable legacy bucket \"${bucket}\", dropping it`);\n return fallback;\n }\n}\n\n/** Assemble a version-0 document out of the four pre-document keys, or null\n * if none of them is present. `fallbackMode` is used when the theme key is\n * absent or holds something unrecognized; `interstellar` passes through\n * untouched for `migrateV0toV1` to coerce. */\nexport function readLegacyDocument(\n storage: StorageAdapter,\n storageKey: string,\n fallbackMode: LabMode,\n): Record<string, unknown> | null {\n const present = LEGACY_BUCKETS.some((b) => storage.read(labStorageKey(storageKey, b)) !== null);\n if (!present) return null;\n\n const rawMode = storage.read(labStorageKey(storageKey, 'theme'));\n const mode =\n rawMode === 'light' || rawMode === 'dark' || rawMode === 'auto' || rawMode === 'interstellar'\n ? rawMode\n : fallbackMode;\n\n return {\n version: 0,\n workspaces: parseOr(storage.read(labStorageKey(storageKey, 'workspaces')), [], 'workspaces'),\n saves: parseOr(storage.read(labStorageKey(storageKey, 'saves')), [], 'saves'),\n layout: parseOr(storage.read(labStorageKey(storageKey, 'layout')), {}, 'layout'),\n mode,\n };\n}\n\n/** Delete the four pre-document keys, but only once `expectedDocument` is\n * confirmed to be exactly what is stored under the document key — a\n * read-back, not a trust of the write that produced it. On any mismatch\n * (write failed, landed partially, or never happened) it deletes nothing,\n * warns, and returns `false` so the legacy buckets stay as a recoverable\n * copy. Deletion itself is read back too — `StorageAdapter.delete` is\n * optional, so an adapter without it leaves the keys in place — and `true`\n * is returned only once all four are actually gone. */\nexport function deleteLegacyKeys(\n storage: StorageAdapter,\n storageKey: string,\n expectedDocument: string,\n): boolean {\n const actual = storage.read(labDocumentKey(storageKey));\n if (actual !== expectedDocument) {\n console.warn(\n `[labkit] keeping legacy keys for \"${storageKey}\": could not confirm the migrated document was written`,\n );\n return false;\n }\n let allGone = true;\n for (const bucket of LEGACY_BUCKETS) {\n const key = labStorageKey(storageKey, bucket);\n storage.delete?.(key);\n if (storage.read(key) !== null) allGone = false;\n }\n return allGone;\n}\n\n/** Set `raw` aside under the quarantine key, reading it back to confirm it\n * landed. Returns `false` when it did not — a full disk is exactly when a\n * document goes unreadable — so the caller can leave the original alone\n * rather than overwrite the only copy of it. */\nexport function quarantineDocument(\n storage: StorageAdapter,\n storageKey: string,\n raw: string,\n): boolean {\n storage.write(quarantineKey(storageKey), raw);\n return storage.read(quarantineKey(storageKey)) === raw;\n}\n\n/** Fill in whatever a document is missing or holds the wrong shape of, so a\n * hydrated store never sees `undefined` where a section should be. Applied to\n * every document, migrated or already current. */\nexport function normalizeDocument(\n doc: Record<string, unknown>,\n fallbackMode: LabMode,\n): LabDocument {\n const mode =\n doc.mode === 'light' || doc.mode === 'dark' || doc.mode === 'auto' ? doc.mode : fallbackMode;\n return {\n version: CURRENT_DOCUMENT_VERSION,\n trials: Array.isArray(doc.trials) ? (doc.trials as SerializedTrial[]) : [],\n saves: Array.isArray(doc.saves) ? (doc.saves as SavedSnapshot[]) : [],\n layout:\n doc.layout && typeof doc.layout === 'object' ? (doc.layout as Record<string, unknown>) : {},\n mode,\n };\n}\n\n/** Version 0 (four loose keys) to version 1 (one document). Normalizes the\n * mode, including `interstellar` — the dark mode's name back when it was a\n * theme. Coerces against version 1's own field names rather than reusing\n * `normalizeDocument`, which produces the current shape and would drop\n * `workspaces` from a document it labels version 1. */\nexport function migrateV0toV1(doc: Record<string, unknown>): Record<string, unknown> {\n const raw = doc.mode === 'interstellar' ? 'dark' : doc.mode;\n const mode = raw === 'light' || raw === 'dark' || raw === 'auto' ? raw : 'auto';\n return {\n version: 1,\n workspaces: Array.isArray(doc.workspaces) ? doc.workspaces : [],\n saves: Array.isArray(doc.saves) ? doc.saves : [],\n layout: doc.layout && typeof doc.layout === 'object' ? doc.layout : {},\n mode,\n };\n}\n\n/** Version 1 to version 2: the vocabulary refresh renamed a tile from\n * workspace to trial. Only the records field moves; a record's own fields are\n * unchanged. */\nexport function migrateV1toV2(doc: Record<string, unknown>): Record<string, unknown> {\n const { workspaces, ...rest } = doc;\n return { ...rest, trials: Array.isArray(workspaces) ? workspaces : [], version: 2 };\n}\n\n/** Index `i` migrates a version-`i` document to version `i + 1`. */\nexport const MIGRATIONS: Migration[] = [migrateV0toV1, migrateV1toV2];\n","import { createStore, type StoreApi } from 'zustand/vanilla';\nimport {\n CURRENT_DOCUMENT_VERSION,\n deleteLegacyKeys,\n emptyDocument,\n labDocumentKey,\n MIGRATIONS,\n normalizeDocument,\n quarantineDocument,\n readLegacyDocument,\n runMigrations,\n} from './document';\nimport { deserializeTrials, emptyUndoStack, serializeTrials } from './helpers';\nimport type {\n CreateLabStoreOptions,\n InstrumentSerializers,\n LabDocument,\n LabMode,\n LabStoreState,\n SavedSnapshot,\n TrialRecord,\n} from './types';\n\n/** Every mutation a lab store supports: managing trials, saving and\n * restoring snapshots, and setting the color mode. */\nexport interface LabStoreActions {\n addTrial: (record: Omit<TrialRecord, 'undoStack'>) => void;\n removeTrial: (id: string) => void;\n updateTrialState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;\n updateTrialConfig: <TC>(id: string, key: keyof TC, value: TC[keyof TC]) => void;\n updateTrialView: (id: string, view: unknown) => void;\n updateTrialUndoStack: (\n id: string,\n next: TrialRecord['undoStack'] | ((prev: TrialRecord['undoStack']) => TrialRecord['undoStack']),\n ) => void;\n setTrialInstrument: (id: string, instrumentName: string) => void;\n saveSnapshot: (trialId: string, name: string) => void;\n loadSnapshot: (snapshotId: string, trialId: string) => void;\n deleteSnapshot: (snapshotId: string) => void;\n listSnapshots: (trialId?: string) => SavedSnapshot[];\n setMode: (mode: LabMode) => void;\n setLabTool: (id: string | null) => void;\n setTrialTool: (trialId: string, id: string | null) => void;\n setLayout: (layout: Record<string, unknown>) => void;\n}\n\n/** A lab's store: its state and actions, plus the hook instruments use to\n * register how their state is serialized. */\nexport type LabStore = StoreApi<LabStoreState & LabStoreActions> & {\n registerSerializers: (s: InstrumentSerializers) => void;\n};\n\n/** Build a lab store, hydrating from storage if anything was saved under the\n * same key. Writes back are debounced. */\nexport function createLabStore(options: CreateLabStoreOptions): LabStore {\n let serializers: InstrumentSerializers = {};\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const hydration = hydrateDocument(options);\n const hydrated = hydration.document;\n const persistDisabled = hydration.persistDisabled;\n // Cleared once the legacy keys are actually gone; see the flush.\n let foldedFromLegacy = hydration.foldedFromLegacy;\n\n const hydratedTrials = deserializeTrials(hydrated.trials, serializers);\n const hydratedSnapshots = hydrated.saves;\n const hydratedLayout = hydrated.layout;\n const hydratedMode = hydrated.mode;\n\n const store = createStore<LabStoreState & LabStoreActions>()((set, get) => ({\n trials: hydratedTrials,\n savedSnapshots: hydratedSnapshots,\n mode: hydratedMode,\n layout: hydratedLayout,\n activeToolId: null,\n\n addTrial: (record) => {\n set((s) => ({\n trials: [...s.trials, { ...record, undoStack: emptyUndoStack() }],\n }));\n scheduleFlush();\n },\n\n removeTrial: (id) => {\n set((s) => ({ trials: s.trials.filter((w) => w.id !== id) }));\n scheduleFlush();\n },\n\n updateTrialState: (id, next) => {\n set((s) => ({\n trials: s.trials.map((w) => {\n if (w.id !== id) return w;\n const nextState =\n typeof next === 'function' ? (next as (prev: unknown) => unknown)(w.state) : next;\n // An updater that returns its input means \"nothing changed\", and must not\n // cost a new record: the trial re-renders on record identity, so\n // allocating here turns the standard React bail-out into a render loop.\n if (Object.is(nextState, w.state)) return w;\n return { ...w, state: nextState };\n }),\n }));\n scheduleFlush();\n },\n\n updateTrialConfig: (id, key, value) => {\n set((s) => ({\n trials: s.trials.map((w) => {\n if (w.id !== id) return w;\n return {\n ...w,\n config: { ...(w.config as Record<string, unknown>), [key as string]: value },\n };\n }),\n }));\n scheduleFlush();\n },\n\n updateTrialView: (id, view) => {\n set((s) => ({\n trials: s.trials.map((w) => (w.id === id && !Object.is(view, w.view) ? { ...w, view } : w)),\n }));\n scheduleFlush();\n },\n\n updateTrialUndoStack: (id, next) => {\n set((s) => ({\n trials: s.trials.map((w) => {\n if (w.id !== id) return w;\n const undoStack =\n typeof next === 'function'\n ? (next as (prev: TrialRecord['undoStack']) => TrialRecord['undoStack'])(w.undoStack)\n : next;\n return { ...w, undoStack };\n }),\n }));\n },\n\n setTrialInstrument: (id, instrumentName) => {\n set((s) => ({\n trials: s.trials.map((w) => (w.id === id ? { ...w, instrumentName } : w)),\n }));\n scheduleFlush();\n },\n\n saveSnapshot: (trialId, name) => {\n const trial = get().trials.find((w) => w.id === trialId);\n if (!trial) return;\n const reg = serializers[trial.instrumentName];\n const serializedState = reg?.serialize\n ? reg.serialize(trial.state)\n : structuredClone(trial.state);\n const clonedConfig = structuredClone(trial.config);\n const lastAt = get().savedSnapshots.reduce((m, sn) => (sn.savedAt > m ? sn.savedAt : m), 0);\n const savedAt = Math.max(Date.now(), lastAt + 1);\n const snapshot: SavedSnapshot = {\n id: crypto.randomUUID(),\n name,\n trialId,\n instrumentName: trial.instrumentName,\n config: clonedConfig,\n state: serializedState,\n savedAt,\n };\n set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));\n scheduleFlush();\n },\n\n loadSnapshot: (snapshotId, trialId) => {\n const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);\n if (!snapshot) return;\n const trial = get().trials.find((w) => w.id === trialId);\n if (!trial) return;\n if (snapshot.instrumentName !== trial.instrumentName) {\n console.warn(\n `[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, trial=${trial.instrumentName}); refusing to load`,\n );\n return;\n }\n const reg = serializers[snapshot.instrumentName];\n const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;\n set((s) => ({\n trials: s.trials.map((w) =>\n w.id === trialId ? { ...w, state: restoredState, config: snapshot.config } : w,\n ),\n }));\n scheduleFlush();\n },\n\n deleteSnapshot: (snapshotId) => {\n set((s) => ({\n savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId),\n }));\n scheduleFlush();\n },\n\n listSnapshots: (trialId) => {\n const all = get().savedSnapshots;\n const filtered = trialId ? all.filter((sn) => sn.trialId === trialId) : all;\n return [...filtered].sort((a, b) => b.savedAt - a.savedAt);\n },\n\n setMode: (mode) => {\n set({ mode });\n scheduleFlush();\n },\n\n setLabTool: (id) => set({ activeToolId: id }),\n\n setTrialTool: (trialId, id) =>\n set((s) => ({\n trials: s.trials.map((t) => (t.id === trialId ? { ...t, activeToolId: id } : t)),\n })),\n\n setLayout: (layout) => {\n set({ layout });\n scheduleFlush();\n },\n }));\n\n function scheduleFlush(): void {\n if (persistDisabled) return;\n if (flushTimer) clearTimeout(flushTimer);\n flushTimer = setTimeout(() => {\n const s = store.getState();\n const document: LabDocument = {\n version: CURRENT_DOCUMENT_VERSION,\n trials: serializeTrials(s.trials, serializers),\n saves: s.savedSnapshots,\n layout: s.layout,\n mode: s.mode,\n };\n const serialized = JSON.stringify(document);\n options.storage.write(labDocumentKey(options.storageKey), serialized);\n if (foldedFromLegacy && deleteLegacyKeys(options.storage, options.storageKey, serialized)) {\n foldedFromLegacy = false;\n }\n flushTimer = null;\n }, 300);\n }\n\n // A lab opened and closed without a single mutation still completes its\n // fold; the flush is what removes the legacy keys.\n if (foldedFromLegacy) scheduleFlush();\n\n return Object.assign(store, {\n registerSerializers(s: InstrumentSerializers) {\n serializers = s;\n },\n });\n}\n\ninterface HydrateResult {\n document: LabDocument;\n /** True when flushing would destroy the only copy of something: a document\n * newer than this code understands, or an unusable one whose quarantine\n * copy did not land. */\n persistDisabled: boolean;\n foldedFromLegacy: boolean;\n}\n\n/** Copy an unusable document aside, and report whether the store may go on\n * persisting. A failed quarantine write means the copy in storage is the only\n * one there is, so the store must not overwrite it. */\nfunction setAside(\n options: CreateLabStoreOptions,\n raw: string,\n why: string,\n error?: unknown,\n): boolean {\n const quarantined = quarantineDocument(options.storage, options.storageKey, raw);\n const message = quarantined\n ? `[labkit] ${why}; quarantined it and starting empty`\n : `[labkit] ${why} and could not be quarantined; leaving it in place and not persisting`;\n if (error === undefined) console.warn(message);\n else console.warn(message, error);\n return quarantined;\n}\n\nfunction hydrateDocument(options: CreateLabStoreOptions): HydrateResult {\n const fallback = emptyDocument(options.initialMode ?? 'auto');\n const raw = options.storage.read(labDocumentKey(options.storageKey));\n\n let parsed: Record<string, unknown> | null = null;\n if (raw !== null) {\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n const persistDisabled = !setAside(options, raw, 'lab document is unparseable');\n return { document: fallback, persistDisabled, foldedFromLegacy: false };\n }\n } else {\n parsed = readLegacyDocument(options.storage, options.storageKey, options.initialMode ?? 'auto');\n }\n\n if (parsed === null) {\n return { document: fallback, persistDisabled: false, foldedFromLegacy: false };\n }\n\n const foldedFromLegacy = raw === null;\n const outcome = runMigrations(parsed, MIGRATIONS, CURRENT_DOCUMENT_VERSION);\n\n if (!outcome.ok) {\n if (outcome.reason === 'future') {\n console.warn(\n '[labkit] lab document is from a newer version of labkit; starting empty and leaving it alone',\n );\n return { document: fallback, persistDisabled: true, foldedFromLegacy: false };\n }\n const stored = JSON.stringify(parsed);\n const persistDisabled = !setAside(\n options,\n stored,\n 'lab document failed to migrate',\n outcome.error,\n );\n return { document: fallback, persistDisabled, foldedFromLegacy: false };\n }\n\n return {\n document: normalizeDocument(outcome.doc, options.initialMode ?? 'auto'),\n persistDisabled: false,\n foldedFromLegacy,\n };\n}\n","import { useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport { LabStoreContext, TrialIdContext } from './context';\nimport type { TrialStateHandle } from './types';\n\n/** An instrument's own state and config, plus setters. Reads the surrounding\n * trial id, so an instrument never has to know which trial it is\n * running in. Throws outside a lab store and trial. */\nexport function useTrialState<TS = unknown, TC = unknown>(): TrialStateHandle<TS, TC> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useTrialState must be used inside <LabStoreProvider>');\n\n const trialId = useContext(TrialIdContext);\n if (!trialId) throw new Error('[labkit] useTrialState must be used inside <TrialIdProvider>');\n\n const record = useStore(ctx.store, (s) => s.trials.find((w) => w.id === trialId));\n\n if (!record) throw new Error(`[labkit] No trial found with id \"${trialId}\"`);\n\n const updateTrialState = useStore(ctx.store, (s) => s.updateTrialState);\n const updateTrialConfig = useStore(ctx.store, (s) => s.updateTrialConfig);\n\n return {\n state: record.state as TS,\n config: record.config as TC,\n setState: (next) => updateTrialState(trialId, next as Parameters<typeof updateTrialState>[1]),\n setConfig: (key, value) => updateTrialConfig(trialId, key as never, value as never),\n };\n}\n"]}