@weasel-js/labkit 1.4.4 → 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 (219) hide show
  1. package/README.md +13 -2
  2. package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CqkxkFJW.d.ts} +1 -1
  3. package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +116 -0
  4. package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-8R6UmlvN.d.ts} +253 -31
  5. package/dist/_dts/index-B8uu2ba7.d.ts +237 -0
  6. package/dist/_dts/{index-Blv9uQzu.d.ts → index-retnAut7.d.ts} +18 -10
  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-MOM3GOVY.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-SMHP6XZ4.js → chunk-KJALCDWE.js} +9 -7
  23. package/dist/chunk-KJALCDWE.js.map +1 -0
  24. package/dist/{chunk-BKFVHKJH.js → chunk-MUKOW3TC.js} +3 -3
  25. package/dist/{chunk-BKFVHKJH.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-64ZCN3DA.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-U3IYHIAE.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 +110 -381
  53. package/dist/index.js +691 -817
  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 +164 -122
  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 +84 -50
  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/paint.test.ts +2 -2
  77. package/src/annotations/paint.ts +1 -1
  78. package/src/annotations/preload.ts +6 -0
  79. package/src/annotations/store.test.ts +18 -0
  80. package/src/annotations/store.ts +5 -4
  81. package/src/annotations/svgNodes.test.ts +2 -2
  82. package/src/annotations/toolMap.ts +3 -3
  83. package/src/annotations/types.ts +6 -3
  84. package/src/canvas/CameraWheelContext.ts +12 -0
  85. package/src/canvas/CanvasStack.test.tsx +25 -0
  86. package/src/canvas/CanvasStack.tsx +31 -4
  87. package/src/canvas/Stage.less +18 -0
  88. package/src/canvas/Stage.test.tsx +186 -0
  89. package/src/canvas/Stage.tsx +134 -0
  90. package/src/canvas/camera.test.ts +17 -2
  91. package/src/canvas/camera.ts +9 -9
  92. package/src/canvas/index.ts +4 -0
  93. package/src/canvas/usePanZoom.ts +10 -5
  94. package/src/chrome/ChromeRegions.stories.tsx +0 -3
  95. package/src/chrome/LabChrome.tsx +114 -0
  96. package/src/chrome/builtins.tsx +20 -31
  97. package/src/chrome/index.ts +15 -0
  98. package/src/chrome/labRegions.test.tsx +208 -0
  99. package/src/chrome/labTypes.ts +50 -0
  100. package/src/chrome/merge.ts +7 -9
  101. package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
  102. package/src/chrome/regions/PaletteRegion.tsx +21 -9
  103. package/src/chrome/regions/SidebarRegion.tsx +25 -13
  104. package/src/chrome/regions/StatusRegion.tsx +15 -8
  105. package/src/chrome/regions/ToolbarRegion.tsx +30 -16
  106. package/src/chrome/types.ts +47 -20
  107. package/src/config/builder.test.ts +4 -0
  108. package/src/config/builder.ts +16 -10
  109. package/src/config/declarationEmit.fixture.ts +11 -0
  110. package/src/config/declarationEmit.test.ts +51 -0
  111. package/src/config/entry.test.ts +46 -0
  112. package/src/config/index.ts +13 -1
  113. package/src/config/nodeClasses.test.ts +18 -0
  114. package/src/config/types.ts +3 -1
  115. package/src/controls/ControlPanel.test.tsx +16 -0
  116. package/src/controls/ControlPanel.tsx +5 -2
  117. package/src/fake-indexeddb-auto.d.ts +5 -0
  118. package/src/index.test.ts +7 -0
  119. package/src/index.ts +38 -5
  120. package/src/instrument/serializers.test.ts +22 -0
  121. package/src/instrument/serializers.ts +11 -0
  122. package/src/instrument/types.ts +25 -1
  123. package/src/lab/Lab.less +4 -13
  124. package/src/lab/Lab.persist.test.tsx +147 -0
  125. package/src/lab/Lab.stories.tsx +0 -2
  126. package/src/lab/Lab.surface.test.tsx +66 -3
  127. package/src/lab/Lab.test.tsx +91 -17
  128. package/src/lab/Lab.tsx +337 -124
  129. package/src/lab/LabFit.stories.less +18 -0
  130. package/src/lab/LabFit.stories.tsx +219 -0
  131. package/src/lab/LabFullChrome.stories.tsx +0 -3
  132. package/src/lab/LabHeader.test.tsx +40 -9
  133. package/src/lab/LabHeader.tsx +7 -13
  134. package/src/lab/LabPalette.tsx +13 -26
  135. package/src/lab/LabShell.less +49 -12
  136. package/src/lab/LabSwitcher.less +8 -1
  137. package/src/lab/Workspace.surface.test.tsx +2 -1
  138. package/src/lab/fitCheck.test.ts +57 -0
  139. package/src/lab/fitCheck.ts +125 -0
  140. package/src/loupe/AGENTS.md +1 -1
  141. package/src/loupe/Loupe.less +1 -1
  142. package/src/loupe/canvasLens.ts +1 -1
  143. package/src/loupe/types.ts +1 -1
  144. package/src/loupe/useLoupe.test.tsx +1 -1
  145. package/src/loupe/useLoupe.ts +1 -1
  146. package/src/passthrough/weasel-ui.ts +9 -0
  147. package/src/primitives/FloatingPanel.stories.tsx +13 -1
  148. package/src/primitives/FloatingPanel.test.tsx +47 -16
  149. package/src/primitives/FloatingPanel.tsx +11 -25
  150. package/src/primitives/Split.less +14 -0
  151. package/src/primitives/Split.test.tsx +108 -0
  152. package/src/primitives/Split.tsx +159 -0
  153. package/src/primitives/ZoomControl.tsx +5 -2
  154. package/src/primitives/index.ts +2 -0
  155. package/src/state/Persistence.stories.tsx +75 -0
  156. package/src/state/Persistence.tsx +33 -0
  157. package/src/state/SingletonExperiment.test.tsx +47 -53
  158. package/src/state/SingletonExperiment.tsx +24 -13
  159. package/src/state/adapterContract.ts +116 -0
  160. package/src/state/adapters.test.ts +113 -61
  161. package/src/state/adapters.ts +328 -69
  162. package/src/state/document.test.ts +71 -96
  163. package/src/state/document.ts +66 -52
  164. package/src/state/helpers.test.ts +1 -1
  165. package/src/state/index.ts +8 -0
  166. package/src/state/labRecords.test.ts +105 -0
  167. package/src/state/labRecords.ts +160 -0
  168. package/src/state/openLabStore.test.ts +658 -0
  169. package/src/state/openLabStore.ts +403 -0
  170. package/src/state/records.test.ts +234 -0
  171. package/src/state/records.ts +219 -0
  172. package/src/state/store.test.ts +104 -635
  173. package/src/state/store.ts +116 -157
  174. package/src/state/toolSlot.test.ts +1 -1
  175. package/src/state/types.ts +39 -15
  176. package/src/state/useOpenOnce.ts +68 -0
  177. package/src/state/usePersistedState.test.tsx +125 -0
  178. package/src/state/usePersistedState.ts +66 -0
  179. package/src/state/useTrialState.test.tsx +2 -3
  180. package/src/state/view.test.ts +49 -15
  181. package/src/state/view.ts +18 -1
  182. package/src/styles.less +2 -0
  183. package/src/surface/AGENTS.md +39 -7
  184. package/src/surface/SurfaceContext.ts +26 -3
  185. package/src/surface/index.ts +2 -0
  186. package/src/surface/useSurfaceTile.test.tsx +2 -1
  187. package/src/surface/useSurfaceTile.ts +6 -5
  188. package/src/surface/useTiledSurface.test.tsx +175 -0
  189. package/src/surface/useTiledSurface.ts +54 -5
  190. package/src/theme/Interstellar.stories.tsx +7 -7
  191. package/src/theme/interstellar.test.ts +1 -1
  192. package/src/theme/interstellar.tokens.json +2 -2
  193. package/src/tools/labTool.ts +11 -0
  194. package/src/trial/Trial.annotations.persist.test.tsx +100 -58
  195. package/src/trial/Trial.annotations.test.tsx +47 -4
  196. package/src/trial/Trial.job.test.tsx +3 -3
  197. package/src/trial/Trial.less +1 -26
  198. package/src/trial/Trial.stories.tsx +1 -2
  199. package/src/trial/Trial.test.tsx +28 -4
  200. package/src/trial/Trial.trialId.test.tsx +23 -1
  201. package/src/trial/Trial.tsx +118 -38
  202. package/src/trial/TrialBody.tsx +17 -141
  203. package/src/trial/TrialChrome.tsx +7 -4
  204. package/src/trial/trialOps.ts +5 -3
  205. package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
  206. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  207. package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
  208. package/dist/chunk-2P6PP5N4.js +0 -553
  209. package/dist/chunk-2P6PP5N4.js.map +0 -1
  210. package/dist/chunk-64ZCN3DA.js.map +0 -1
  211. package/dist/chunk-CTRKTLYZ.js.map +0 -1
  212. package/dist/chunk-FQMJUVHJ.js.map +0 -1
  213. package/dist/chunk-I6JCVE24.js.map +0 -1
  214. package/dist/chunk-MOM3GOVY.js.map +0 -1
  215. package/dist/chunk-SMHP6XZ4.js.map +0 -1
  216. package/dist/chunk-TJ7QY3OC.js.map +0 -1
  217. package/dist/chunk-U3IYHIAE.js.map +0 -1
  218. package/dist/chunk-W3ECWC2K.js +0 -7335
  219. package/dist/chunk-W3ECWC2K.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
+ }