@weasel-js/labkit 1.3.0 → 1.4.0-pre.1

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 (211) hide show
  1. package/README.md +226 -55
  2. package/dist/_dts/CanvasStackContext-LnCfqNBA.d.ts +42 -0
  3. package/dist/_dts/DrawCommand-B3bskUsC.d.ts +564 -0
  4. package/dist/_dts/{PrefsForm-BYa6cWnO.d.ts → PrefsForm-BkUJZx0A.d.ts} +4 -1
  5. package/dist/_dts/fitViewToBounds-dZ2UDB6e.d.ts +21 -0
  6. package/dist/_dts/frac-C-2c72Ij.d.ts +735 -0
  7. package/dist/_dts/index-BDVzvRzQ.d.ts +236 -0
  8. package/dist/_dts/shapeKinds-Cx_rxwsa.d.ts +87 -0
  9. package/dist/_dts/{DrawCommand-DBB45NfN.d.ts → types-C-gh9Ap-.d.ts} +1 -582
  10. package/dist/_dts/{types-DCGEcMO_.d.ts → types-D6s4b7if.d.ts} +1 -1
  11. package/dist/_dts/{types-1Sdxy_Pv.d.ts → types-DYMaEvM5.d.ts} +26 -1
  12. package/dist/_dts/{useTrialState-CsGMjhu9.d.ts → useTrialState-gmMvZqPc.d.ts} +7 -2
  13. package/dist/canvas/index.d.ts +110 -26
  14. package/dist/canvas/index.js +5 -4
  15. package/dist/chrome/index.d.ts +9 -6
  16. package/dist/chrome/index.js +6 -6
  17. package/dist/chunk-2AYGEN57.js +32 -0
  18. package/dist/chunk-2AYGEN57.js.map +1 -0
  19. package/dist/chunk-6OEYTYML.js +669 -0
  20. package/dist/chunk-6OEYTYML.js.map +1 -0
  21. package/dist/{chunk-BMW4TDP5.js → chunk-AE5CNVRU.js} +3 -3
  22. package/dist/{chunk-BMW4TDP5.js.map → chunk-AE5CNVRU.js.map} +1 -1
  23. package/dist/{chunk-CPUJ3QXL.js → chunk-CQLQPQ4P.js} +2 -2
  24. package/dist/chunk-CQLQPQ4P.js.map +1 -0
  25. package/dist/chunk-CTRKTLYZ.js +33 -0
  26. package/dist/chunk-CTRKTLYZ.js.map +1 -0
  27. package/dist/chunk-E2UQVZ44.js +458 -0
  28. package/dist/chunk-E2UQVZ44.js.map +1 -0
  29. package/dist/{chunk-LGKRIUSW.js → chunk-ESIQNQ6K.js} +32 -7
  30. package/dist/chunk-ESIQNQ6K.js.map +1 -0
  31. package/dist/{chunk-JDROYQM3.js → chunk-HHGVISVZ.js} +130 -131
  32. package/dist/chunk-HHGVISVZ.js.map +1 -0
  33. package/dist/{chunk-BDJXIBRZ.js → chunk-HMFODCOX.js} +126 -29
  34. package/dist/chunk-HMFODCOX.js.map +1 -0
  35. package/dist/{chunk-QCICIHEO.js → chunk-L5QJOOLV.js} +839 -320
  36. package/dist/chunk-L5QJOOLV.js.map +1 -0
  37. package/dist/{chunk-3XXPU73K.js → chunk-M75ZU6ZZ.js} +9 -7
  38. package/dist/chunk-M75ZU6ZZ.js.map +1 -0
  39. package/dist/{chunk-QRYPXSGP.js → chunk-QB5SM4BM.js} +16 -8
  40. package/dist/chunk-QB5SM4BM.js.map +1 -0
  41. package/dist/chunk-RBGKL7NF.js +7128 -0
  42. package/dist/chunk-RBGKL7NF.js.map +1 -0
  43. package/dist/{chunk-WTI26YTM.js → chunk-TO2FUOKF.js} +40 -13
  44. package/dist/chunk-TO2FUOKF.js.map +1 -0
  45. package/dist/{chunk-4TMMVIDM.js → chunk-XJ6N32QP.js} +50 -6
  46. package/dist/chunk-XJ6N32QP.js.map +1 -0
  47. package/dist/controls/index.d.ts +4 -28
  48. package/dist/controls/index.js +3 -3
  49. package/dist/dragdrop/index.d.ts +9 -5
  50. package/dist/dragdrop/index.js +3 -3
  51. package/dist/index.d.ts +517 -234
  52. package/dist/index.js +1769 -143
  53. package/dist/index.js.map +1 -1
  54. package/dist/layers/index.d.ts +6 -5
  55. package/dist/layers/index.js +3 -3
  56. package/dist/loupe/index.d.ts +187 -0
  57. package/dist/loupe/index.js +7 -0
  58. package/dist/loupe/index.js.map +1 -0
  59. package/dist/passthrough/weasel-canvas.d.ts +3 -1
  60. package/dist/passthrough/weasel-canvas.js +1 -1
  61. package/dist/passthrough/weasel-ui.d.ts +138 -91
  62. package/dist/passthrough/weasel-ui.js +2 -2
  63. package/dist/primitives/index.d.ts +3 -1
  64. package/dist/primitives/index.js +5 -5
  65. package/dist/state/index.d.ts +3 -3
  66. package/dist/state/index.js +2 -2
  67. package/dist/styles.css +229 -1
  68. package/dist/surface/index.d.ts +18 -2
  69. package/dist/surface/index.js +2 -2
  70. package/dist/ui/layers/index.js +2 -2
  71. package/dist/undo/index.d.ts +5 -4
  72. package/package.json +14 -7
  73. package/src/annotations/AnnotationOverlay.tsx +205 -0
  74. package/src/annotations/AnnotationTargets.tsx +37 -0
  75. package/src/annotations/Annotations.less +129 -0
  76. package/src/annotations/Annotations.meaning.test.tsx +111 -0
  77. package/src/annotations/Annotations.overlay.test.tsx +142 -0
  78. package/src/annotations/AnnotationsContext.ts +49 -0
  79. package/src/annotations/ExportMenu.test.tsx +86 -0
  80. package/src/annotations/ExportMenu.tsx +182 -0
  81. package/src/annotations/MarkList.tsx +75 -0
  82. package/src/annotations/capture.test.ts +140 -0
  83. package/src/annotations/capture.ts +237 -0
  84. package/src/annotations/drawOne.test.ts +56 -0
  85. package/src/annotations/drawOne.ts +47 -0
  86. package/src/annotations/frac.test.ts +59 -0
  87. package/src/annotations/frac.ts +66 -0
  88. package/src/annotations/history.test.ts +82 -0
  89. package/src/annotations/history.ts +118 -0
  90. package/src/annotations/index.ts +37 -0
  91. package/src/annotations/paint.test.ts +125 -0
  92. package/src/annotations/paint.ts +130 -0
  93. package/src/annotations/staleness.test.ts +56 -0
  94. package/src/annotations/staleness.ts +41 -0
  95. package/src/annotations/store.test.ts +247 -0
  96. package/src/annotations/store.ts +347 -0
  97. package/src/annotations/svgNodes.test.ts +77 -0
  98. package/src/annotations/svgNodes.ts +74 -0
  99. package/src/annotations/toolMap.test.ts +36 -0
  100. package/src/annotations/toolMap.ts +54 -0
  101. package/src/annotations/types.ts +229 -0
  102. package/src/annotations/view.test.ts +37 -0
  103. package/src/annotations/view.ts +40 -0
  104. package/src/canvas/AGENTS.md +55 -5
  105. package/src/canvas/CanvasStack.test.tsx +27 -1
  106. package/src/canvas/CanvasStack.tsx +29 -4
  107. package/src/canvas/CanvasStackContext.ts +23 -4
  108. package/src/canvas/camera.test.ts +78 -0
  109. package/src/canvas/camera.ts +62 -0
  110. package/src/canvas/canvasCoords.test.ts +26 -0
  111. package/src/canvas/canvasCoords.ts +20 -7
  112. package/src/canvas/index.ts +16 -1
  113. package/src/canvas/useLayerScheduler.ts +7 -3
  114. package/src/canvas/usePanZoom.test.ts +36 -5
  115. package/src/canvas/usePanZoom.ts +20 -15
  116. package/src/canvas/worldSpec.test.ts +63 -0
  117. package/src/canvas/worldSpec.ts +48 -0
  118. package/src/chrome/builtins.test.ts +31 -1
  119. package/src/chrome/builtins.tsx +63 -19
  120. package/src/chrome/regions/PaletteRegion.test.tsx +22 -1
  121. package/src/chrome/regions/PaletteRegion.tsx +4 -0
  122. package/src/chrome/regions/SidebarRegion.tsx +37 -13
  123. package/src/chrome/regions/ToolbarRegion.tsx +2 -1
  124. package/src/chrome/regions/ViewportRegion.tsx +9 -1
  125. package/src/chrome/regions/regions.test.tsx +6 -1
  126. package/src/chrome/types.ts +20 -0
  127. package/src/controls/ControlPanel.test.tsx +38 -0
  128. package/src/controls/ControlPanel.tsx +50 -3
  129. package/src/dragdrop/DragDropRuntime.tsx +8 -2
  130. package/src/dragdrop/dragDrop.test.tsx +29 -2
  131. package/src/index.test.ts +23 -0
  132. package/src/index.ts +93 -4
  133. package/src/instrument/SineWave.smoke.test.tsx +1 -0
  134. package/src/instrument/types.ts +24 -1
  135. package/src/lab/Lab.surface.test.tsx +162 -0
  136. package/src/lab/Lab.tsx +102 -17
  137. package/src/lab/Lab.undock.test.tsx +85 -0
  138. package/src/lab/LabFullChrome.stories.tsx +20 -0
  139. package/src/lab/LabShell.less +11 -0
  140. package/src/lab/Workspace.less +73 -0
  141. package/src/lab/Workspace.surface.test.tsx +2 -0
  142. package/src/lab/Workspace.tsx +97 -8
  143. package/src/lab/index.ts +1 -1
  144. package/src/lab/panelHost.ts +37 -0
  145. package/src/loupe/AGENTS.md +49 -0
  146. package/src/loupe/CanvasLoupe.tsx +83 -0
  147. package/src/loupe/DomLoupe.tsx +62 -0
  148. package/src/loupe/Loupe.less +38 -0
  149. package/src/loupe/LoupeBubble.tsx +32 -0
  150. package/src/loupe/TrialLoupe.tsx +93 -0
  151. package/src/loupe/canvasLens.test.ts +205 -0
  152. package/src/loupe/canvasLens.ts +141 -0
  153. package/src/loupe/index.ts +19 -0
  154. package/src/loupe/types.test.ts +29 -0
  155. package/src/loupe/types.ts +90 -0
  156. package/src/loupe/useHostSize.ts +30 -0
  157. package/src/loupe/useLoupe.test.tsx +197 -0
  158. package/src/loupe/useLoupe.ts +189 -0
  159. package/src/passthrough/weasel-ui.ts +3 -0
  160. package/src/primitives/FloatingPanel.test.tsx +76 -1
  161. package/src/primitives/FloatingPanel.tsx +19 -3
  162. package/src/primitives/ScaleIndicator.test.tsx +7 -2
  163. package/src/primitives/Sidebar.less +18 -0
  164. package/src/primitives/Toolbar.less +8 -0
  165. package/src/primitives/Toolbar.tsx +5 -0
  166. package/src/primitives/useRovingTabIndex.test.ts +21 -0
  167. package/src/primitives/useRovingTabIndex.ts +26 -17
  168. package/src/state/document.test.ts +23 -0
  169. package/src/state/document.ts +14 -2
  170. package/src/state/index.ts +2 -0
  171. package/src/state/store.test.ts +24 -0
  172. package/src/state/store.ts +35 -1
  173. package/src/state/types.ts +11 -0
  174. package/src/state/undock.test.ts +40 -0
  175. package/src/state/undock.ts +39 -0
  176. package/src/styles.less +2 -0
  177. package/src/surface/SurfaceContext.ts +4 -0
  178. package/src/surface/index.ts +8 -3
  179. package/src/surface/useSurfaceTile.test.tsx +2 -0
  180. package/src/surface/useSurfaceTile.ts +7 -1
  181. package/src/surface/useTiledSurface.test.tsx +58 -0
  182. package/src/surface/useTiledSurface.ts +38 -3
  183. package/src/trial/Trial.annotations.persist.test.tsx +166 -0
  184. package/src/trial/Trial.annotations.test.tsx +121 -0
  185. package/src/trial/Trial.canvas.test.tsx +76 -1
  186. package/src/trial/Trial.less +19 -0
  187. package/src/trial/Trial.loupe.test.tsx +128 -0
  188. package/src/trial/Trial.stories.tsx +1 -0
  189. package/src/trial/Trial.test.tsx +12 -0
  190. package/src/trial/Trial.trialId.test.tsx +36 -0
  191. package/src/trial/Trial.tsx +215 -40
  192. package/src/trial/TrialChrome.tsx +35 -0
  193. package/src/trial/UndockedSections.tsx +65 -0
  194. package/src/trial/trialOps.test.ts +21 -0
  195. package/src/trial/trialOps.ts +5 -3
  196. package/dist/_dts/types-BttTIent.d.ts +0 -314
  197. package/dist/chunk-3XXPU73K.js.map +0 -1
  198. package/dist/chunk-4TMMVIDM.js.map +0 -1
  199. package/dist/chunk-6X5RBC6G.js +0 -366
  200. package/dist/chunk-6X5RBC6G.js.map +0 -1
  201. package/dist/chunk-BDJXIBRZ.js.map +0 -1
  202. package/dist/chunk-CPUJ3QXL.js.map +0 -1
  203. package/dist/chunk-JDROYQM3.js.map +0 -1
  204. package/dist/chunk-LGKRIUSW.js.map +0 -1
  205. package/dist/chunk-QCICIHEO.js.map +0 -1
  206. package/dist/chunk-QRYPXSGP.js.map +0 -1
  207. package/dist/chunk-RGT4EJIB.js +0 -6961
  208. package/dist/chunk-RGT4EJIB.js.map +0 -1
  209. package/dist/chunk-T66HU3HX.js +0 -17
  210. package/dist/chunk-T66HU3HX.js.map +0 -1
  211. package/dist/chunk-WTI26YTM.js.map +0 -1
