@weasel-js/labkit 1.4.3 → 1.5.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 (229) hide show
  1. package/README.md +13 -2
  2. package/dist/_dts/{CanvasStackContext-Dde8_1my.d.ts → CanvasStackContext-CqkxkFJW.d.ts} +1 -1
  3. package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +116 -0
  4. package/dist/_dts/{frac-B5mB8LsJ.d.ts → frac-8R6UmlvN.d.ts} +256 -33
  5. package/dist/_dts/index-B8uu2ba7.d.ts +237 -0
  6. package/dist/_dts/{index-DelFfeV0.d.ts → index-retnAut7.d.ts} +45 -12
  7. package/dist/_dts/{types-B9_zrHmb.d.ts → types-Ca2LCnPq.d.ts} +65 -2
  8. package/dist/_dts/usePanZoom-nS798rOF.d.ts +160 -0
  9. package/dist/_dts/useTrialState-jcx_TdU6.d.ts +212 -0
  10. package/dist/canvas/index.d.ts +17 -128
  11. package/dist/canvas/index.js +4 -2
  12. package/dist/chrome/index.d.ts +4 -92
  13. package/dist/chrome/index.js +9 -5
  14. package/dist/chunk-67SJLMC7.js +704 -0
  15. package/dist/chunk-67SJLMC7.js.map +1 -0
  16. package/dist/chunk-6ZDGOZQV.js +310 -0
  17. package/dist/chunk-6ZDGOZQV.js.map +1 -0
  18. package/dist/{chunk-QAGAHJKZ.js → chunk-EKIICY6X.js} +111 -25
  19. package/dist/chunk-EKIICY6X.js.map +1 -0
  20. package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
  21. package/dist/chunk-H6ZAOWNE.js.map +1 -0
  22. package/dist/{chunk-2RQPGOQF.js → chunk-KJALCDWE.js} +9 -7
  23. package/dist/chunk-KJALCDWE.js.map +1 -0
  24. package/dist/{chunk-ISCOFAUO.js → chunk-MUKOW3TC.js} +3 -3
  25. package/dist/{chunk-ISCOFAUO.js.map → chunk-MUKOW3TC.js.map} +1 -1
  26. package/dist/chunk-NDRYLVOW.js +15 -0
  27. package/dist/chunk-NDRYLVOW.js.map +1 -0
  28. package/dist/{chunk-C42T6DJT.js → chunk-NS64DXMN.js} +226 -159
  29. package/dist/chunk-NS64DXMN.js.map +1 -0
  30. package/dist/{chunk-I6JCVE24.js → chunk-PGETSEDK.js} +135 -13
  31. package/dist/chunk-PGETSEDK.js.map +1 -0
  32. package/dist/{chunk-FQMJUVHJ.js → chunk-QLSV2N3G.js} +32 -37
  33. package/dist/chunk-QLSV2N3G.js.map +1 -0
  34. package/dist/chunk-RWNDDKOH.js +682 -0
  35. package/dist/chunk-RWNDDKOH.js.map +1 -0
  36. package/dist/chunk-S2SHZRD7.js +7681 -0
  37. package/dist/chunk-S2SHZRD7.js.map +1 -0
  38. package/dist/{chunk-ASWKLRKJ.js → chunk-TQBBYTAF.js} +19 -82
  39. package/dist/chunk-TQBBYTAF.js.map +1 -0
  40. package/dist/chunk-UU3NJO6N.js +72 -0
  41. package/dist/chunk-UU3NJO6N.js.map +1 -0
  42. package/dist/chunk-WIG6XHJ7.js +35 -0
  43. package/dist/chunk-WIG6XHJ7.js.map +1 -0
  44. package/dist/{chunk-TJ7QY3OC.js → chunk-ZPH5WTWF.js} +21 -15
  45. package/dist/chunk-ZPH5WTWF.js.map +1 -0
  46. package/dist/config/index.d.ts +232 -0
  47. package/dist/config/index.js +6 -0
  48. package/dist/config/index.js.map +1 -0
  49. package/dist/controls/index.d.ts +2 -3
  50. package/dist/controls/index.js +3 -2
  51. package/dist/dragdrop/index.d.ts +2 -4
  52. package/dist/index.d.ts +123 -348
  53. package/dist/index.js +794 -837
  54. package/dist/index.js.map +1 -1
  55. package/dist/layers/index.d.ts +3 -5
  56. package/dist/layers/index.js +2 -2
  57. package/dist/loupe/index.d.ts +6 -8
  58. package/dist/loupe/index.js +2 -2
  59. package/dist/passthrough/weasel-ui.d.ts +169 -117
  60. package/dist/passthrough/weasel-ui.js +1 -1
  61. package/dist/primitives/index.d.ts +53 -6
  62. package/dist/primitives/index.js +6 -4
  63. package/dist/state/index.d.ts +12 -7
  64. package/dist/state/index.js +16 -13
  65. package/dist/state/index.js.map +1 -1
  66. package/dist/styles.css +163 -62
  67. package/dist/surface/index.d.ts +44 -7
  68. package/dist/surface/index.js +2 -1
  69. package/dist/ui/layers/index.js +1 -1
  70. package/dist/undo/index.d.ts +2 -4
  71. package/package.json +15 -8
  72. package/src/annotations/AnnotationOverlay.tsx +53 -5
  73. package/src/annotations/Annotations.overlay.test.tsx +85 -6
  74. package/src/annotations/drawOne.test.ts +1 -1
  75. package/src/annotations/drawOne.ts +2 -2
  76. package/src/annotations/frac.test.ts +4 -4
  77. package/src/annotations/frac.ts +3 -2
  78. package/src/annotations/index.ts +1 -1
  79. package/src/annotations/paint.test.ts +2 -2
  80. package/src/annotations/paint.ts +1 -1
  81. package/src/annotations/preload.ts +6 -0
  82. package/src/annotations/store.test.ts +18 -0
  83. package/src/annotations/store.ts +7 -6
  84. package/src/annotations/svgNodes.test.ts +2 -2
  85. package/src/annotations/svgNodes.ts +1 -2
  86. package/src/annotations/toolMap.ts +3 -3
  87. package/src/annotations/types.ts +6 -3
  88. package/src/canvas/CameraWheelContext.ts +12 -0
  89. package/src/canvas/CanvasStack.test.tsx +25 -0
  90. package/src/canvas/CanvasStack.tsx +31 -4
  91. package/src/canvas/Stage.less +18 -0
  92. package/src/canvas/Stage.test.tsx +186 -0
  93. package/src/canvas/Stage.tsx +134 -0
  94. package/src/canvas/camera.test.ts +17 -2
  95. package/src/canvas/camera.ts +9 -9
  96. package/src/canvas/index.ts +4 -0
  97. package/src/canvas/usePanZoom.ts +10 -5
  98. package/src/chrome/ChromeRegions.stories.tsx +0 -3
  99. package/src/chrome/LabChrome.tsx +114 -0
  100. package/src/chrome/builtins.tsx +20 -31
  101. package/src/chrome/index.ts +15 -0
  102. package/src/chrome/labRegions.test.tsx +208 -0
  103. package/src/chrome/labTypes.ts +50 -0
  104. package/src/chrome/merge.ts +7 -9
  105. package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
  106. package/src/chrome/regions/PaletteRegion.tsx +21 -9
  107. package/src/chrome/regions/SidebarRegion.tsx +25 -13
  108. package/src/chrome/regions/StatusRegion.tsx +15 -8
  109. package/src/chrome/regions/ToolbarRegion.tsx +30 -16
  110. package/src/chrome/types.ts +47 -20
  111. package/src/config/builder.test.ts +4 -0
  112. package/src/config/builder.ts +16 -10
  113. package/src/config/declarationEmit.fixture.ts +11 -0
  114. package/src/config/declarationEmit.test.ts +51 -0
  115. package/src/config/entry.test.ts +46 -0
  116. package/src/config/index.ts +13 -1
  117. package/src/config/nodeClasses.test.ts +18 -0
  118. package/src/config/types.ts +3 -1
  119. package/src/controls/ControlPanel.test.tsx +16 -0
  120. package/src/controls/ControlPanel.tsx +5 -2
  121. package/src/fake-indexeddb-auto.d.ts +5 -0
  122. package/src/index.test.ts +7 -0
  123. package/src/index.ts +39 -6
  124. package/src/instrument/serializers.test.ts +22 -0
  125. package/src/instrument/serializers.ts +11 -0
  126. package/src/instrument/types.ts +25 -1
  127. package/src/lab/Lab.less +4 -13
  128. package/src/lab/Lab.persist.test.tsx +147 -0
  129. package/src/lab/Lab.stories.tsx +0 -2
  130. package/src/lab/Lab.surface.test.tsx +66 -3
  131. package/src/lab/Lab.test.tsx +91 -17
  132. package/src/lab/Lab.tsx +337 -124
  133. package/src/lab/LabFit.stories.less +18 -0
  134. package/src/lab/LabFit.stories.tsx +219 -0
  135. package/src/lab/LabFullChrome.stories.tsx +0 -3
  136. package/src/lab/LabHeader.test.tsx +40 -9
  137. package/src/lab/LabHeader.tsx +7 -13
  138. package/src/lab/LabPalette.tsx +13 -26
  139. package/src/lab/LabShell.less +49 -12
  140. package/src/lab/LabShell.tsx +14 -2
  141. package/src/lab/LabSwitcher.less +92 -0
  142. package/src/lab/LabSwitcher.test.tsx +96 -0
  143. package/src/lab/LabSwitcher.tsx +112 -0
  144. package/src/lab/Workspace.surface.test.tsx +2 -1
  145. package/src/lab/fitCheck.test.ts +57 -0
  146. package/src/lab/fitCheck.ts +125 -0
  147. package/src/lab/index.ts +2 -0
  148. package/src/loupe/AGENTS.md +1 -1
  149. package/src/loupe/Loupe.less +1 -1
  150. package/src/loupe/canvasLens.ts +1 -1
  151. package/src/loupe/types.ts +1 -1
  152. package/src/loupe/useLoupe.test.tsx +1 -1
  153. package/src/loupe/useLoupe.ts +1 -1
  154. package/src/passthrough/weasel-ui.ts +9 -0
  155. package/src/primitives/FloatingPanel.stories.tsx +13 -1
  156. package/src/primitives/FloatingPanel.test.tsx +47 -16
  157. package/src/primitives/FloatingPanel.tsx +11 -25
  158. package/src/primitives/Split.less +14 -0
  159. package/src/primitives/Split.test.tsx +108 -0
  160. package/src/primitives/Split.tsx +159 -0
  161. package/src/primitives/ZoomControl.tsx +5 -2
  162. package/src/primitives/index.ts +2 -0
  163. package/src/state/Persistence.stories.tsx +75 -0
  164. package/src/state/Persistence.tsx +33 -0
  165. package/src/state/SingletonExperiment.test.tsx +47 -53
  166. package/src/state/SingletonExperiment.tsx +24 -13
  167. package/src/state/adapterContract.ts +116 -0
  168. package/src/state/adapters.test.ts +113 -61
  169. package/src/state/adapters.ts +328 -69
  170. package/src/state/document.test.ts +71 -96
  171. package/src/state/document.ts +66 -52
  172. package/src/state/helpers.test.ts +20 -0
  173. package/src/state/helpers.ts +16 -0
  174. package/src/state/index.ts +8 -0
  175. package/src/state/labRecords.test.ts +105 -0
  176. package/src/state/labRecords.ts +160 -0
  177. package/src/state/openLabStore.test.ts +658 -0
  178. package/src/state/openLabStore.ts +403 -0
  179. package/src/state/records.test.ts +234 -0
  180. package/src/state/records.ts +219 -0
  181. package/src/state/store.test.ts +104 -635
  182. package/src/state/store.ts +117 -158
  183. package/src/state/toolSlot.test.ts +1 -1
  184. package/src/state/types.ts +39 -15
  185. package/src/state/useOpenOnce.ts +68 -0
  186. package/src/state/usePersistedState.test.tsx +125 -0
  187. package/src/state/usePersistedState.ts +66 -0
  188. package/src/state/useTrialState.test.tsx +2 -3
  189. package/src/state/view.test.ts +49 -15
  190. package/src/state/view.ts +18 -1
  191. package/src/styles.less +3 -0
  192. package/src/surface/AGENTS.md +39 -7
  193. package/src/surface/SurfaceContext.ts +26 -3
  194. package/src/surface/index.ts +2 -0
  195. package/src/surface/useSurfaceTile.test.tsx +2 -1
  196. package/src/surface/useSurfaceTile.ts +6 -5
  197. package/src/surface/useTiledSurface.test.tsx +175 -0
  198. package/src/surface/useTiledSurface.ts +54 -5
  199. package/src/theme/Interstellar.stories.tsx +7 -7
  200. package/src/theme/base.less +4 -4
  201. package/src/theme/interstellar.test.ts +1 -1
  202. package/src/theme/interstellar.tokens.json +2 -2
  203. package/src/tools/labTool.ts +11 -0
  204. package/src/trial/Trial.annotations.persist.test.tsx +100 -58
  205. package/src/trial/Trial.annotations.test.tsx +47 -4
  206. package/src/trial/Trial.job.test.tsx +3 -3
  207. package/src/trial/Trial.less +1 -26
  208. package/src/trial/Trial.stories.tsx +1 -2
  209. package/src/trial/Trial.test.tsx +28 -4
  210. package/src/trial/Trial.trialId.test.tsx +23 -1
  211. package/src/trial/Trial.tsx +118 -38
  212. package/src/trial/TrialBody.tsx +17 -141
  213. package/src/trial/TrialChrome.tsx +7 -4
  214. package/src/trial/trialOps.ts +7 -4
  215. package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
  216. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  217. package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
  218. package/dist/chunk-2RQPGOQF.js.map +0 -1
  219. package/dist/chunk-5TROQVQ2.js +0 -547
  220. package/dist/chunk-5TROQVQ2.js.map +0 -1
  221. package/dist/chunk-ASWKLRKJ.js.map +0 -1
  222. package/dist/chunk-C42T6DJT.js.map +0 -1
  223. package/dist/chunk-CTRKTLYZ.js.map +0 -1
  224. package/dist/chunk-FQMJUVHJ.js.map +0 -1
  225. package/dist/chunk-I6JCVE24.js.map +0 -1
  226. package/dist/chunk-MHAGC6VD.js +0 -7320
  227. package/dist/chunk-MHAGC6VD.js.map +0 -1
  228. package/dist/chunk-QAGAHJKZ.js.map +0 -1
  229. package/dist/chunk-TJ7QY3OC.js.map +0 -1