package/README.md CHANGED
@@ -1,52 +1,185 @@
1
1
  # @weasel-js/labkit
2
2
 
3
- React widgets for building self-contained interactive **lab** pages — pages with sliders, controls, and canvas-based experimentation.
3
+ React components for building **labs**: pages where you change parameters and
4
+ watch something redraw. A lab hosts one or more **instruments** — self-contained
5
+ experiments that own their settings and their state and render a picture from
6
+ both. Each open instrument gets a **trial**, one tile of the lab's workspace,
7
+ and the trial supplies the apparatus around it: a control panel, a toolbar, a
8
+ sidebar, undo, snapshots, persistence and drawing tools.
4
9
 
5
- This is the v0.x of the library. The Lab/Trial/Instrument runtime arrives in later plans; v0.0.1 ships presentational primitives.
10
+ Reach for it when you have a visualization, a simulation or a rendering
11
+ algorithm and would rather not build the page around it.
6
12
 
7
- ## Installation
8
-
9
- Not published to npm — for now the package is intentionally local-install-only.
10
- It also depends on a sibling clone of [weasel](https://github.com/orochi235/weasel),
11
- so clone both side by side:
13
+ ## Install
12
14
 
13
15
  ```bash
14
- git clone https://github.com/orochi235/weasel.git
15
- git clone https://github.com/orochi235/labkit.git
16
- cd weasel && npm install && npm run build
17
- cd ../labkit && npm install && npm run build
16
+ npm i @weasel-js/labkit
18
17
  ```
19
18
 
20
- Then point your app at the local clone:
19
+ React 19 is a peer dependency (`react` and `react-dom`, `^19.0.0`). One
20
+ stylesheet import covers everything labkit draws, including the theme tokens and
21
+ the `@weasel-js/ui` components it passes through:
21
22
 
22
- ```json
23
- "dependencies": {
24
- "@weasel-js/labkit": "file:../labkit"
25
- }
23
+ ```ts
24
+ import '@weasel-js/labkit/styles.css';
26
25
  ```
27
26
 
28
- ## Usage
27
+ Annotations are on the `pre` tag until the next stable release:
28
+ `npm i @weasel-js/labkit@pre`.
29
+
30
+ ## A lab
29
31
 
30
32
  ```tsx
31
- import { LabShell, Toolbar, Workspace, FpsMeter } from '@weasel-js/labkit';
33
+ import { type ConfigOf, defineInstrument, f, Lab, localStorageAdapter } from '@weasel-js/labkit';
32
34
  import '@weasel-js/labkit/styles.css';
33
35
 
34
- function MyLab() {
36
+ const config = f.schema({
37
+ bins: f.number(20).range(5, 100).step(1).label('Bins'),
38
+ showAxes: f.boolean(true),
39
+ });
40
+
41
+ const Histogram = defineInstrument<{ samples: number[] }, ConfigOf<typeof config>>({
42
+ name: 'Histogram',
43
+ config,
44
+ initialState: () => ({ samples: sample(500) }),
45
+ render: () => null,
46
+ canvas: {
47
+ layers: [
48
+ { id: 'axes', draw: drawAxes },
49
+ { id: 'bars', draw: drawBars },
50
+ ],
51
+ },
52
+ layers: { ids: ['axes', 'bars'] },
53
+ });
54
+
55
+ export function App() {
35
56
  return (
36
- <LabShell title="My Lab" header={<button>+ Add</button>}>
37
- <Workspace>
38
- <div>Trial 1</div>
39
- <div>Trial 2</div>
40
- </Workspace>
41
- </LabShell>
57
+ <Lab
58
+ instruments={[Histogram]}
59
+ defaultInstrument="Histogram"
60
+ title="Histogram"
61
+ storage={localStorageAdapter}
62
+ storageKey="histogram-lab"
63
+ />
42
64
  );
43
65
  }
44
66
  ```
45
67
 
68
+ `f.schema` states the config once — values, types and controls — and the trial
69
+ renders the settings panel from it. `render` returns the instrument's own DOM;
70
+ returning `null` alongside `canvas` means the canvas layers are the whole
71
+ picture. `storage` keeps open trials and their state across reloads.
72
+
73
+ ## Capabilities
74
+
75
+ A capability is a field on the instrument. Declaring it is what makes the trial
76
+ provide the corresponding chrome; they compose freely.
77
+
78
+ | Field | What the trial then provides |
79
+ | --- | --- |
80
+ | `canvas` | A stack of layered `<canvas>` elements with pan and zoom, redrawing only the layers that changed |
81
+ | `layers` | A sidebar list that hides and reorders those layers |
82
+ | `annotations` | Drawing tools, an overlay on each region you name, a Marks panel, undo and export |
83
+ | `loupe` | A magnifier toggle that redraws the canvas through a zoomed camera, or calls your own `render` for DOM content |
84
+ | `dragDrop` | A palette to drag items from, and a drop pipeline that hands you world coordinates |
85
+ | `undo` | Undo and redo buttons, snapshotting state on the events you name |
86
+ | `job` | Starts async work, aborts it on unmount and on a key change, and renders progress and a cancel control |
87
+ | `tools` | A tool palette and a tool slot on the trial |
88
+ | `chrome` | Anything else, as contributions keyed to a region |
89
+
90
+ ## Annotations
91
+
92
+ An instrument names regions of itself that accept **marks** — freehand strokes,
93
+ lines, arrows, rectangles, ellipses and text drawn over its picture. labkit
94
+ supplies the tool palette, the overlay, the store, undo, persistence and export.
95
+
96
+ ```tsx
97
+ const paneRef = createRef<HTMLDivElement>();
98
+
99
+ defineInstrument<State, Config>({
100
+ // …
101
+ annotations: {
102
+ targets: () => [
103
+ { id: 'pane', ref: paneRef, content: { w: 260, h: 180 }, positionDependsOn: ['angle'] },
104
+ ],
105
+ meaning: {
106
+ statuses: [
107
+ { id: 'open', label: 'Open', color: '#e5484d' },
108
+ { id: 'fixed', label: 'Fixed', color: '#30a46c' },
109
+ ],
110
+ },
111
+ },
112
+ });
113
+ ```
114
+
115
+ A mark's position is stored as fractions of its target's `content` box, so it
116
+ stays on the same feature when the picture is rendered at a different size.
117
+ `positionDependsOn` names the config keys that move the picture; labkit
118
+ snapshots their values beside each mark, and a mark whose values have since
119
+ changed draws dashed and reports `isStale`. `meaning.statuses` is the optional
120
+ vocabulary a mark can be labelled with — a status carries its own color, which
121
+ the mark on the canvas follows.
122
+
123
+ Marks live in the trial's record and survive a reload. An instrument that would
124
+ rather keep them in a format it already owns declares `annotations.storage` with
125
+ a `load`/`save` pair, and labkit never writes its own slot.
126
+
127
+ `useAnnotations()` reaches the store from the instrument's `render` or from a
128
+ chrome contribution, and re-renders its caller as marks change:
129
+
130
+ ```tsx
131
+ const marks = useAnnotations();
132
+ const stale = marks.query({ target: 'pane' }).filter((m) => marks.isStale(m, config));
133
+ ```
134
+
135
+ `selection()` answers which marks the user has picked on the overlay, as
136
+ annotation ids, and `setSelection()` replaces them — the same selection
137
+ weasel's own click, marquee and handles drive, merged across every target.
138
+ A panel that opens a card for the clicked mark subscribes and re-reads it.
139
+
140
+ It also carries `get`, `hitTest`, `within`, `add`, `update`, `setMeta`,
141
+ `remove`, `undo`/`redo` and `capture`.
142
+
143
+ **Export.** A target declaring `base()` hands over the picture underneath its
144
+ marks — SVG markup, an image `src` or a canvas — and
145
+ `capture(target, { format, scale })` returns a Blob with the marks drawn on top,
146
+ as PNG or, from an SVG base, as vector SVG. Declaring `annotations` earns an
147
+ Export button in the toolbar; `onCapture` fires after every export, labkit's own
148
+ chrome included.
149
+
150
+ The pieces are exported for a host that wants to assemble them itself:
151
+ `createAnnotationStore`, `<AnnotationOverlay>`, `<AnnotationTargets>`,
152
+ `<MarkList>`, `<ExportMenu>`, `ANNOTATION_TOOLS` and `capturePlan`.
153
+
154
+ ## Chrome
155
+
156
+ A trial's chrome has six named regions — `titlebar`, `toolbar`, `palette`,
157
+ `sidebar`, `viewport`, `status`. A `TrialContribution` names one and supplies
158
+ data the region lays out, or a `render` function that opts out of that layout:
159
+
160
+ ```tsx
161
+ <Lab
162
+ chrome={[{ id: 'seed', region: 'status', item: { text: `seed ${seed}` } }]}
163
+ suppress={['fps']}
164
+ // …
165
+ />
166
+ ```
167
+
168
+ Contributions passed to `<Lab>` apply to every trial; an instrument's own
169
+ `chrome` field applies to its trials only. `suppress` drops a built-in by id and
170
+ throws on an id that is not there.
171
+
172
+ A sidebar section can be torn out of the trial into the workspace — as a tile
173
+ beside the trials, or as a floating panel — with the trial still rendering into
174
+ it. `SidebarSection.undockable` and `undockAs` control the offer;
175
+ `undockPanel` / `dockPanel` move one.
176
+
46
177
  ## Theming
47
178
 
48
179
  labkit ships one theme, `interstellar` — a cosmic dark and a warm parchment
49
- light — as a value, not a stylesheet:
180
+ light — as a value, not a stylesheet. `<Lab>` and `<LabShell>` apply it for you;
181
+ `mode` is `"auto"` (follow the OS), `"light"` or `"dark"`. Only `styles.css`
182
+ needs importing, and the token values arrive through the provider:
50
183
 
51
184
  ```tsx
52
185
  import { interstellarTheme } from '@weasel-js/labkit';
@@ -55,9 +188,31 @@ import { ThemeProvider } from '@weasel-js/theme/react';
55
188
  <ThemeProvider theme={interstellarTheme} mode="dark">…</ThemeProvider>
56
189
  ```
57
190
 
58
- `<Lab>` and `<LabShell>` do this for you; `mode` is `"auto"` (follow the OS),
59
- `"light"` or `"dark"`. Only `styles.css` needs importing — the token values
60
- arrive through the provider.
191
+ Style your own surfaces against the `--wzl-*` tokens so they follow the mode the
192
+ lab is in. Class names are `lk-*` and are not public API — every component takes
193
+ a `className`.
194
+
195
+ ## Without the runtime
196
+
197
+ The presentational pieces work on their own. `<LabShell>` is a titled page
198
+ frame; `<Workspace>` is a tile grid that can be resized and reordered and takes
199
+ any children:
200
+
201
+ ```tsx
202
+ import { LabShell, Workspace } from '@weasel-js/labkit';
203
+
204
+ <LabShell title="My Lab" header={<button>+ Add</button>}>
205
+ <Workspace ids={['a', 'b']} resizable>
206
+ <MyPane id="a" />
207
+ <MyPane id="b" />
208
+ </Workspace>
209
+ </LabShell>;
210
+ ```
211
+
212
+ `<Toolbar>`, `<Sidebar>`, `<StatusBar>`, `<FpsMeter>`, `<ScaleIndicator>`,
213
+ `<Legend>`, `<FloatingPanel>`, `<ZoomControl>` and the `@weasel-js/ui` property
214
+ rows re-exported from the root are all usable this way. `<Trial>` is not: it
215
+ reads the lab store and only mounts inside a `<Lab>`.
61
216
 
62
217
  ## Driving your own renderer
63
218
 
@@ -68,40 +223,56 @@ labkit and keep the GL yourself:
68
223
  import { toDeviceRect, useSurfaceTile, useTiledSurface } from '@weasel-js/labkit/surface';
69
224
  ```
70
225
 
71
- A trial's `view` is opaque to labkit — it is persisted, restored on Reset and
72
- handed to the instrument without being read into — so a 3D lab stores an orbit
73
- there and gets all three. `useOrbit` is the 3D peer of `usePanZoom`.
226
+ A trial's `view` is opaque to labkit — persisted, restored on Reset and handed
227
+ to the instrument without being read into — so a 3D lab stores an orbit there.
228
+ Both cameras ship independently of `CanvasStack`: `useOrbit` for a 3D view,
229
+ `usePanZoom` for a 2D one over a `ViewTransform`. See `src/surface/AGENTS.md`
230
+ for the contract and the traps.
74
231
 
75
- See `src/surface/AGENTS.md` for the contract and the traps.
232
+ ## Subpath exports
76
233
 
77
- ## Long-running work
234
+ The root barrel carries the common surface; a subpath reaches one slice
235
+ directly. Several expose more than the root does, `/state` most of all.
78
236
 
79
- An instrument with work too slow for a render declares a `job`. labkit starts it,
80
- aborts it on unmount and on a key change, discards results from a superseded run,
81
- and renders progress and a cancel control into the trial chrome. Per-item failure
82
- is an event rather than a thrown error, so a run with two failed items is a
83
- partial success.
84
-
85
- ```tsx
86
- import type { JobCapability } from '@weasel-js/labkit/job';
87
- ```
237
+ | Subpath | |
238
+ | --- | --- |
239
+ | `@weasel-js/labkit` | Everything |
240
+ | `/styles.css` | The one stylesheet |
241
+ | `/primitives` | Toolbar, Sidebar, StatusBar, Legend, FloatingPanel, meters |
242
+ | `/chrome` | Regions, contribution types, built-in contributions |
243
+ | `/controls` | `<ControlPanel>` and the config field types |
244
+ | `/canvas` | `<CanvasStack>`, coordinate helpers, `usePanZoom`, `useOrbit` |
245
+ | `/layers` | `<LayerList>` |
246
+ | `/loupe` | Magnifier components and `useLoupe` |
247
+ | `/surface` | Tiled surface hooks for your own renderer |
248
+ | `/job` | `useJob` and the job capability types |
249
+ | `/state` | The lab store, storage adapters, `useTrialState`, serialization helpers |
250
+ | `/undo` | Undo stack and event bus |
251
+ | `/dragdrop` | `<Palette>`, `<DragGhost>`, `useDragDrop` |
252
+ | `/ui/layers` | `<LayerStack>`, the expandable layer-card list |
253
+ | `/weasel-ui`, `/weasel-canvas` | Passthroughs to `@weasel-js/ui` and `@weasel-js/core` |
88
254
 
89
255
  ## Development
90
256
 
257
+ labkit is a workspace package in the [weasel](https://github.com/orochi235/weasel)
258
+ monorepo. From the repo root, `npm install` once, then from `packages/labkit`:
259
+
91
260
  ```bash
92
- npm install
93
- npm run dev # Vite dev server (examples/)
94
- npm run storybook # Storybook on :6006
95
- npm test # Vitest
96
- npm run lint # Biome + class-prefix check
97
- npm run build # Build dist/ for publish
261
+ npm run dev # Vite dev server on examples/minimal
262
+ npm run dev:annotate # …on examples/annotate-lab (and dev:drag, dev:weasel, dev:schema)
263
+ npm test # Vitest
264
+ npm run lint # Biome, plus the class-prefix and design-token checks
265
+ npm run build # dist/ for publish
266
+ npm run docs:dev # VitePress on docs/ (docs:build to render it)
98
267
  ```
99
268
 
269
+ Storybook is built at the repo root: `npm run storybook` from there covers every
270
+ package.
271
+
100
272
  ## Documentation
101
273
 
102
- - [Docs site](https://orochi235.github.io/labkit/)
103
- - [Recipes](https://orochi235.github.io/labkit/RECIPES) — composition patterns
104
- - [Agent guide](https://orochi235.github.io/labkit/AGENTS) — agent navigation guide
105
- - [Storybook](https://orochi235.github.io/labkit/storybook/)
106
- - [Speech balloon experiment](https://orochi235.github.io/labkit/storybook/?path=/story/ui-properties-propertypanel-speechballoonpanels--right-sidebar-tails) — sample implementation built on the property-panel widgets
107
- - [Design spec](https://github.com/orochi235/labkit/blob/main/docs/superpowers/specs/2026-04-26-labkit-design.md)
274
+ - [Docs site](https://orochi235.github.io/weasel/labkit/)
275
+ - [Recipes](https://orochi235.github.io/weasel/labkit/RECIPES) — composition patterns
276
+ - [Agent guide](https://orochi235.github.io/weasel/labkit/AGENTS) — a map of the source
277
+ - [Storybook](https://orochi235.github.io/weasel/docs/ui/storybook/)
278
+ - [Design spec](https://github.com/orochi235/weasel/blob/main/packages/labkit/docs/superpowers/specs/2026-04-26-labkit-design.md)
@@ -0,0 +1,42 @@
1
+ import * as react from 'react';
2
+ import { RefObject } from 'react';
3
+ import { V as ViewTransform, W as WorldFrame } from './frac-C-2c72Ij.js';
4
+
5
+ /** One layer of a canvas stack: its id, whether it is currently shown, and how
6
+ * it paints itself. */
7
+ interface CanvasLayerDescriptor {
8
+ id: string;
9
+ visible: boolean;
10
+ render: (ctx: CanvasRenderingContext2D, view: ViewTransform, frame: WorldFrame) => void;
11
+ }
12
+
13
+ /** The stack's own drawing surface, for an overlay that has to re-draw it at
14
+ * another camera or read back what it presented — a loupe. */
15
+ interface CanvasStackSurface {
16
+ /** The element the layers are stacked in, and the one pan/zoom listens on. */
17
+ element: RefObject<HTMLElement | null>;
18
+ /** Its measured CSS size, and the ratio the backing stores are scaled by. */
19
+ size: {
20
+ width: number;
21
+ height: number;
22
+ dpr: number;
23
+ };
24
+ /** The presented `<canvas>` per layer id. */
25
+ canvases: RefObject<Map<string, HTMLCanvasElement>>;
26
+ /** The layers as the stack is drawing them, bottom first. */
27
+ layers: readonly CanvasLayerDescriptor[];
28
+ }
29
+ /** What a canvas stack publishes to its children: the view, and the resolved
30
+ * coordinate system it is read in, so DOM overlays can place themselves in the
31
+ * same coordinates the layers draw in. */
32
+ interface CanvasStackContextValue {
33
+ view: ViewTransform;
34
+ frame: WorldFrame;
35
+ /** Absent from a context assembled by hand, which has no stack behind it. */
36
+ surface?: CanvasStackSurface;
37
+ }
38
+ /** Context carrying the surrounding canvas stack's view and world frame. */
39
+ declare const CanvasStackContext: react.Context<CanvasStackContextValue | null>;
40
+
41
+ export { CanvasStackContext as a };
42
+ export type { CanvasLayerDescriptor as C, CanvasStackContextValue as b, CanvasStackSurface as c };