@@ -0,0 +1,219 @@
1
+ import type { StorageAdapter, StorageChange } from './types';
2
+
3
+ /** One record changing, as a cache listener hears it. `value` is `undefined`
4
+ * for a delete. `local` changes were made through this cache; `remote` ones
5
+ * came from another writer. */
6
+ export interface RecordChange {
7
+ name: string;
8
+ value: unknown;
9
+ origin: 'local' | 'remote';
10
+ }
11
+
12
+ /**
13
+ * Every record under one prefix, held in memory: the only thing that talks to
14
+ * a `StorageAdapter`. Names are keys with the prefix removed.
15
+ *
16
+ * Writes land in memory at once and reach storage on a debounce. The newest
17
+ * write to a record wins: another writer's change to a record with a write
18
+ * still queued here is ignored, since the queued write lands after it.
19
+ */
20
+ export interface RecordCache {
21
+ readonly prefix: string;
22
+ /** False once writing is off — the records could not be read, or were
23
+ * written by a newer labkit — so nothing here can overwrite them. */
24
+ readonly writable: boolean;
25
+ has(name: string): boolean;
26
+ get(name: string): unknown;
27
+ /** Every record whose name starts with `namePrefix`. */
28
+ entries(namePrefix?: string): [string, unknown][];
29
+ set(name: string, value: unknown): void;
30
+ delete(name: string): void;
31
+ subscribe(listener: (changes: RecordChange[]) => void): () => void;
32
+ /** Send queued writes now. */
33
+ flush(): Promise<void>;
34
+ /** Send queued writes and stop hearing other writers. */
35
+ close(): Promise<void>;
36
+ }
37
+
38
+ /** The parts of a cache only its opener uses. */
39
+ export interface OwnedRecordCache extends RecordCache {
40
+ /** Hold these records without writing them back. */
41
+ seed(entries: Iterable<[string, unknown]>): void;
42
+ stopWriting(): void;
43
+ }
44
+
45
+ /** Options for `createRecordCache` and `openRecords`. */
46
+ export interface RecordCacheOptions {
47
+ storage: StorageAdapter;
48
+ prefix: string;
49
+ /** Trailing debounce on writes. Default 300 ms. */
50
+ debounceMs?: number;
51
+ /** The longest a write waits under continuous change. Default 1000 ms. */
52
+ maxWaitMs?: number;
53
+ }
54
+
55
+ const DELETED = Symbol('deleted');
56
+
57
+ /** A cache holding `initial`, with nothing read from storage. */
58
+ export function createRecordCache(
59
+ options: RecordCacheOptions & { initial?: Iterable<[string, unknown]>; writable?: boolean },
60
+ ): OwnedRecordCache {
61
+ const { storage, prefix } = options;
62
+ const debounceMs = options.debounceMs ?? 300;
63
+ const maxWaitMs = options.maxWaitMs ?? 1000;
64
+ const values = new Map<string, unknown>(options.initial ?? []);
65
+ const queued = new Map<string, unknown>();
66
+ const listeners = new Set<(changes: RecordChange[]) => void>();
67
+ let writable = options.writable ?? true;
68
+ let closed = false;
69
+ let timer: ReturnType<typeof setTimeout> | null = null;
70
+ let firstQueuedAt: number | null = null;
71
+
72
+ const emit = (changes: RecordChange[]): void => {
73
+ for (const listener of [...listeners]) listener(changes);
74
+ };
75
+
76
+ const schedule = (): void => {
77
+ if (!writable || closed) return;
78
+ const now = Date.now();
79
+ firstQueuedAt ??= now;
80
+ if (timer) clearTimeout(timer);
81
+ const wait = Math.max(0, Math.min(debounceMs, firstQueuedAt + maxWaitMs - now));
82
+ timer = setTimeout(() => {
83
+ timer = null;
84
+ void flush();
85
+ }, wait);
86
+ };
87
+
88
+ async function flush(): Promise<void> {
89
+ if (timer) clearTimeout(timer);
90
+ timer = null;
91
+ firstQueuedAt = null;
92
+ if (!writable || queued.size === 0) return;
93
+ const batch = [...queued];
94
+ queued.clear();
95
+ await Promise.all(
96
+ batch.map(async ([name, value]) => {
97
+ const key = prefix + name;
98
+ try {
99
+ if (value === DELETED) await storage.delete(key);
100
+ else await storage.set(key, value);
101
+ } catch (error) {
102
+ console.warn(`[labkit] could not write "${key}"; keeping it in memory`, error);
103
+ }
104
+ }),
105
+ );
106
+ }
107
+
108
+ const applyRemote = (changes: StorageChange[]): void => {
109
+ const applied: RecordChange[] = [];
110
+ for (const [key, value] of changes) {
111
+ if (!key.startsWith(prefix)) continue;
112
+ const name = key.slice(prefix.length);
113
+ if (queued.has(name)) continue;
114
+ if (value === undefined) {
115
+ if (!values.has(name)) continue;
116
+ values.delete(name);
117
+ } else {
118
+ values.set(name, value);
119
+ }
120
+ applied.push({ name, value, origin: 'remote' });
121
+ }
122
+ if (applied.length > 0) emit(applied);
123
+ };
124
+
125
+ const unsubscribe = writable ? storage.subscribe?.(prefix, applyRemote) : undefined;
126
+
127
+ const onPageHide = (): void => {
128
+ void flush();
129
+ };
130
+ const onVisibility = (): void => {
131
+ if (document.visibilityState === 'hidden') void flush();
132
+ };
133
+ if (typeof window !== 'undefined') {
134
+ window.addEventListener('pagehide', onPageHide);
135
+ document.addEventListener('visibilitychange', onVisibility);
136
+ }
137
+
138
+ const cache: OwnedRecordCache & { applyRemote: typeof applyRemote } = {
139
+ prefix,
140
+ get writable() {
141
+ return writable;
142
+ },
143
+ has: (name) => values.has(name),
144
+ get: (name) => values.get(name),
145
+ entries: (namePrefix = '') => [...values].filter(([name]) => name.startsWith(namePrefix)),
146
+ set: (name, value) => {
147
+ values.set(name, value);
148
+ queued.set(name, value);
149
+ emit([{ name, value, origin: 'local' }]);
150
+ schedule();
151
+ },
152
+ delete: (name) => {
153
+ if (!values.has(name) && !queued.has(name)) return;
154
+ values.delete(name);
155
+ queued.set(name, DELETED);
156
+ emit([{ name, value: undefined, origin: 'local' }]);
157
+ schedule();
158
+ },
159
+ subscribe: (listener) => {
160
+ listeners.add(listener);
161
+ return () => {
162
+ listeners.delete(listener);
163
+ };
164
+ },
165
+ flush,
166
+ close: async () => {
167
+ if (closed) return;
168
+ await flush();
169
+ closed = true;
170
+ unsubscribe?.();
171
+ if (typeof window !== 'undefined') {
172
+ window.removeEventListener('pagehide', onPageHide);
173
+ document.removeEventListener('visibilitychange', onVisibility);
174
+ }
175
+ },
176
+ seed: (entries) => {
177
+ for (const [name, value] of entries) values.set(name, value);
178
+ },
179
+ stopWriting: () => {
180
+ writable = false;
181
+ queued.clear();
182
+ if (timer) clearTimeout(timer);
183
+ timer = null;
184
+ unsubscribe?.();
185
+ },
186
+ applyRemote,
187
+ };
188
+ return cache;
189
+ }
190
+
191
+ /** Read every record under `prefix` and hold them. A failed read opens the
192
+ * cache empty with writing off, so it never overwrites what it could not
193
+ * read. */
194
+ export async function openRecords(options: RecordCacheOptions): Promise<OwnedRecordCache> {
195
+ // Listen before listing, so a change landing between the two is not lost.
196
+ const early: StorageChange[] = [];
197
+ const stopEarly = options.storage.subscribe?.(options.prefix, (c) => early.push(...c));
198
+ let listed: [string, unknown][];
199
+ try {
200
+ listed = await options.storage.list(options.prefix);
201
+ } catch (error) {
202
+ stopEarly?.();
203
+ console.warn(
204
+ `[labkit] could not read "${options.prefix}"; opening empty and not persisting`,
205
+ error,
206
+ );
207
+ return createRecordCache({ ...options, writable: false });
208
+ }
209
+ const cache = createRecordCache({
210
+ ...options,
211
+ initial: listed.map(([key, value]): [string, unknown] => [
212
+ key.slice(options.prefix.length),
213
+ value,
214
+ ]),
215
+ });
216
+ stopEarly?.();
217
+ (cache as unknown as { applyRemote: (c: StorageChange[]) => void }).applyRemote(early);
218
+ return cache;
219
+ }