@weasel-js/labkit 1.7.3 → 1.8.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 (197) hide show
  1. package/README.md +24 -0
  2. package/dist/_dts/{CanvasStackContext-Cjy6I-Iv.d.ts → CanvasStackContext-B05ZM04l.d.ts} +3 -1
  3. package/dist/_dts/{Stage-D6t7o0pF.d.ts → Stage-CIJlbjrE.d.ts} +5 -3
  4. package/dist/_dts/{index-DCMA4Fy1.d.ts → index-8geQNQet.d.ts} +84 -12
  5. package/dist/_dts/{index-BTmaRy6a.d.ts → index-DRi3V5sN.d.ts} +6 -4
  6. package/dist/_dts/{types-BUBSIZuV.d.ts → types-DUfQ0isU.d.ts} +5 -1
  7. package/dist/_dts/{useTrialState-Bh8bzFgE.d.ts → useTrialState-Ar0AFpCe.d.ts} +94 -87
  8. package/dist/canvas/index.d.ts +5 -5
  9. package/dist/canvas/index.js +2 -2
  10. package/dist/chrome/index.d.ts +3 -3
  11. package/dist/chrome/index.js +7 -7
  12. package/dist/{chunk-HBGH344L.js → chunk-4RCVWX4M.js} +55 -18
  13. package/dist/chunk-4RCVWX4M.js.map +1 -0
  14. package/dist/{chunk-WIG6XHJ7.js → chunk-6IIKLXBZ.js} +13 -4
  15. package/dist/chunk-6IIKLXBZ.js.map +1 -0
  16. package/dist/{chunk-F4ACIK7Z.js → chunk-6Q6ACJ3H.js} +3 -3
  17. package/dist/{chunk-F4ACIK7Z.js.map → chunk-6Q6ACJ3H.js.map} +1 -1
  18. package/dist/{chunk-PMLZP7RT.js → chunk-7ALJOVGP.js} +5 -9
  19. package/dist/chunk-7ALJOVGP.js.map +1 -0
  20. package/dist/{chunk-XE5HJOPL.js → chunk-7FMZT2WL.js} +3 -3
  21. package/dist/{chunk-XE5HJOPL.js.map → chunk-7FMZT2WL.js.map} +1 -1
  22. package/dist/{chunk-XVUKDSYV.js → chunk-AVOILDN2.js} +2 -2
  23. package/dist/{chunk-XVUKDSYV.js.map → chunk-AVOILDN2.js.map} +1 -1
  24. package/dist/{chunk-2N7UBSS2.js → chunk-J5MDJEOZ.js} +3 -3
  25. package/dist/{chunk-2N7UBSS2.js.map → chunk-J5MDJEOZ.js.map} +1 -1
  26. package/dist/{chunk-LPGBC63T.js → chunk-KHB3KRRQ.js} +7 -7
  27. package/dist/chunk-KHB3KRRQ.js.map +1 -0
  28. package/dist/{chunk-IBVRMHW5.js → chunk-NHIO2O3O.js} +31 -30
  29. package/dist/chunk-NHIO2O3O.js.map +1 -0
  30. package/dist/{chunk-EESJUSYR.js → chunk-QMAY74TP.js} +56 -5
  31. package/dist/chunk-QMAY74TP.js.map +1 -0
  32. package/dist/{chunk-4X44DMRS.js → chunk-RN7T7OBO.js} +46 -24
  33. package/dist/chunk-RN7T7OBO.js.map +1 -0
  34. package/dist/{chunk-4TJ34FXA.js → chunk-RXO5UEGT.js} +5 -12
  35. package/dist/chunk-RXO5UEGT.js.map +1 -0
  36. package/dist/{chunk-E6NARGU7.js → chunk-SQX35X5V.js} +3 -3
  37. package/dist/{chunk-E6NARGU7.js.map → chunk-SQX35X5V.js.map} +1 -1
  38. package/dist/{chunk-YCFKW5RX.js → chunk-T5OEUNCB.js} +16 -2
  39. package/dist/chunk-T5OEUNCB.js.map +1 -0
  40. package/dist/{chunk-RL2LOLNI.js → chunk-UX66USTN.js} +2 -2
  41. package/dist/chunk-UX66USTN.js.map +1 -0
  42. package/dist/{chunk-IRMPZIYU.js → chunk-V4SMGEYY.js} +6732 -6507
  43. package/dist/chunk-V4SMGEYY.js.map +1 -0
  44. package/dist/{chunk-E23Q6A3Y.js → chunk-VCK5T6EP.js} +3 -3
  45. package/dist/{chunk-E23Q6A3Y.js.map → chunk-VCK5T6EP.js.map} +1 -1
  46. package/dist/{chunk-T6DLGEJB.js → chunk-XWR2P6MR.js} +23 -6
  47. package/dist/chunk-XWR2P6MR.js.map +1 -0
  48. package/dist/{chunk-XSJJTXNA.js → chunk-YZA73U4A.js} +19 -19
  49. package/dist/chunk-YZA73U4A.js.map +1 -0
  50. package/dist/config/index.d.ts +12 -5
  51. package/dist/config/index.js +2 -2
  52. package/dist/controls/index.d.ts +2 -2
  53. package/dist/controls/index.js +5 -5
  54. package/dist/dragdrop/index.d.ts +2 -2
  55. package/dist/dragdrop/index.js +2 -2
  56. package/dist/index.d.ts +158 -16
  57. package/dist/index.js +1036 -415
  58. package/dist/index.js.map +1 -1
  59. package/dist/layers/index.d.ts +2 -2
  60. package/dist/layers/index.js +3 -3
  61. package/dist/loupe/index.d.ts +176 -28
  62. package/dist/loupe/index.js +547 -141
  63. package/dist/loupe/index.js.map +1 -1
  64. package/dist/overview/index.d.ts +1 -1
  65. package/dist/overview/index.js +5 -5
  66. package/dist/passthrough/weasel-ui.js +3 -3
  67. package/dist/primitives/index.d.ts +4 -19
  68. package/dist/primitives/index.js +7 -7
  69. package/dist/state/index.d.ts +6 -8
  70. package/dist/state/index.js +5 -5
  71. package/dist/styles.css +181 -44
  72. package/dist/surface/index.d.ts +20 -1
  73. package/dist/surface/index.js +2 -2
  74. package/dist/undo/index.d.ts +19 -8
  75. package/dist/undo/index.js +1 -1
  76. package/package.json +11 -11
  77. package/src/annotations/AnnotationOverlay.abandon.test.tsx +4 -0
  78. package/src/annotations/AnnotationOverlay.tsx +10 -1
  79. package/src/annotations/Annotations.less +2 -0
  80. package/src/annotations/Annotations.overlay.test.tsx +29 -0
  81. package/src/canvas/CanvasStack.test.tsx +35 -0
  82. package/src/canvas/CanvasStack.tsx +12 -1
  83. package/src/canvas/useLayerScheduler.ts +14 -0
  84. package/src/chrome/builtins.test.ts +14 -0
  85. package/src/chrome/builtins.tsx +17 -2
  86. package/src/chrome/regions/StatusRegion.tsx +20 -14
  87. package/src/chrome/regions/regions.test.tsx +28 -0
  88. package/src/chrome/types.ts +8 -0
  89. package/src/clock/clock.browser.test.less +5 -0
  90. package/src/clock/clock.browser.test.tsx +77 -0
  91. package/src/clock/clock.integration.test.tsx +95 -0
  92. package/src/clock/clockRegistry.test.ts +33 -0
  93. package/src/clock/clockRegistry.ts +46 -0
  94. package/src/clock/hooks.ts +44 -0
  95. package/src/clock/index.ts +2 -0
  96. package/src/clock/trialClock.test.ts +265 -0
  97. package/src/clock/trialClock.ts +245 -0
  98. package/src/clock/useClockLoop.ts +45 -0
  99. package/src/config/builder.ts +11 -1
  100. package/src/config/resolve.test.ts +12 -0
  101. package/src/config/resolve.ts +5 -0
  102. package/src/config/types.ts +5 -1
  103. package/src/controls/ControlMatrix.test.tsx +10 -0
  104. package/src/controls/ControlMatrix.tsx +110 -58
  105. package/src/controls/ControlPanel.test.tsx +30 -0
  106. package/src/index.ts +3 -2
  107. package/src/instrument/types.ts +21 -1
  108. package/src/lab/Lab.chrome.test.tsx +8 -1
  109. package/src/lab/Lab.presentation.test.tsx +113 -0
  110. package/src/lab/Lab.surface.test.tsx +20 -9
  111. package/src/lab/Lab.tsx +132 -257
  112. package/src/lab/LabLightbox.browser.test.less +17 -0
  113. package/src/lab/LabLightbox.browser.test.tsx +216 -0
  114. package/src/lab/LabShell.less +10 -3
  115. package/src/lab/LabSurface.tsx +129 -0
  116. package/src/lab/Workspace.stories.tsx +18 -0
  117. package/src/lab/Workspace.surface.test.tsx +4 -0
  118. package/src/lab/Workspace.test.tsx +37 -1
  119. package/src/lab/Workspace.tsx +34 -6
  120. package/src/lab/index.ts +2 -0
  121. package/src/lab/nebula.ts +30 -0
  122. package/src/lab/openLab.test.ts +81 -0
  123. package/src/lab/openLab.ts +119 -0
  124. package/src/lab/presentation.browser.test.less +5 -0
  125. package/src/lab/presentation.browser.test.tsx +119 -0
  126. package/src/lab/presentation.less +64 -0
  127. package/src/lab/presentation.tsx +47 -0
  128. package/src/lab/useFocusPick.ts +43 -0
  129. package/src/lightbox/Lightbox.browser.test.less +14 -0
  130. package/src/lightbox/Lightbox.browser.test.tsx +113 -0
  131. package/src/lightbox/Lightbox.less +128 -0
  132. package/src/lightbox/Lightbox.test.tsx +195 -0
  133. package/src/lightbox/Lightbox.tsx +205 -0
  134. package/src/lightbox/LightboxContext.ts +38 -0
  135. package/src/lightbox/LightboxLayers.tsx +40 -0
  136. package/src/lightbox/index.ts +8 -0
  137. package/src/lightbox/modal.ts +94 -0
  138. package/src/loupe/AGENTS.md +40 -2
  139. package/src/loupe/CanvasLoupe.tsx +5 -4
  140. package/src/loupe/DomLoupe.tsx +7 -5
  141. package/src/loupe/Loupe.less +15 -0
  142. package/src/loupe/LoupeBubble.tsx +35 -13
  143. package/src/loupe/SourceLoupe.browser.test.tsx +204 -0
  144. package/src/loupe/SourceLoupe.tsx +60 -0
  145. package/src/loupe/TrialLoupe.browser.test.tsx +342 -0
  146. package/src/loupe/TrialLoupe.tsx +126 -28
  147. package/src/loupe/canvasLens.ts +20 -17
  148. package/src/loupe/index.ts +26 -2
  149. package/src/loupe/sourceLens.test.ts +65 -0
  150. package/src/loupe/sourceLens.ts +97 -0
  151. package/src/loupe/types.test.ts +6 -0
  152. package/src/loupe/types.ts +109 -7
  153. package/src/loupe/useLoupe.test.tsx +188 -3
  154. package/src/loupe/useLoupe.ts +106 -11
  155. package/src/primitives/index.ts +2 -2
  156. package/src/specimen/Specimen.tsx +0 -7
  157. package/src/state/helpers.test.ts +4 -11
  158. package/src/state/helpers.ts +2 -8
  159. package/src/state/index.ts +0 -2
  160. package/src/state/openLabStore.test.ts +10 -8
  161. package/src/state/openLabStore.ts +1 -1
  162. package/src/state/store.test.ts +2 -2
  163. package/src/state/store.ts +15 -16
  164. package/src/state/types.ts +9 -9
  165. package/src/styles.less +2 -1
  166. package/src/surface/AGENTS.md +13 -0
  167. package/src/surface/index.ts +1 -0
  168. package/src/surface/useSurfaceTile.test.tsx +4 -0
  169. package/src/surface/useSurfaceTile.ts +17 -1
  170. package/src/surface/useTiledSurface.test.tsx +65 -0
  171. package/src/surface/useTiledSurface.ts +68 -1
  172. package/src/theme/interstellar.theme.json +0 -14
  173. package/src/trial/Trial.stories.tsx +0 -1
  174. package/src/trial/Trial.test.tsx +40 -3
  175. package/src/trial/Trial.tsx +87 -43
  176. package/src/trial/TrialChrome.tsx +20 -2
  177. package/src/trial/trialOps.test.ts +1 -2
  178. package/src/trial/trialOps.ts +2 -3
  179. package/src/undo/stateOp.ts +21 -0
  180. package/src/undo/undoStack.ts +18 -6
  181. package/dist/chunk-4TJ34FXA.js.map +0 -1
  182. package/dist/chunk-4X44DMRS.js.map +0 -1
  183. package/dist/chunk-EESJUSYR.js.map +0 -1
  184. package/dist/chunk-HBGH344L.js.map +0 -1
  185. package/dist/chunk-IBVRMHW5.js.map +0 -1
  186. package/dist/chunk-IRMPZIYU.js.map +0 -1
  187. package/dist/chunk-LPGBC63T.js.map +0 -1
  188. package/dist/chunk-PMLZP7RT.js.map +0 -1
  189. package/dist/chunk-RL2LOLNI.js.map +0 -1
  190. package/dist/chunk-T6DLGEJB.js.map +0 -1
  191. package/dist/chunk-WIG6XHJ7.js.map +0 -1
  192. package/dist/chunk-XSJJTXNA.js.map +0 -1
  193. package/dist/chunk-YCFKW5RX.js.map +0 -1
  194. package/src/primitives/StatusBar.less +0 -34
  195. package/src/primitives/StatusBar.stories.tsx +0 -24
  196. package/src/primitives/StatusBar.test.tsx +0 -25
  197. package/src/primitives/StatusBar.tsx +0 -28
package/dist/index.js CHANGED
@@ -1,46 +1,46 @@
1
1
  import { LoupeSwitchContext } from './chunk-2ES4W47N.js';
2
2
  export { LoupeSwitchContext } from './chunk-2ES4W47N.js';
3
- import { createEventBus, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
4
- export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
5
- import { useDragDrop, DragOverlay, Palette } from './chunk-2N7UBSS2.js';
6
- import { useTiledSurface } from './chunk-EESJUSYR.js';
7
- export { composeRects, toDeviceRect, useTiledSurface } from './chunk-EESJUSYR.js';
3
+ import { createEventBus } from './chunk-UX66USTN.js';
4
+ export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-UX66USTN.js';
5
+ import { useDragDrop, DragOverlay, Palette } from './chunk-J5MDJEOZ.js';
6
+ import { useTiledSurface } from './chunk-QMAY74TP.js';
7
+ export { composeRects, toDeviceRect, useTiledSurface } from './chunk-QMAY74TP.js';
8
8
  import { useJob } from './chunk-EFXRM6XQ.js';
9
9
  export { useJob } from './chunk-EFXRM6XQ.js';
10
- import { markCommands, boundsOf, roundFrac, worldToFrac, fracToWorld, fracEncloses, fracContains, POINT_MARK_SHAPE, roundPoints, fromShape, markSvgNodes, toShape } from './chunk-F4ACIK7Z.js';
11
- export { fracContains, fracEncloses, fracToWorld, markCommands, markSvgNodes, roundFrac, worldToFrac } from './chunk-F4ACIK7Z.js';
12
- import { ANNOTATION_WEASEL_TOOLS, annotationToolInfo, suppressContributions, mergeContributions, builtinContributions, TRANSIENT_BUILTINS, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, resolveLabTool, useLabChromeContext, labContributions, labAnnotationTools, LabHeaderRegion, LabFooterRegion, LabAsideRegion, LabSidebarRegion } from './chunk-4X44DMRS.js';
13
- export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, ExportMenu, LabAsideRegion, LabFooterRegion, LabHeaderRegion, LabSidebarRegion, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TRANSIENT_BUILTINS, TitleBarRegion, ToolbarRegion, ViewportRegion, annotationToolInfo, builtinContributions, contributionsIn, labContributions, mergeContributions, suppressContributions, toolContributions, useLabChromeContext } from './chunk-4X44DMRS.js';
10
+ import { markCommands, boundsOf, roundFrac, worldToFrac, fracToWorld, fracEncloses, fracContains, POINT_MARK_SHAPE, roundPoints, fromShape, markSvgNodes, toShape } from './chunk-6Q6ACJ3H.js';
11
+ export { fracContains, fracEncloses, fracToWorld, markCommands, markSvgNodes, roundFrac, worldToFrac } from './chunk-6Q6ACJ3H.js';
12
+ import { ANNOTATION_WEASEL_TOOLS, annotationToolInfo, suppressContributions, mergeContributions, builtinContributions, TRANSIENT_BUILTINS, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, resolveLabTool, useLabChromeContext, labContributions, labAnnotationTools, LabHeaderRegion, LabFooterRegion, LabAsideRegion, LabSidebarRegion } from './chunk-RN7T7OBO.js';
13
+ export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, ExportMenu, LabAsideRegion, LabFooterRegion, LabHeaderRegion, LabSidebarRegion, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TRANSIENT_BUILTINS, TitleBarRegion, ToolbarRegion, ViewportRegion, annotationToolInfo, builtinContributions, contributionsIn, labContributions, mergeContributions, suppressContributions, toolContributions, useLabChromeContext } from './chunk-RN7T7OBO.js';
14
14
  import { AnnotationsContext } from './chunk-3RY6WL5T.js';
15
15
  export { AnnotationsContext, useAnnotations, useAnnotationsOptional } from './chunk-3RY6WL5T.js';
16
- import { Split, JobProgress, Legend } from './chunk-LPGBC63T.js';
17
- export { JobProgress, Legend, Readout, Split } from './chunk-LPGBC63T.js';
18
- import { FloatingPanel } from './chunk-XE5HJOPL.js';
19
- export { FloatingPanel } from './chunk-XE5HJOPL.js';
20
- import { Toolbar, StatusBar, ScaleIndicator, ZoomControl } from './chunk-4TJ34FXA.js';
21
- export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-4TJ34FXA.js';
16
+ import { Split, JobProgress, Legend } from './chunk-KHB3KRRQ.js';
17
+ export { JobProgress, Legend, Readout, Split } from './chunk-KHB3KRRQ.js';
18
+ import { FloatingPanel } from './chunk-7FMZT2WL.js';
19
+ export { FloatingPanel } from './chunk-7FMZT2WL.js';
20
+ import { Toolbar, ScaleIndicator, ZoomControl } from './chunk-RXO5UEGT.js';
21
+ export { FpsMeter, ScaleIndicator, Toolbar, ZoomControl } from './chunk-RXO5UEGT.js';
22
22
  import './chunk-CZZPXUOU.js';
23
- import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-IBVRMHW5.js';
24
- export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-IBVRMHW5.js';
25
- import { newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-PMLZP7RT.js';
26
- export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-PMLZP7RT.js';
27
- export { ControlMatrix } from './chunk-HBGH344L.js';
28
- export { ControlPanel, inDialog, summarizeValue } from './chunk-XSJJTXNA.js';
29
- import { useConfigSchema } from './chunk-E6NARGU7.js';
30
- export { sectionTree, useConfigSchema } from './chunk-E6NARGU7.js';
31
- import { useLabContext, LabContext } from './chunk-Z76KEZAX.js';
23
+ import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-NHIO2O3O.js';
24
+ export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-NHIO2O3O.js';
25
+ import { valueRecord, newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-7ALJOVGP.js';
26
+ export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-7ALJOVGP.js';
27
+ export { ControlMatrix } from './chunk-4RCVWX4M.js';
28
+ export { ControlPanel, inDialog, summarizeValue } from './chunk-YZA73U4A.js';
29
+ import { useConfigSchema } from './chunk-SQX35X5V.js';
30
+ export { sectionTree, useConfigSchema } from './chunk-SQX35X5V.js';
31
+ import { LabContext, useLabContext } from './chunk-Z76KEZAX.js';
32
32
  export { LabContext, useLabContext } from './chunk-Z76KEZAX.js';
33
- import { useResolvedConfig } from './chunk-YCFKW5RX.js';
34
- export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-YCFKW5RX.js';
33
+ import { useResolvedConfig } from './chunk-T5OEUNCB.js';
34
+ export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-T5OEUNCB.js';
35
35
  export { fromConfigFields, isLeafVisible } from './chunk-WLMDTI2Q.js';
36
36
  import { hasConfigPath, schemaNodeAtPath, applyConfigWrite, resolveAutoConfig, isAuto, isRecord, autoPathsOf, fillConfigDefaults } from './chunk-READTDQX.js';
37
37
  export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-READTDQX.js';
38
- import { CanvasStack, Stage, fitStage } from './chunk-T6DLGEJB.js';
39
- export { CanvasStack, Stage, fitStage } from './chunk-T6DLGEJB.js';
38
+ import { CanvasStack, Stage, fitStage } from './chunk-XWR2P6MR.js';
39
+ export { CanvasStack, Stage, fitStage } from './chunk-XWR2P6MR.js';
40
40
  export { centerOn, zoomAt } from './chunk-ROGJWDQA.js';
41
41
  export { screenToWorld, worldToScreen } from './chunk-FG3EXZDV.js';
42
- import { useSurfaceOptional, useSurfaceCanvas, useTileId, SurfaceContext, SurfaceCanvasContext } from './chunk-WIG6XHJ7.js';
43
- export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId } from './chunk-WIG6XHJ7.js';
42
+ import { useSurfaceOptional, useSurfaceCanvas, useTileId, useTileInScope, SurfaceContext, SurfaceCanvasContext } from './chunk-6IIKLXBZ.js';
43
+ export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId, useTileInScope } from './chunk-6IIKLXBZ.js';
44
44
  import { CameraWheelContext, as2DView, CameraRegistryContext, DEFAULT_VIEW, CameraPublishContext, createCameraRegistry, withZoom } from './chunk-VYVGVBGY.js';
45
45
  export { CameraContext, CameraInput, CameraPublishContext, CameraRegistryContext, CameraScope, DEFAULT_VIEW, as2DView, createCameraRegistry, usePublishCamera } from './chunk-VYVGVBGY.js';
46
46
  import { applyCamera } from './chunk-ULYYNWGP.js';
@@ -50,12 +50,12 @@ export { CanvasStackContext } from './chunk-ZQIHJXP4.js';
50
50
  import { LabStoreContext, TrialIdProvider, TrialIdContext } from './chunk-2YILQ7Y5.js';
51
51
  export { LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, useLabStore, useTrialId } from './chunk-2YILQ7Y5.js';
52
52
  import './chunk-2RVCB2PT.js';
53
- import { D2, S2, X, U, G, p4, o3, c, Xt, c6, a6, l5, u3, a5, r3, a8, i3, o5, s4, c7, l9, p, f2, u2, y, O2, b4, l10, S, c8, i4, h2, j, k2, ae2, oe, de, b3, j2, n6, a9, C3, l3, u, v2, d3, j3, r4, n7, r5, i5, p2, f, n8, o4, c5, l8, w, L, n9, j4, xe2, n2 } from './chunk-IRMPZIYU.js';
54
- export { c as Button, V as CloseIcon, xe as CollapseIcon, _ as CurveField, s as DetailList, c2 as DetailRow, Se as ExpandIcon, l as Icon, u2 as Input, l11 as Jog, D2 as LayerList, re as PauseIcon, ne as PlayIcon, fe2 as PropertyControl, de as PropertyField, b3 as PropertyGroup, oe as PropertyList, ae2 as PropertyPanel, L2 as PropertyRow, se as PropertySpan, B as ResetIcon, O2 as Select, ae as StepBackIcon, $ as StepForwardIcon, ie as StopIcon, x2 as Subpanel, a5 as ToggleBar, oe2 as TokenPanel, H2 as inferTokenType, S2 as moveLayers, O3 as refitScale } from './chunk-IRMPZIYU.js';
55
- import { serializeSvg } from './chunk-E23Q6A3Y.js';
56
- import './chunk-XVUKDSYV.js';
57
- import { makeViewportZoomAction, createScene, asNodeId as asNodeId$1, registerNodeShape, useLatest, WeaselProvider, SceneCanvas, createPointerStore, PointerContextProvider, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, useActionsRegistry, useDepSource, ActionDisabledReason, useGestureDispatcher, sceneFromJSON, warmRender, renderSceneToPixels, useActiveToolContext } from '@weasel-js/core';
58
- import { createContext, useState, useRef, useEffect, useInsertionEffect, useCallback, useContext, useMemo, useSyncExternalStore, useLayoutEffect, useId, Children } from 'react';
53
+ import { A2, T, Z, U, V, de, G, p6, o3, c, Xt, c4, a5, l5, u3, s3, r3, a7, i3, o5, s5, c5, l8, p, f3, u2, y, O2, b4, l9, w2, c6, i4, h2, j, k2, ce2, le, ye, b3, j2, n6, a8, C3, l3, u, v3, d2, j3, r4, n7, r5, i5, p2, f2, n8, o4, p4, l7, w, L, n9, j4, xe, n2 } from './chunk-V4SMGEYY.js';
54
+ export { c as Button, V as CloseIcon, we as CollapseIcon, _ as CurveField, s as DetailList, c2 as DetailRow, Te as ExpandIcon, l as Icon, u2 as Input, l10 as Jog, A2 as LayerList, oe as PauseIcon, ae as PlayIcon, be as PropertyControl, ye as PropertyField, b3 as PropertyGroup, le as PropertyList, ce2 as PropertyPanel, L2 as PropertyRow, ue as PropertySpan, B as ResetIcon, O2 as Select, n7 as StatusBar, r5 as StatusBarItem, i5 as StatusBarSpacer, ce as StepBackIcon, $ as StepForwardIcon, se as StopIcon, x2 as Subpanel, s3 as ToggleBar, oe2 as TokenPanel, H2 as inferTokenType, T as moveLayers, O3 as refitScale } from './chunk-V4SMGEYY.js';
55
+ import { serializeSvg } from './chunk-VCK5T6EP.js';
56
+ import './chunk-AVOILDN2.js';
57
+ import { makeViewportZoomAction, createScene, asNodeId as asNodeId$1, registerNodeShape, useLatest, WeaselProvider, SceneCanvas, createPointerStore, PointerContextProvider, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, useActionsRegistry, useDepSource, ActionDisabledReason, useGestureDispatcher, sceneFromJSON, warmRender, renderSceneToPixels, useActiveToolContext, createHistory, useVisibleRaf, stableStringify } from '@weasel-js/core';
58
+ import { createContext, useState, useRef, useEffect, useInsertionEffect, useCallback, useContext, useSyncExternalStore, useReducer, useLayoutEffect, useMemo, useId, Children } from 'react';
59
59
  import { flushSync, createPortal } from 'react-dom';
60
60
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
61
61
  import { defineTheme, weaselTheme, colorCount, resolveTheme, colorAt } from '@weasel-js/theme';
@@ -92,7 +92,7 @@ function createMarkDrawOne(opts) {
92
92
  }
93
93
  function resolveMarkStyle(data, opts) {
94
94
  return {
95
- color: opts.meaning?.statuses?.find((s3) => s3.id === data.status)?.color,
95
+ color: opts.meaning?.statuses?.find((s4) => s4.id === data.status)?.color,
96
96
  // Read off the node rather than through the store's `isStale`, which
97
97
  // wants a projected Annotation this path does not have.
98
98
  stale: isStale(data.seen, opts.config, opts.positionDependsOn ?? [])
@@ -105,7 +105,7 @@ function capturePlan(base, format) {
105
105
  if (format === "svg") return "svg-document";
106
106
  return base?.kind === "svg" ? "svg-document" : "raster-stack";
107
107
  }
108
- function nestBaseSvg(markup, w2, h2) {
108
+ function nestBaseSvg(markup, w3, h2) {
109
109
  const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
110
110
  const root = doc.documentElement;
111
111
  if (doc.getElementsByTagName("parsererror").length > 0 || root.localName !== "svg") {
@@ -113,17 +113,17 @@ function nestBaseSvg(markup, w2, h2) {
113
113
  }
114
114
  root.setAttribute("x", "0");
115
115
  root.setAttribute("y", "0");
116
- root.setAttribute("width", String(w2));
116
+ root.setAttribute("width", String(w3));
117
117
  root.setAttribute("height", String(h2));
118
118
  return new XMLSerializer().serializeToString(root);
119
119
  }
120
- function embedRasterBase(base, w2, h2, onWarn) {
120
+ function embedRasterBase(base, w3, h2, onWarn) {
121
121
  const href = base.kind === "canvas" ? base.canvas.toDataURL() : base.kind === "image" ? base.src : "";
122
122
  if (base.kind === "image" && !href.startsWith("data:")) {
123
123
  onWarn?.(`base image '${href}' is a reference, so this SVG is not self-contained`);
124
124
  }
125
- return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w2, height: h2 }], {
126
- viewBox: { x: 0, y: 0, width: w2, height: h2 }
125
+ return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w3, height: h2 }], {
126
+ viewBox: { x: 0, y: 0, width: w3, height: h2 }
127
127
  });
128
128
  }
129
129
  function markNodesOf(scene, draw, scale) {
@@ -139,15 +139,15 @@ function markNodesOf(scene, draw, scale) {
139
139
  }
140
140
  function composeCaptureSvg(args) {
141
141
  const { base, scene, draw, scale, onWarn } = args;
142
- const { w: w2, h: h2 } = draw.content;
143
- const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w2, h2) : embedRasterBase(base, w2, h2, onWarn);
142
+ const { w: w3, h: h2 } = draw.content;
143
+ const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w3, h2) : embedRasterBase(base, w3, h2, onWarn);
144
144
  const marksXml = serializeSvg(markNodesOf(scene, draw, scale), {
145
- viewBox: { x: 0, y: 0, width: w2, height: h2 },
145
+ viewBox: { x: 0, y: 0, width: w3, height: h2 },
146
146
  onWarn
147
147
  });
148
- const outW = Math.max(1, Math.round(w2 * scale));
148
+ const outW = Math.max(1, Math.round(w3 * scale));
149
149
  const outH = Math.max(1, Math.round(h2 * scale));
150
- return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w2} ${h2}">${baseXml}${marksXml}</svg>`;
150
+ return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w3} ${h2}">${baseXml}${marksXml}</svg>`;
151
151
  }
152
152
  function context2d(width, height) {
153
153
  const canvas = document.createElement("canvas");
@@ -283,13 +283,13 @@ var MarkHistory = class {
283
283
  return null;
284
284
  }
285
285
  canUndo(at) {
286
- return this.next(this.undoable, (s3) => s3.canUndo(), at) !== null;
286
+ return this.next(this.undoable, (s4) => s4.canUndo(), at) !== null;
287
287
  }
288
288
  canRedo(at) {
289
- return this.next(this.redoable, (s3) => s3.canRedo(), at) !== null;
289
+ return this.next(this.redoable, (s4) => s4.canRedo(), at) !== null;
290
290
  }
291
291
  undo(at) {
292
- const target = this.next(this.undoable, (s3) => s3.canUndo(), at);
292
+ const target = this.next(this.undoable, (s4) => s4.canUndo(), at);
293
293
  const scene = target === null ? void 0 : at(target);
294
294
  if (!scene) return false;
295
295
  this.replaying = "undo";
@@ -300,7 +300,7 @@ var MarkHistory = class {
300
300
  }
301
301
  }
302
302
  redo(at) {
303
- const target = this.next(this.redoable, (s3) => s3.canRedo(), at);
303
+ const target = this.next(this.redoable, (s4) => s4.canRedo(), at);
304
304
  const scene = target === null ? void 0 : at(target);
305
305
  if (!scene) return false;
306
306
  this.replaying = "redo";
@@ -429,21 +429,21 @@ function createAnnotationStore(opts) {
429
429
  if (!scene) return [];
430
430
  const out = [];
431
431
  for (const id of scene.renderOrder()) {
432
- const a5 = project(target, String(id));
433
- if (a5) out.push(a5);
432
+ const a4 = project(target, String(id));
433
+ if (a4) out.push(a4);
434
434
  }
435
435
  return out;
436
436
  };
437
437
  const all = () => liveTargets().flatMap(marksOn);
438
- const matches = (a5, q) => {
439
- if (q.target !== void 0 && a5.target !== q.target) return false;
440
- if (q.kind !== void 0 && a5.kind !== q.kind) return false;
441
- if (q.status !== void 0 && a5.status !== q.status) return false;
438
+ const matches = (a4, q) => {
439
+ if (q.target !== void 0 && a4.target !== q.target) return false;
440
+ if (q.kind !== void 0 && a4.kind !== q.kind) return false;
441
+ if (q.status !== void 0 && a4.status !== q.status) return false;
442
442
  if (q.tags !== void 0) {
443
- const have = a5.tags ?? [];
443
+ const have = a4.tags ?? [];
444
444
  for (const t of q.tags) if (!have.includes(t)) return false;
445
445
  }
446
- if (q.where && !q.where(a5)) return false;
446
+ if (q.where && !q.where(a4)) return false;
447
447
  return true;
448
448
  };
449
449
  const nodeOf = (id) => {
@@ -461,17 +461,17 @@ function createAnnotationStore(opts) {
461
461
  return parts ? project(parts.target, parts.node) : void 0;
462
462
  },
463
463
  query(q) {
464
- return q ? all().filter((a5) => matches(a5, q)) : all();
464
+ return q ? all().filter((a4) => matches(a4, q)) : all();
465
465
  },
466
466
  hitTest(target, pt, tol = 0) {
467
- return marksOn(target).filter((a5) => fracContains(a5.frac, pt, tol)).reverse();
467
+ return marksOn(target).filter((a4) => fracContains(a4.frac, pt, tol)).reverse();
468
468
  },
469
469
  within(target, box) {
470
- return marksOn(target).filter((a5) => fracEncloses(box, a5.frac));
470
+ return marksOn(target).filter((a4) => fracEncloses(box, a4.frac));
471
471
  },
472
- isStale(a5, config) {
473
- const keys = targetOf(a5.target)?.positionDependsOn ?? [];
474
- return isStale(a5.seen, config, keys);
472
+ isStale(a4, config) {
473
+ const keys = targetOf(a4.target)?.positionDependsOn ?? [];
474
+ return isStale(a4.seen, config, keys);
475
475
  },
476
476
  canUndo() {
477
477
  return history.canUndo(historySceneAt);
@@ -624,7 +624,7 @@ function fitView(content, pane) {
624
624
  const zoom = content.w > 0 && content.h > 0 ? Math.min(pane.w / content.w, pane.h / content.h) : 1;
625
625
  return { zoom: Number.isFinite(zoom) && zoom > 0 ? zoom : 1, pan: { x: 0, y: 0 } };
626
626
  }
627
- var sameRect = (a5, b3) => a5 !== null && a5.x === b3.x && a5.y === b3.y && a5.w === b3.w && a5.h === b3.h;
627
+ var sameRect = (a4, b3) => a4 !== null && a4.x === b3.x && a4.y === b3.y && a4.w === b3.w && a4.h === b3.h;
628
628
  function ToolBridge({ toolId }) {
629
629
  const tools = useActiveToolContext();
630
630
  const setActive = tools.setActive;
@@ -668,6 +668,7 @@ function AnnotationOverlay({
668
668
  const configRef = useLatest(config);
669
669
  const id = target.id;
670
670
  const tileId = useTileId(id);
671
+ const inScope = useTileInScope(tileId);
671
672
  const lastTile = useRef(null);
672
673
  useEffect(() => {
673
674
  const el = target.ref.current ?? null;
@@ -758,6 +759,7 @@ function AnnotationOverlay({
758
759
  className: "lk-annotate__input",
759
760
  "data-annotation-target": id,
760
761
  "data-idle": annotationToolInfo(activeToolId) ? void 0 : "",
762
+ hidden: !inScope,
761
763
  role: "application",
762
764
  "aria-label": `Annotations on ${id}`,
763
765
  tabIndex: 0,
@@ -811,6 +813,62 @@ function AnnotationTargets({
811
813
  t.id
812
814
  )) });
813
815
  }
816
+ function createClockRegistry() {
817
+ const handles = /* @__PURE__ */ new Map();
818
+ const listeners = /* @__PURE__ */ new Set();
819
+ const notify = () => {
820
+ for (const l8 of [...listeners]) l8();
821
+ };
822
+ return {
823
+ register(trialId, handle) {
824
+ handles.set(trialId, handle);
825
+ notify();
826
+ return () => {
827
+ if (handles.get(trialId) !== handle) return;
828
+ handles.delete(trialId);
829
+ notify();
830
+ };
831
+ },
832
+ get: (trialId) => handles.get(trialId) ?? null,
833
+ all: () => handles.values(),
834
+ subscribe(listener) {
835
+ listeners.add(listener);
836
+ return () => {
837
+ listeners.delete(listener);
838
+ };
839
+ }
840
+ };
841
+ }
842
+ var ClockRegistryContext = createContext(null);
843
+ var TrialClockContext = createContext(null);
844
+
845
+ // src/clock/hooks.ts
846
+ var NO_SUBSCRIBE = () => () => {
847
+ };
848
+ function useTrialClock(trialId) {
849
+ const registry = useContext(ClockRegistryContext);
850
+ const own = useContext(TrialClockContext);
851
+ const lab = useContext(LabContext);
852
+ useSyncExternalStore(registry?.subscribe ?? NO_SUBSCRIBE, () => registry ? resolve() : own);
853
+ function resolve() {
854
+ if (trialId !== void 0) return registry?.get(trialId) ?? null;
855
+ if (own) return own;
856
+ const focused = lab?.focusedTrialId;
857
+ return focused ? registry?.get(focused) ?? null : null;
858
+ }
859
+ const clock = resolve()?.clock ?? null;
860
+ const [, bump] = useReducer((n6) => n6 + 1, 0);
861
+ useEffect(() => clock?.subscribe(bump), [clock]);
862
+ return clock;
863
+ }
864
+ function useClockFrame(fn) {
865
+ const handle = useContext(TrialClockContext);
866
+ const latest = useLatest(fn);
867
+ useEffect(
868
+ () => handle?.onFrame((elapsed, pass) => latest.current(elapsed, pass)),
869
+ [handle, latest]
870
+ );
871
+ }
814
872
 
815
873
  // src/instrument/defineInstrument.ts
816
874
  function defineInstrument(spec) {
@@ -870,6 +928,41 @@ function validateConfigSchema(fields) {
870
928
  return { valid: errors.length === 0, errors };
871
929
  }
872
930
  var AnnotationPreloadContext = createContext(null);
931
+ function useClockLoop(registry) {
932
+ const loop = useVisibleRaf(
933
+ (timestamp) => {
934
+ let live = false;
935
+ for (const { clock } of registry.all()) {
936
+ if (clock.inert) continue;
937
+ clock.sync(timestamp);
938
+ if (!clock.inert) live = true;
939
+ }
940
+ if (live) loop.request();
941
+ },
942
+ {
943
+ onResume: () => {
944
+ for (const { clock } of registry.all()) clock.rebase();
945
+ }
946
+ }
947
+ );
948
+ useEffect(() => {
949
+ let unwakes = [];
950
+ const attach = () => {
951
+ for (const off2 of unwakes) off2();
952
+ unwakes = [];
953
+ for (const { clock } of registry.all()) {
954
+ unwakes.push(clock.onWake(() => loop.request()));
955
+ if (!clock.inert) loop.request();
956
+ }
957
+ };
958
+ attach();
959
+ const off = registry.subscribe(attach);
960
+ return () => {
961
+ off();
962
+ for (const unwake of unwakes) unwake();
963
+ };
964
+ }, [registry, loop]);
965
+ }
873
966
 
874
967
  // src/theme/interstellar.theme.json
875
968
  var interstellar_theme_default = {
@@ -938,20 +1031,6 @@ var interstellar_theme_default = {
938
1031
  type: "color"
939
1032
  }
940
1033
  },
941
- "fg-muted": {
942
- by: "mode",
943
- dark: {
944
- value: "#a8a8c0",
945
- type: "color"
946
- }
947
- },
948
- "fg-subtle": {
949
- by: "mode",
950
- dark: {
951
- value: "#5a5a78",
952
- type: "color"
953
- }
954
- },
955
1034
  accent: {
956
1035
  by: "mode",
957
1036
  dark: {
@@ -1010,6 +1089,180 @@ var interstellarTheme = defineTheme({
1010
1089
  extends: weaselTheme
1011
1090
  });
1012
1091
 
1092
+ // src/clock/trialClock.ts
1093
+ var passesOf = (loop) => loop === true ? Number.POSITIVE_INFINITY : loop === false ? 1 : Math.max(1, loop);
1094
+ var checkRate = (rate, seekable) => {
1095
+ if (!Number.isFinite(rate)) throw new RangeError(`labkit: a clock's rate is finite, not ${rate}`);
1096
+ if (rate < 0 && !seekable)
1097
+ throw new RangeError("labkit: a clock that is not seekable cannot run backward");
1098
+ };
1099
+ function createTrialClock(spec, start) {
1100
+ const duration = spec.duration ?? Number.POSITIVE_INFINITY;
1101
+ const seekable = spec.seekable ?? true;
1102
+ const openRate = spec.rate ?? 0;
1103
+ checkRate(openRate, seekable);
1104
+ let loop = spec.loop ?? false;
1105
+ let span = duration * passesOf(loop);
1106
+ let rate = start ? start.rate : openRate;
1107
+ let elapsed = start ? Math.min(Math.max(0, start.elapsed), span) : 0;
1108
+ let ramp = null;
1109
+ let last = Number.NaN;
1110
+ let changed = false;
1111
+ let slept = false;
1112
+ let wakeFired = false;
1113
+ const wakes = /* @__PURE__ */ new Set();
1114
+ const subs = /* @__PURE__ */ new Set();
1115
+ const frames = /* @__PURE__ */ new Set();
1116
+ const isInert = () => rate === 0 && ramp === null && !changed;
1117
+ const notify = () => {
1118
+ for (const fn of [...subs]) fn();
1119
+ };
1120
+ const change = () => {
1121
+ changed = true;
1122
+ if (wakeFired) return;
1123
+ wakeFired = true;
1124
+ for (const fn of [...wakes]) fn();
1125
+ };
1126
+ const passAt = () => duration > 0 && Number.isFinite(duration) ? elapsed >= span ? passesOf(loop) - 1 : Math.floor(elapsed / duration) : 0;
1127
+ const phaseAt = () => duration > 0 && Number.isFinite(duration) ? elapsed >= span ? 1 : elapsed % duration / duration : 0;
1128
+ const advance = (dt) => {
1129
+ if (!ramp) return rate * dt;
1130
+ if (dt >= ramp.left) {
1131
+ const area2 = (rate + ramp.to) / 2 * ramp.left + ramp.to * (dt - ramp.left);
1132
+ rate = ramp.to;
1133
+ ramp = null;
1134
+ notify();
1135
+ return area2;
1136
+ }
1137
+ const next = rate + (ramp.to - rate) * dt / ramp.left;
1138
+ const area = (rate + next) / 2 * dt;
1139
+ rate = next;
1140
+ ramp.left -= dt;
1141
+ return area;
1142
+ };
1143
+ const clock = {
1144
+ get elapsed() {
1145
+ return elapsed;
1146
+ },
1147
+ get pass() {
1148
+ return passAt();
1149
+ },
1150
+ get phase() {
1151
+ return phaseAt();
1152
+ },
1153
+ get rate() {
1154
+ return rate;
1155
+ },
1156
+ set rate(next) {
1157
+ checkRate(next, seekable);
1158
+ rate = next;
1159
+ ramp = null;
1160
+ change();
1161
+ notify();
1162
+ },
1163
+ get loop() {
1164
+ return loop;
1165
+ },
1166
+ set loop(next) {
1167
+ loop = next;
1168
+ span = duration * passesOf(loop);
1169
+ elapsed = Math.min(elapsed, span);
1170
+ change();
1171
+ notify();
1172
+ },
1173
+ ramp(to, over) {
1174
+ checkRate(to, seekable);
1175
+ if (!(over > 0)) {
1176
+ clock.rate = to;
1177
+ return;
1178
+ }
1179
+ ramp = { to, left: over };
1180
+ change();
1181
+ notify();
1182
+ },
1183
+ seek(target) {
1184
+ if (!seekable) throw new Error("labkit: this clock is not seekable");
1185
+ elapsed = Math.min(Math.max(0, target), span);
1186
+ change();
1187
+ notify();
1188
+ },
1189
+ sync(timestamp) {
1190
+ const wasChanged = changed;
1191
+ const before = elapsed;
1192
+ if (!Number.isNaN(last) && !slept) {
1193
+ elapsed += advance(Math.max(0, timestamp - last));
1194
+ if (elapsed >= span && rate > 0) {
1195
+ elapsed = span;
1196
+ rate = 0;
1197
+ ramp = null;
1198
+ notify();
1199
+ } else if (elapsed <= 0 && rate < 0) {
1200
+ elapsed = 0;
1201
+ rate = 0;
1202
+ ramp = null;
1203
+ notify();
1204
+ }
1205
+ }
1206
+ last = timestamp;
1207
+ changed = false;
1208
+ wakeFired = false;
1209
+ slept = isInert();
1210
+ if (elapsed !== before || wasChanged) {
1211
+ const pass = passAt();
1212
+ for (const fn of [...frames]) fn(elapsed, pass);
1213
+ }
1214
+ },
1215
+ rebase() {
1216
+ last = Number.NaN;
1217
+ },
1218
+ get inert() {
1219
+ return isInert();
1220
+ },
1221
+ onWake(fn) {
1222
+ wakes.add(fn);
1223
+ return () => {
1224
+ wakes.delete(fn);
1225
+ };
1226
+ },
1227
+ subscribe(fn) {
1228
+ subs.add(fn);
1229
+ return () => {
1230
+ subs.delete(fn);
1231
+ };
1232
+ }
1233
+ };
1234
+ return {
1235
+ clock,
1236
+ reset() {
1237
+ elapsed = 0;
1238
+ rate = openRate;
1239
+ ramp = null;
1240
+ change();
1241
+ notify();
1242
+ },
1243
+ onFrame(fn) {
1244
+ frames.add(fn);
1245
+ return () => {
1246
+ frames.delete(fn);
1247
+ };
1248
+ }
1249
+ };
1250
+ }
1251
+
1252
+ // src/undo/stateOp.ts
1253
+ function stateOp(before, read, write) {
1254
+ let after;
1255
+ const forward = {
1256
+ label: "state",
1257
+ apply: () => write(structuredClone(after)),
1258
+ invert: () => {
1259
+ after = structuredClone(read());
1260
+ return { label: "state", apply: () => write(structuredClone(before)), invert: () => forward };
1261
+ }
1262
+ };
1263
+ return forward;
1264
+ }
1265
+
1013
1266
  // src/canvas/cameraZoom.ts
1014
1267
  function atScale(view, scale, at) {
1015
1268
  return {
@@ -1027,6 +1280,36 @@ function zoomCameraTo(camera, zoom) {
1027
1280
  const scale = Math.min(max, Math.max(min, zoom));
1028
1281
  camera.set(atScale(camera.get(), scale, viewMiddle(camera)));
1029
1282
  }
1283
+ var PresentationContext = createContext(null);
1284
+ function usePresentation() {
1285
+ const value = useContext(PresentationContext);
1286
+ if (!value) throw new Error("[labkit] usePresentation() must be called inside <Lab>");
1287
+ return value;
1288
+ }
1289
+ function usePresentedTrial(trialId) {
1290
+ const presented = useContext(PresentationContext)?.trialId === trialId;
1291
+ const surface = useSurfaceOptional();
1292
+ const [el, ref] = useState(null);
1293
+ useLayoutEffect(() => {
1294
+ if (!presented || !el || !surface) return;
1295
+ surface.scope(el);
1296
+ return () => surface.scope(null);
1297
+ }, [presented, el, surface]);
1298
+ return { presented, ref };
1299
+ }
1300
+ function hasPresentParam() {
1301
+ return typeof location !== "undefined" && new URLSearchParams(location.search).has("present");
1302
+ }
1303
+ var LightboxContext = createContext(null);
1304
+ function useLightbox() {
1305
+ return useContext(LightboxContext);
1306
+ }
1307
+ function useLightboxControl() {
1308
+ const ctx = useContext(LightboxContext);
1309
+ const claim = ctx?.claimControl;
1310
+ useLayoutEffect(() => claim?.(), [claim]);
1311
+ return ctx;
1312
+ }
1030
1313
  function TrialBody({ sidebar, children, contentClassName, ...rest }) {
1031
1314
  return /* @__PURE__ */ jsx(
1032
1315
  Split,
@@ -1123,7 +1406,7 @@ function Portalled({
1123
1406
  }
1124
1407
  function UndockedSections({ trialId, sections, onDock }) {
1125
1408
  if (sections.length === 0) return null;
1126
- return /* @__PURE__ */ jsx(Fragment, { children: sections.map((c7) => /* @__PURE__ */ jsx(Portalled, { trialId, contribution: c7, onDock }, c7.id)) });
1409
+ return /* @__PURE__ */ jsx(Fragment, { children: sections.map((c6) => /* @__PURE__ */ jsx(Portalled, { trialId, contribution: c6, onDock }, c6.id)) });
1127
1410
  }
1128
1411
  var NO_OP = () => {
1129
1412
  };
@@ -1145,19 +1428,20 @@ function TrialChrome({
1145
1428
  children
1146
1429
  }) {
1147
1430
  const lab = useLabContext();
1431
+ const { presented, ref: presentedRef } = usePresentedTrial(trialId);
1148
1432
  const storeCtx = useContext(LabStoreContext);
1149
1433
  if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
1150
- const updateTrialView = useStore(storeCtx.store, (s3) => s3.updateTrialView);
1151
- const updateTrialSidebarWidth = useStore(storeCtx.store, (s3) => s3.updateTrialSidebarWidth);
1152
- const setTrialTitle = useStore(storeCtx.store, (s3) => s3.setTrialTitle);
1153
- const setTrialSectionCollapsed = useStore(storeCtx.store, (s3) => s3.setTrialSectionCollapsed);
1154
- const updateTrialConfig = useStore(storeCtx.store, (s3) => s3.updateTrialConfig);
1155
- const updateTrialState = useStore(storeCtx.store, (s3) => s3.updateTrialState);
1156
- const undockPanelAction = useStore(storeCtx.store, (s3) => s3.undockPanel);
1157
- const dockPanelAction = useStore(storeCtx.store, (s3) => s3.dockPanel);
1158
- const undockedPanels = useStore(storeCtx.store, (s3) => s3.undockedPanels);
1434
+ const updateTrialView = useStore(storeCtx.store, (s4) => s4.updateTrialView);
1435
+ const updateTrialSidebarWidth = useStore(storeCtx.store, (s4) => s4.updateTrialSidebarWidth);
1436
+ const setTrialTitle = useStore(storeCtx.store, (s4) => s4.setTrialTitle);
1437
+ const setTrialSectionCollapsed = useStore(storeCtx.store, (s4) => s4.setTrialSectionCollapsed);
1438
+ const updateTrialConfig = useStore(storeCtx.store, (s4) => s4.updateTrialConfig);
1439
+ const updateTrialState = useStore(storeCtx.store, (s4) => s4.updateTrialState);
1440
+ const undockPanelAction = useStore(storeCtx.store, (s4) => s4.undockPanel);
1441
+ const dockPanelAction = useStore(storeCtx.store, (s4) => s4.dockPanel);
1442
+ const undockedPanels = useStore(storeCtx.store, (s4) => s4.undockedPanels);
1159
1443
  const undockedIds = useMemo(
1160
- () => Object.values(undockedPanels).filter((p4) => p4.trialId === trialId).map((p4) => p4.sectionId),
1444
+ () => Object.values(undockedPanels).filter((p5) => p5.trialId === trialId).map((p5) => p5.sectionId),
1161
1445
  [undockedPanels, trialId]
1162
1446
  );
1163
1447
  const view2d = as2DView(record.view);
@@ -1174,6 +1458,7 @@ function TrialChrome({
1174
1458
  cameras?.subscribe ?? noSubscribe,
1175
1459
  () => cameras?.get(trialId) ?? null
1176
1460
  );
1461
+ const lightbox = useLightboxControl();
1177
1462
  const ctx = useMemo(() => {
1178
1463
  const setZoom = (z) => {
1179
1464
  if (camera) zoomCameraTo(camera, z);
@@ -1194,6 +1479,9 @@ function TrialChrome({
1194
1479
  hasLoupe: loupe != null,
1195
1480
  loupeOn: loupe?.on ?? false,
1196
1481
  toggleLoupe: loupe?.toggle ?? NO_OP,
1482
+ canExpand: lightbox != null,
1483
+ expanded: lightbox?.expanded ?? false,
1484
+ toggleExpanded: lightbox?.toggle ?? NO_OP,
1197
1485
  configSchema,
1198
1486
  configFields: instrument.config ? [] : instrument.configSchema?.() ?? [],
1199
1487
  config: record.config,
@@ -1218,7 +1506,7 @@ function TrialChrome({
1218
1506
  undockedPanels: undockedIds,
1219
1507
  undockPanel: (sectionId, as) => undockPanelAction(trialId, sectionId, as),
1220
1508
  dockPanel: (sectionId) => dockPanelAction(trialId, sectionId),
1221
- savedSnapshots: lab.savedSnapshots.filter((s3) => s3.trialId === trialId),
1509
+ savedSnapshots: lab.savedSnapshots.filter((s4) => s4.trialId === trialId),
1222
1510
  saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
1223
1511
  loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
1224
1512
  clone: () => lab.cloneTrial(trialId),
@@ -1239,6 +1527,7 @@ function TrialChrome({
1239
1527
  updateTrialState,
1240
1528
  undoBindings,
1241
1529
  loupe,
1530
+ lightbox,
1242
1531
  view2d,
1243
1532
  activeToolId,
1244
1533
  setActiveTool,
@@ -1266,10 +1555,10 @@ function TrialChrome({
1266
1555
  ),
1267
1556
  [instrument, ctx, trialChrome, chrome, suppress, lab.controls]
1268
1557
  );
1269
- const inRegion = (region) => contributions.filter((c7) => c7.region === region);
1558
+ const inRegion = (region) => contributions.filter((c6) => c6.region === region);
1270
1559
  const handleKeyDown = (e) => {
1271
1560
  const mod = e.metaKey || e.ctrlKey;
1272
- if (!mod) return;
1561
+ if (!mod || presented) return;
1273
1562
  if (e.key === "z" || e.key === "Z") {
1274
1563
  e.preventDefault();
1275
1564
  if (e.shiftKey) ctx.redo();
@@ -1282,9 +1571,11 @@ function TrialChrome({
1282
1571
  return /* @__PURE__ */ jsxs(
1283
1572
  "section",
1284
1573
  {
1574
+ ref: presentedRef,
1285
1575
  className: "lk-trial",
1286
1576
  "data-trial-id": trialId,
1287
1577
  "data-focused": lab.trials.length > 1 && lab.focusedTrialId === trialId ? "true" : void 0,
1578
+ "data-lk-presented": presented ? "" : void 0,
1288
1579
  "aria-label": `Trial ${title}`,
1289
1580
  tabIndex: -1,
1290
1581
  onKeyDown: handleKeyDown,
@@ -1304,15 +1595,18 @@ function TrialChrome({
1304
1595
  TrialBody,
1305
1596
  {
1306
1597
  width: record.sidebarWidth,
1307
- onWidthChange: (w2) => updateTrialSidebarWidth(trialId, w2),
1308
- contentClassName: instrument.canvas || instrument.stage ? "lk-trial__content--flush" : void 0,
1598
+ onWidthChange: (w3) => updateTrialSidebarWidth(trialId, w3),
1599
+ contentClassName: [
1600
+ instrument.canvas || instrument.stage ? "lk-trial__content--flush" : "",
1601
+ presented ? "lk-trial__stage" : ""
1602
+ ].filter(Boolean).join(" ") || void 0,
1309
1603
  sidebar: /* @__PURE__ */ jsxs(Fragment, { children: [
1310
1604
  /* @__PURE__ */ jsx(SidebarRegion, { contributions: inRegion("sidebar"), ctx }),
1311
1605
  /* @__PURE__ */ jsx(
1312
1606
  UndockedSections,
1313
1607
  {
1314
1608
  trialId,
1315
- sections: inRegion("sidebar").filter((c7) => undockedIds.includes(c7.id)),
1609
+ sections: inRegion("sidebar").filter((c6) => undockedIds.includes(c6.id)),
1316
1610
  onDock: ctx.dockPanel
1317
1611
  }
1318
1612
  )
@@ -1336,8 +1630,8 @@ function Trial({ id, chrome, suppress }) {
1336
1630
  const lab = useLabContext();
1337
1631
  const storeCtx = useContext(LabStoreContext);
1338
1632
  if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
1339
- const instruments = useStore(storeCtx.store, (s3) => s3.instruments) ?? lab.instruments;
1340
- const record = useStore(storeCtx.store, (s3) => s3.trials.find((w2) => w2.id === id));
1633
+ const instruments = useStore(storeCtx.store, (s4) => s4.instruments) ?? lab.instruments;
1634
+ const record = useStore(storeCtx.store, (s4) => s4.trials.find((w3) => w3.id === id));
1341
1635
  const instrument = record ? instruments.find((i4) => i4.name === record.instrumentName) : void 0;
1342
1636
  const kept = useKeptMarks(instrument?.annotations?.storage, id);
1343
1637
  if (!record) {
@@ -1410,14 +1704,15 @@ function TrialRuntime({
1410
1704
  );
1411
1705
  const [pointer] = useState(createPointerStore);
1412
1706
  const canvasContainerRef = useRef(null);
1413
- const updateTrialState = useStore(store, (s3) => s3.updateTrialState);
1414
- const updateTrialConfig = useStore(store, (s3) => s3.updateTrialConfig);
1415
- const updateTrialView = useStore(store, (s3) => s3.updateTrialView);
1416
- const updateTrialUndoStack = useStore(store, (s3) => s3.updateTrialUndoStack);
1417
- const updateTrialAnnotations = useStore(store, (s3) => s3.updateTrialAnnotations);
1418
- const labToolId = useStore(store, (s3) => s3.activeToolId);
1419
- const setLabTool = useStore(store, (s3) => s3.setLabTool);
1420
- const setTrialTool = useStore(store, (s3) => s3.setTrialTool);
1707
+ const updateTrialState = useStore(store, (s4) => s4.updateTrialState);
1708
+ const updateTrialConfig = useStore(store, (s4) => s4.updateTrialConfig);
1709
+ const updateTrialView = useStore(store, (s4) => s4.updateTrialView);
1710
+ const setTrialHistory = useStore(store, (s4) => s4.setTrialHistory);
1711
+ const updateTrialClock = useStore(store, (s4) => s4.updateTrialClock);
1712
+ const updateTrialAnnotations = useStore(store, (s4) => s4.updateTrialAnnotations);
1713
+ const labToolId = useStore(store, (s4) => s4.activeToolId);
1714
+ const setLabTool = useStore(store, (s4) => s4.setLabTool);
1715
+ const setTrialTool = useStore(store, (s4) => s4.setTrialTool);
1421
1716
  const schema = useConfigSchema(instrument);
1422
1717
  const { config } = useResolvedConfig(schema, record.config, record.auto);
1423
1718
  const busRef = useRef(null);
@@ -1436,15 +1731,40 @@ function TrialRuntime({
1436
1731
  () => (instrument.canvas?.layers ?? []).filter((l8) => layerVisibility[l8.id] !== false).map((l8) => l8.id),
1437
1732
  [instrument.canvas, layerVisibility]
1438
1733
  );
1734
+ const [clockHandle] = useState(
1735
+ () => instrument.clock ? createTrialClock(instrument.clock, record.clock) : null
1736
+ );
1737
+ const clocks = useContext(ClockRegistryContext);
1738
+ useEffect(() => {
1739
+ if (!clockHandle || !clocks) return;
1740
+ return clocks.register(record.id, clockHandle);
1741
+ }, [clocks, clockHandle, record.id]);
1742
+ useEffect(() => {
1743
+ if (!clockHandle) return;
1744
+ const { clock } = clockHandle;
1745
+ return clock.subscribe(
1746
+ () => updateTrialClock(record.id, { elapsed: clock.elapsed, rate: clock.rate })
1747
+ );
1748
+ }, [clockHandle, record.id, updateTrialClock]);
1749
+ const ticks = useMemo(
1750
+ () => clockHandle ? (fn) => clockHandle.onFrame(() => fn()) : void 0,
1751
+ [clockHandle]
1752
+ );
1439
1753
  const undoCap = instrument.undo;
1440
1754
  const undoEvents = useMemo(() => new Set(undoCap?.snapshotOn ?? ["state.change"]), [undoCap]);
1441
1755
  const maxDepth = undoCap?.maxDepth ?? 50;
1442
1756
  const snapshotIfNeeded = (event) => {
1443
1757
  if (!undoCap || !undoEvents.has(event)) return;
1444
- const current = store.getState().trials.find((w2) => w2.id === record.id);
1758
+ const current = store.getState().trials.find((w3) => w3.id === record.id);
1445
1759
  if (!current) return;
1446
- const snap = structuredClone(current.state);
1447
- updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
1760
+ let history = current.history;
1761
+ if (!history) {
1762
+ history = createHistory(null, { historyLimit: maxDepth });
1763
+ setTrialHistory(record.id, history);
1764
+ }
1765
+ const read = () => store.getState().trials.find((w3) => w3.id === record.id)?.state;
1766
+ const write = (state) => updateTrialState(record.id, state);
1767
+ history.recordEntry([stateOp(structuredClone(current.state), read, write)], event);
1448
1768
  };
1449
1769
  const setView = (v2) => updateTrialView(record.id, v2);
1450
1770
  const stage = instrument.canvas ? void 0 : instrument.stage;
@@ -1573,7 +1893,8 @@ function TrialRuntime({
1573
1893
  },
1574
1894
  activeToolId: resolvedToolId,
1575
1895
  visibleLayers,
1576
- pointer
1896
+ pointer,
1897
+ ...clockHandle ? { clock: clockHandle.clock } : {}
1577
1898
  },
1578
1899
  emit: (event) => {
1579
1900
  snapshotIfNeeded(event);
@@ -1581,50 +1902,48 @@ function TrialRuntime({
1581
1902
  },
1582
1903
  job: jobCap ? job : void 0
1583
1904
  };
1584
- const undoState = () => {
1585
- const result = undo(record.undoStack, structuredClone(record.state));
1586
- if (!result) return;
1587
- updateTrialState(record.id, result.snapshot);
1588
- updateTrialUndoStack(record.id, result.stack);
1589
- };
1590
- const redoState = () => {
1591
- const result = redo(record.undoStack, structuredClone(record.state));
1592
- if (!result) return;
1593
- updateTrialState(record.id, result.snapshot);
1594
- updateTrialUndoStack(record.id, result.stack);
1595
- };
1905
+ const stateHistory = useHistoryVersion(record.history);
1596
1906
  const undoBindings = undoCap || annotationsCap ? {
1597
- canUndo: markMoves.undo || (undoCap ? record.undoStack.past.length > 0 : false),
1598
- canRedo: markMoves.redo || (undoCap ? record.undoStack.future.length > 0 : false),
1907
+ canUndo: markMoves.undo || (undoCap ? stateHistory?.canUndo() ?? false : false),
1908
+ canRedo: markMoves.redo || (undoCap ? stateHistory?.canRedo() ?? false : false),
1599
1909
  undo: () => {
1600
1910
  if (annotationsCap && annotations.undo()) return;
1601
- if (undoCap) undoState();
1911
+ if (undoCap) stateHistory?.undo();
1602
1912
  },
1603
1913
  redo: () => {
1604
1914
  if (annotationsCap && annotations.redo()) return;
1605
- if (undoCap) redoState();
1915
+ if (undoCap) stateHistory?.redo();
1606
1916
  }
1607
1917
  } : void 0;
1608
1918
  const loupeBindings = lenses > 0 ? { on: loupeOn, toggle: () => setLoupeOn((v2) => !v2) } : void 0;
1609
1919
  const canvasLayers = useMemo(() => {
1610
1920
  if (!instrument.canvas) return [];
1611
1921
  const baseLayers = instrument.canvas.layers;
1612
- const ordered = layerOrder ? [...baseLayers].sort((a5, b3) => layerOrder.indexOf(a5.id) - layerOrder.indexOf(b3.id)) : baseLayers;
1922
+ const ordered = layerOrder ? [...baseLayers].sort((a4, b3) => layerOrder.indexOf(a4.id) - layerOrder.indexOf(b3.id)) : baseLayers;
1923
+ const clock = clockHandle?.clock;
1613
1924
  return ordered.map((layer) => ({
1614
1925
  id: layer.id,
1615
1926
  visible: layerVisibility[layer.id] !== false,
1927
+ timed: layer.timed === true,
1616
1928
  render: (ctx, view, frame) => {
1617
1929
  applyCamera(ctx, view, frame);
1618
- layer.draw(ctx, { state: record.state, config, zoom: view.zoom });
1930
+ layer.draw(ctx, {
1931
+ state: record.state,
1932
+ config,
1933
+ zoom: view.zoom,
1934
+ elapsed: clock?.elapsed ?? 0,
1935
+ pass: clock?.pass ?? 0,
1936
+ phase: clock?.phase ?? 0
1937
+ });
1619
1938
  }
1620
1939
  }));
1621
- }, [instrument.canvas, record.state, config, layerVisibility, layerOrder]);
1940
+ }, [instrument.canvas, record.state, config, layerVisibility, layerOrder, clockHandle]);
1622
1941
  const layerDescriptors = useMemo(() => {
1623
1942
  if (!instrument.layers) return [];
1624
1943
  return instrument.layers.ids.map((l8) => typeof l8 === "string" ? { id: l8, label: l8 } : l8);
1625
1944
  }, [instrument.layers]);
1626
1945
  const layerItems = useMemo(() => {
1627
- const ordered = layerOrder ? [...layerDescriptors].sort((a5, b3) => layerOrder.indexOf(a5.id) - layerOrder.indexOf(b3.id)) : layerDescriptors;
1946
+ const ordered = layerOrder ? [...layerDescriptors].sort((a4, b3) => layerOrder.indexOf(a4.id) - layerOrder.indexOf(b3.id)) : layerDescriptors;
1628
1947
  const pinnedLast = [
1629
1948
  ...ordered.filter((l8) => !l8.alwaysOn),
1630
1949
  ...ordered.filter((l8) => l8.alwaysOn)
@@ -1637,7 +1956,7 @@ function TrialRuntime({
1637
1956
  }));
1638
1957
  }, [layerDescriptors, layerOrder, layerVisibility]);
1639
1958
  const dragDropResult = useDragDrop({
1640
- capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s3) => s3 },
1959
+ capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s4) => s4 },
1641
1960
  canvasContainerRef,
1642
1961
  view: view2d ?? DEFAULT_VIEW,
1643
1962
  worldSpec: instrument.canvas?.worldSpec,
@@ -1654,8 +1973,8 @@ function TrialRuntime({
1654
1973
  });
1655
1974
  const paletteItems = useMemo(() => {
1656
1975
  if (!instrument.dragDrop) return [];
1657
- const p4 = instrument.dragDrop.palette;
1658
- return typeof p4 === "function" ? p4(record.state, config) : p4;
1976
+ const p5 = instrument.dragDrop.palette;
1977
+ return typeof p5 === "function" ? p5(record.state, config) : p5;
1659
1978
  }, [instrument.dragDrop, record.state, config]);
1660
1979
  const layersWithFeedback = useMemo(() => {
1661
1980
  if (!dragDropResult.drag?.feedback) return canvasLayers;
@@ -1694,6 +2013,7 @@ function TrialRuntime({
1694
2013
  onResize: placeView,
1695
2014
  minZoom: instrument.canvas.minZoom,
1696
2015
  maxZoom: instrument.canvas.maxZoom,
2016
+ ticks,
1697
2017
  children: instrument.render(renderCtx)
1698
2018
  }
1699
2019
  ),
@@ -1723,11 +2043,11 @@ function TrialRuntime({
1723
2043
  );
1724
2044
  const layerListNode = useMemo(
1725
2045
  () => instrument.layers && layerDescriptors.length > 0 ? /* @__PURE__ */ jsx(
1726
- D2,
2046
+ A2,
1727
2047
  {
1728
2048
  items: layerItems,
1729
2049
  onReorder: (move) => {
1730
- setLayerOrder(S2(layerItems, move).map((l8) => l8.id));
2050
+ setLayerOrder(T(layerItems, move).map((l8) => l8.id));
1731
2051
  bus.emit("layers.reorder");
1732
2052
  },
1733
2053
  onVisibilityChange: (lid, visible) => {
@@ -1768,7 +2088,7 @@ function TrialRuntime({
1768
2088
  }
1769
2089
  ) : null;
1770
2090
  const scopedBody = /* @__PURE__ */ jsx(WeaselProvider, { isolate: true, children: /* @__PURE__ */ jsx(PointerContextProvider, { store: pointer, children: /* @__PURE__ */ jsx(CameraPublishContext.Provider, { value: publishCamera, children: /* @__PURE__ */ jsx(LoupeSwitchContext.Provider, { value: loupeSwitch, children: body }) }) }) });
1771
- return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? provided : null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: wheelSlot, children: /* @__PURE__ */ jsxs(
2091
+ return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(TrialClockContext.Provider, { value: clockHandle, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? provided : null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: wheelSlot, children: /* @__PURE__ */ jsxs(
1772
2092
  TrialChrome,
1773
2093
  {
1774
2094
  job: jobCap ? job : void 0,
@@ -1788,7 +2108,13 @@ function TrialRuntime({
1788
2108
  annotationOverlays
1789
2109
  ]
1790
2110
  }
1791
- ) }) }) });
2111
+ ) }) }) }) });
2112
+ }
2113
+ var NO_SUBSCRIBE2 = () => () => {
2114
+ };
2115
+ function useHistoryVersion(history) {
2116
+ useSyncExternalStore(history?.subscribe ?? NO_SUBSCRIBE2, () => history?.getVersion() ?? 0);
2117
+ return history;
1792
2118
  }
1793
2119
 
1794
2120
  // src/trial/trialOps.ts
@@ -1841,8 +2167,7 @@ function addTrial(trials, instruments, instrumentName, options = {}) {
1841
2167
  instrumentName,
1842
2168
  config,
1843
2169
  state,
1844
- view: initialView(instrument),
1845
- undoStack: { past: [], future: [] }
2170
+ view: initialView(instrument)
1846
2171
  };
1847
2172
  if (options.config) record.configSeed = seeded.config;
1848
2173
  if (seeded.autoPaths.length > 0) record.autoSeed = seeded.autoPaths;
@@ -1850,26 +2175,26 @@ function addTrial(trials, instruments, instrumentName, options = {}) {
1850
2175
  return [...trials, record];
1851
2176
  }
1852
2177
  function cloneTrial(trials, id) {
1853
- const sourceIdx = trials.findIndex((w2) => w2.id === id);
2178
+ const sourceIdx = trials.findIndex((w3) => w3.id === id);
1854
2179
  const source = trials[sourceIdx];
1855
2180
  if (!source) return trials;
2181
+ const { history: _history, ...rest } = source;
1856
2182
  const clone = {
1857
- ...source,
2183
+ ...rest,
1858
2184
  id: newId(),
1859
2185
  config: structuredClone(source.config),
1860
2186
  state: structuredClone(source.state),
1861
- view: structuredClone(source.view),
1862
- undoStack: { past: [], future: [] }
2187
+ view: structuredClone(source.view)
1863
2188
  };
1864
2189
  return [...trials.slice(0, sourceIdx + 1), clone, ...trials.slice(sourceIdx + 1)];
1865
2190
  }
1866
2191
  function closeTrial(trials, id) {
1867
2192
  if (trials.length <= 1) return trials;
1868
- const next = trials.filter((w2) => w2.id !== id);
2193
+ const next = trials.filter((w3) => w3.id !== id);
1869
2194
  return next.length === trials.length ? trials : next;
1870
2195
  }
1871
2196
  function resetTrial(trials, id, instruments) {
1872
- const idx = trials.findIndex((w2) => w2.id === id);
2197
+ const idx = trials.findIndex((w3) => w3.id === id);
1873
2198
  const current = trials[idx];
1874
2199
  if (!current) return trials;
1875
2200
  const instrument = findInstrument(instruments, current.instrumentName);
@@ -1887,7 +2212,7 @@ function resetTrial(trials, id, instruments) {
1887
2212
  return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
1888
2213
  }
1889
2214
  function swapTrial(trials, instruments, id, instrumentName, options = {}) {
1890
- const idx = trials.findIndex((w2) => w2.id === id);
2215
+ const idx = trials.findIndex((w3) => w3.id === id);
1891
2216
  const current = trials[idx];
1892
2217
  if (!current) return trials;
1893
2218
  const [record] = addTrial([], instruments, instrumentName, options);
@@ -1895,10 +2220,10 @@ function swapTrial(trials, instruments, id, instrumentName, options = {}) {
1895
2220
  return [...trials.slice(0, idx), record, ...trials.slice(idx + 1)];
1896
2221
  }
1897
2222
  function reorderTrials(trials, ids) {
1898
- const byId = new Map(trials.map((w2) => [w2.id, w2]));
1899
- const named = ids.map((id) => byId.get(id)).filter((w2) => w2 !== void 0);
1900
- const seen = new Set(named.map((w2) => w2.id));
1901
- const rest = trials.filter((w2) => !seen.has(w2.id));
2223
+ const byId = new Map(trials.map((w3) => [w3.id, w3]));
2224
+ const named = ids.map((id) => byId.get(id)).filter((w3) => w3 !== void 0);
2225
+ const seen = new Set(named.map((w3) => w3.id));
2226
+ const rest = trials.filter((w3) => !seen.has(w3.id));
1902
2227
  return [...named, ...rest];
1903
2228
  }
1904
2229
  function findOvershoot(bound, items, tolerance = 1) {
@@ -1913,7 +2238,7 @@ function findOvershoot(bound, items, tolerance = 1) {
1913
2238
  return worst;
1914
2239
  }
1915
2240
  function tagAndClasses(el) {
1916
- return el.tagName.toLowerCase() + [...el.classList].map((c7) => `.${c7}`).join("");
2241
+ return el.tagName.toLowerCase() + [...el.classList].map((c6) => `.${c6}`).join("");
1917
2242
  }
1918
2243
  function describeElement(el) {
1919
2244
  if (el.classList.length > 0) return tagAndClasses(el);
@@ -1996,7 +2321,7 @@ function LabHeader({ addTrial: addTrial2 = true }) {
1996
2321
  ]
1997
2322
  }
1998
2323
  ) : /* @__PURE__ */ jsx(
1999
- p4,
2324
+ p6,
2000
2325
  {
2001
2326
  "aria-label": "Add trial",
2002
2327
  label: "Add trial\u2026",
@@ -2012,7 +2337,7 @@ function LabThemeSwitcher() {
2012
2337
  function LabPalette({ tools, contributions }) {
2013
2338
  const ctx = useLabChromeContext();
2014
2339
  const all = labContributions(tools, contributions);
2015
- return /* @__PURE__ */ jsx(PaletteRegion, { contributions: all.filter((c7) => c7.region === "palette"), ctx });
2340
+ return /* @__PURE__ */ jsx(PaletteRegion, { contributions: all.filter((c6) => c6.region === "palette"), ctx });
2016
2341
  }
2017
2342
  function LabRoot({ children, mode = "auto", className }) {
2018
2343
  const resolved = useResolvedColorMode(mode);
@@ -2031,13 +2356,13 @@ function locate(url) {
2031
2356
  function currentPage(path, pages) {
2032
2357
  const here = locate(path);
2033
2358
  const onDoc = (doc) => here.doc.endsWith(doc);
2034
- const routed = pages.findIndex((p4) => {
2035
- if (!p4.href.includes("#")) return false;
2036
- const page = locate(p4.href);
2359
+ const routed = pages.findIndex((p5) => {
2360
+ if (!p5.href.includes("#")) return false;
2361
+ const page = locate(p5.href);
2037
2362
  return onDoc(page.doc) && (here.route === page.route || here.route.startsWith(`${page.route}/`));
2038
2363
  });
2039
2364
  if (routed >= 0) return routed;
2040
- return pages.findIndex((p4) => !p4.href.includes("#") && onDoc(locate(p4.href).doc));
2365
+ return pages.findIndex((p5) => !p5.href.includes("#") && onDoc(locate(p5.href).doc));
2041
2366
  }
2042
2367
  function LabSwitcher({ title, pages, path, className }) {
2043
2368
  const [open, setOpen] = useState(false);
@@ -2126,6 +2451,91 @@ function LabShell({
2126
2451
  footer && /* @__PURE__ */ jsx("footer", { className: "lk-shell-footer", children: footer })
2127
2452
  ] });
2128
2453
  }
2454
+ var NONE = { below: [], above: [] };
2455
+ var LightboxLayersContext = createContext(NONE);
2456
+ function LightboxLayers({ below, above, children }) {
2457
+ const value = useMemo(
2458
+ () => ({ below: below ?? NONE.below, above: above ?? NONE.above }),
2459
+ [below, above]
2460
+ );
2461
+ return /* @__PURE__ */ jsx(LightboxLayersContext.Provider, { value, children });
2462
+ }
2463
+ function useLightboxLayers() {
2464
+ return useContext(LightboxLayersContext);
2465
+ }
2466
+ function LabSurface({ bodyRef, children }) {
2467
+ const outerSurface = useSurfaceOptional();
2468
+ const outerOver = useSurfaceCanvas("over");
2469
+ const outerUnder = useSurfaceCanvas("under");
2470
+ const [ownOver, setOwnOver] = useState(null);
2471
+ const [ownUnder, setOwnUnder] = useState(null);
2472
+ const [underLayer, setUnderLayer] = useState(null);
2473
+ const [overLayer, setOverLayer] = useState(null);
2474
+ const ownOverRef = useRef(null);
2475
+ const ownUnderRef = useRef(null);
2476
+ const bufferRef = useRef({ w: 0, h: 0 });
2477
+ const surfaceRef = useRef(null);
2478
+ const onFrame = useCallback((frame) => {
2479
+ const canvases2 = [ownUnderRef.current, ownOverRef.current].filter((c6) => c6 !== null);
2480
+ if (canvases2.length === 0) return;
2481
+ const w3 = Math.round(frame.size.width * frame.dpr);
2482
+ const h2 = Math.round(frame.size.height * frame.dpr);
2483
+ if (bufferRef.current.w !== w3 || bufferRef.current.h !== h2) {
2484
+ bufferRef.current = { w: w3, h: h2 };
2485
+ for (const c6 of canvases2) {
2486
+ c6.width = w3;
2487
+ c6.height = h2;
2488
+ c6.style.width = `${frame.size.width}px`;
2489
+ c6.style.height = `${frame.size.height}px`;
2490
+ }
2491
+ surfaceRef.current?.invalidateAll();
2492
+ return;
2493
+ }
2494
+ }, []);
2495
+ const ownSurface = useTiledSurface({ onFrame });
2496
+ useInsertionEffect(() => {
2497
+ surfaceRef.current = ownSurface;
2498
+ }, [ownSurface]);
2499
+ const attachOwnSurface = ownSurface.containerRef;
2500
+ const overLayerRef = useCallback(
2501
+ (el) => {
2502
+ setOverLayer(el);
2503
+ attachOwnSurface(el);
2504
+ },
2505
+ [attachOwnSurface]
2506
+ );
2507
+ const surface = outerSurface ?? ownSurface;
2508
+ const canvases = useMemo(
2509
+ () => outerSurface ? { over: outerOver, under: outerUnder } : { over: ownOver, under: ownUnder },
2510
+ [outerSurface, outerOver, outerUnder, ownOver, ownUnder]
2511
+ );
2512
+ const below = useMemo(() => [underLayer], [underLayer]);
2513
+ const above = useMemo(() => [overLayer], [overLayer]);
2514
+ const body = /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", ref: bodyRef, children: [
2515
+ outerSurface ? null : /* @__PURE__ */ jsx("div", { className: "lk-lab__layer lk-lab__layer--under", ref: setUnderLayer, children: /* @__PURE__ */ jsx(
2516
+ "canvas",
2517
+ {
2518
+ className: "lk-lab__surface",
2519
+ ref: (el) => {
2520
+ ownUnderRef.current = el;
2521
+ setOwnUnder(el);
2522
+ }
2523
+ }
2524
+ ) }),
2525
+ children,
2526
+ outerSurface ? null : /* @__PURE__ */ jsx("div", { className: "lk-lab__layer lk-lab__layer--over", ref: overLayerRef, children: /* @__PURE__ */ jsx(
2527
+ "canvas",
2528
+ {
2529
+ className: "lk-lab__surface",
2530
+ ref: (el) => {
2531
+ ownOverRef.current = el;
2532
+ setOwnOver(el);
2533
+ }
2534
+ }
2535
+ ) })
2536
+ ] });
2537
+ return /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: canvases, children: /* @__PURE__ */ jsx(LightboxLayers, { below, above, children: body }) }) });
2538
+ }
2129
2539
  var ZOOM_ID = "viewport.zoom";
2130
2540
  var NO_TOOLS = /* @__PURE__ */ new Map();
2131
2541
  var CHANNELS = { contextMenu: false, ingest: false };
@@ -2158,8 +2568,8 @@ function LabZoomControls() {
2158
2568
  cameras?.subscribe ?? noSubscribe2,
2159
2569
  () => cameras && focusedTrialId ? cameras.get(focusedTrialId) : null
2160
2570
  );
2161
- const zoom = useStore(storeCtx.store, (s3) => {
2162
- const record = s3.trials.find((t) => t.id === focusedTrialId);
2571
+ const zoom = useStore(storeCtx.store, (s4) => {
2572
+ const record = s4.trials.find((t) => t.id === focusedTrialId);
2163
2573
  return as2DView(record?.view)?.zoom ?? null;
2164
2574
  });
2165
2575
  const live = zoom !== null && camera !== null;
@@ -2201,7 +2611,7 @@ function LabZoomControls() {
2201
2611
  title: hint("Zoom out", "out"),
2202
2612
  disabled: !canOut,
2203
2613
  onClick: run("out"),
2204
- children: /* @__PURE__ */ jsx(X, { size: 16 })
2614
+ children: /* @__PURE__ */ jsx(Z, { size: 16 })
2205
2615
  }
2206
2616
  ),
2207
2617
  /* @__PURE__ */ jsx(
@@ -2229,6 +2639,311 @@ function LabZoomControls() {
2229
2639
  }
2230
2640
  var noSubscribe2 = () => () => {
2231
2641
  };
2642
+ var NEBULA_SLOTS = [
2643
+ { cx: "18%", cy: "30%", sx: "60%", sy: "80%", stop: "70%" },
2644
+ { cx: "78%", cy: "70%", sx: "55%", sy: "90%", stop: "65%" },
2645
+ { cx: "50%", cy: "50%", sx: "50%", sy: "70%", stop: "75%" },
2646
+ { cx: "12%", cy: "82%", sx: "50%", sy: "70%", stop: "70%" },
2647
+ { cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
2648
+ ];
2649
+ function buildNebula(list, theme, mode, density) {
2650
+ const ctx = { theme, resolved: resolveTheme(theme, { mode, density }) };
2651
+ const n6 = colorCount(list, ctx) ?? NEBULA_SLOTS.length;
2652
+ const colors = Array.from({ length: n6 }, (_2, i4) => colorAt(list, i4, ctx));
2653
+ const blobs = colors.map((c6, i4) => {
2654
+ const p5 = NEBULA_SLOTS[i4 % NEBULA_SLOTS.length];
2655
+ return `radial-gradient(ellipse ${p5.sx} ${p5.sy} at ${p5.cx} ${p5.cy}, color-mix(in srgb, ${c6} 22%, transparent), transparent ${p5.stop})`;
2656
+ });
2657
+ blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
2658
+ return blobs.join(", ");
2659
+ }
2660
+ function presentStorageKey(storageKey) {
2661
+ return `${storageKey}:present`;
2662
+ }
2663
+ var SEED_RECORD = valueRecord(null, "lk-present-seed");
2664
+ function seedPresentedTrial(store, records, { instruments, defaultInstrument }, seed) {
2665
+ const instrument = seed.instrument ?? defaultInstrument;
2666
+ const fingerprint = stableStringify({ ...seed, instrument });
2667
+ const trials = store.getState().trials;
2668
+ if (trials.length > 0 && records.get(SEED_RECORD) === fingerprint) return;
2669
+ const record = addTrial([], instruments, instrument, { config: seed.config })[0];
2670
+ if (!record) return;
2671
+ store.setState({ trials: [] });
2672
+ store.getState().addTrial({
2673
+ ...record,
2674
+ ..."state" in seed ? { state: seed.state } : {},
2675
+ ..."view" in seed ? { view: seed.view } : {}
2676
+ });
2677
+ records.set(SEED_RECORD, fingerprint);
2678
+ }
2679
+ function seedDefaultTrial(store, instruments, defaultInstrument) {
2680
+ if (store.getState().trials.length > 0) return;
2681
+ const record = addTrial([], instruments, defaultInstrument)[0];
2682
+ if (!record) return;
2683
+ store.getState().addTrial(record);
2684
+ }
2685
+ function openUnstoredLab(options, seed) {
2686
+ const { instruments, defaultInstrument, mode } = options;
2687
+ const store = createLabStore({ initialMode: mode ?? "auto", instruments });
2688
+ const records = createRecordCache({ storage: noneAdapter, prefix: "", writable: false });
2689
+ if (seed) seedPresentedTrial(store, records, options, seed);
2690
+ else seedDefaultTrial(store, instruments, defaultInstrument);
2691
+ return { store, records, close: () => records.close(), marks: /* @__PURE__ */ new Map() };
2692
+ }
2693
+ async function openStoredLab(options, storageKey, storage, seed) {
2694
+ const { instruments, defaultInstrument, mode } = options;
2695
+ const opened = await openLabStore({
2696
+ storageKey,
2697
+ storage: storage ?? await defaultStorage(),
2698
+ initialMode: mode ?? "auto",
2699
+ instruments
2700
+ });
2701
+ if (seed) seedPresentedTrial(opened.store, opened.records, options, seed);
2702
+ else seedDefaultTrial(opened.store, instruments, defaultInstrument);
2703
+ const marks = /* @__PURE__ */ new Map();
2704
+ await Promise.all(
2705
+ opened.store.getState().trials.map(async (trial) => {
2706
+ const kept = instruments.find((i4) => i4.name === trial.instrumentName)?.annotations?.storage;
2707
+ if (!kept) return;
2708
+ try {
2709
+ marks.set(trial.id, await kept.load());
2710
+ } catch (error) {
2711
+ console.warn(`[labkit] could not load the marks of trial "${trial.id}"`, error);
2712
+ marks.set(trial.id, null);
2713
+ }
2714
+ })
2715
+ );
2716
+ return { ...opened, marks };
2717
+ }
2718
+ function useFocusPick(labBody, store, pick) {
2719
+ useEffect(() => {
2720
+ if (!labBody) return;
2721
+ const pickFrom = (node) => {
2722
+ const owned = new Set(store.getState().trials.map((t) => t.id));
2723
+ const nearest = (from) => from?.closest(".lk-trial[data-trial-id]") ?? null;
2724
+ let trial = typeof node?.closest === "function" ? nearest(node) : null;
2725
+ while (trial && !owned.has(trial.dataset.trialId ?? "")) trial = nearest(trial.parentElement);
2726
+ if (trial?.dataset.trialId) pick(trial.dataset.trialId);
2727
+ };
2728
+ const onInput = (event) => pickFrom(event.target);
2729
+ let pending;
2730
+ const doc = labBody.ownerDocument;
2731
+ const win = doc.defaultView;
2732
+ const onBlur = () => {
2733
+ clearTimeout(pending);
2734
+ pending = setTimeout(() => pickFrom(doc.activeElement), 0);
2735
+ };
2736
+ labBody.addEventListener("pointerdown", onInput, true);
2737
+ labBody.addEventListener("focusin", onInput, true);
2738
+ win?.addEventListener("blur", onBlur);
2739
+ return () => {
2740
+ clearTimeout(pending);
2741
+ labBody.removeEventListener("pointerdown", onInput, true);
2742
+ labBody.removeEventListener("focusin", onInput, true);
2743
+ win?.removeEventListener("blur", onBlur);
2744
+ };
2745
+ }, [labBody, store, pick]);
2746
+ }
2747
+
2748
+ // src/lightbox/modal.ts
2749
+ var OWN_DOUBLE_CLICK = [
2750
+ "button",
2751
+ "a[href]",
2752
+ "input",
2753
+ "select",
2754
+ "textarea",
2755
+ "label",
2756
+ "summary",
2757
+ '[contenteditable]:not([contenteditable="false"])',
2758
+ '[role="button"]',
2759
+ '[role="checkbox"]',
2760
+ '[role="combobox"]',
2761
+ '[role="link"]',
2762
+ '[role="menuitem"]',
2763
+ '[role="option"]',
2764
+ '[role="radio"]',
2765
+ '[role="separator"]',
2766
+ '[role="slider"]',
2767
+ '[role="spinbutton"]',
2768
+ '[role="switch"]',
2769
+ '[role="tab"]',
2770
+ '[role="textbox"]',
2771
+ "[data-lk-lightbox-ignore]"
2772
+ ].join(",");
2773
+ function opensLightbox(event, root) {
2774
+ if (event.defaultPrevented) return false;
2775
+ const target = event.target;
2776
+ if (!(target instanceof Element)) return true;
2777
+ const owner = target.closest(OWN_DOUBLE_CLICK);
2778
+ return owner === null || !root.contains(owner);
2779
+ }
2780
+ function inertOutside(keep) {
2781
+ const path = /* @__PURE__ */ new Set();
2782
+ for (const el of keep) {
2783
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
2784
+ path.add(node);
2785
+ }
2786
+ }
2787
+ const marked = [];
2788
+ for (const node of path) {
2789
+ const parent = node.parentElement;
2790
+ if (!parent) continue;
2791
+ for (const sibling of Array.from(parent.children)) {
2792
+ if (path.has(sibling) || sibling.hasAttribute("inert")) continue;
2793
+ sibling.setAttribute("inert", "");
2794
+ marked.push(sibling);
2795
+ }
2796
+ }
2797
+ return () => {
2798
+ for (const sibling of marked) sibling.removeAttribute("inert");
2799
+ };
2800
+ }
2801
+ function liftStack(stack, self) {
2802
+ if (typeof self.showPopover !== "function") return () => {
2803
+ };
2804
+ stack.forEach((el, i4) => {
2805
+ el.setAttribute("popover", "manual");
2806
+ if (el !== self) el.setAttribute("data-lk-lightbox-layer", "");
2807
+ if (i4 === 0) el.setAttribute("data-lk-lightbox-scrim", "");
2808
+ el.showPopover();
2809
+ });
2810
+ return () => {
2811
+ for (const el of [...stack].reverse()) {
2812
+ if (el.matches(":popover-open")) el.hidePopover();
2813
+ el.removeAttribute("popover");
2814
+ el.removeAttribute("data-lk-lightbox-layer");
2815
+ el.removeAttribute("data-lk-lightbox-scrim");
2816
+ }
2817
+ };
2818
+ }
2819
+ function dialogProps(label) {
2820
+ return {
2821
+ role: "dialog",
2822
+ "aria-modal": true,
2823
+ "aria-label": label,
2824
+ "data-wzl-portal-host": ""
2825
+ };
2826
+ }
2827
+ function Lightbox({
2828
+ children,
2829
+ label = "Expanded view",
2830
+ disabled = false,
2831
+ expandOnDoubleClick = false,
2832
+ expanded: expandedProp,
2833
+ onExpandedChange,
2834
+ className
2835
+ }) {
2836
+ const [own, setOwn] = useState(false);
2837
+ const expanded = !disabled && (expandedProp ?? own);
2838
+ const onChangeRef = useLatest(onExpandedChange);
2839
+ const expandedRef = useLatest(expanded);
2840
+ const rootRef = useRef(null);
2841
+ const closeRef = useRef(null);
2842
+ const [claims, setClaims] = useState(0);
2843
+ const layers = useLightboxLayers();
2844
+ const surface = useSurfaceOptional();
2845
+ const set = useCallback(
2846
+ (next) => {
2847
+ if (next === expandedRef.current) return;
2848
+ setOwn(next);
2849
+ onChangeRef.current?.(next);
2850
+ },
2851
+ [expandedRef, onChangeRef]
2852
+ );
2853
+ const ctx = useMemo(
2854
+ () => disabled ? null : {
2855
+ expanded,
2856
+ open: () => set(true),
2857
+ close: () => set(false),
2858
+ toggle: () => set(!expandedRef.current),
2859
+ claimControl: () => {
2860
+ setClaims((n6) => n6 + 1);
2861
+ return () => setClaims((n6) => n6 - 1);
2862
+ }
2863
+ },
2864
+ [disabled, expanded, set, expandedRef]
2865
+ );
2866
+ useLayoutEffect(() => {
2867
+ const el = rootRef.current;
2868
+ if (!expanded || !el) return;
2869
+ const returnTo = document.activeElement;
2870
+ const stack = [...layers.below, el, ...layers.above].filter(
2871
+ (layer) => layer !== null
2872
+ );
2873
+ const unlift = liftStack(stack, el);
2874
+ const release = inertOutside(stack);
2875
+ surface?.scope(el);
2876
+ closeRef.current?.focus({ preventScroll: true });
2877
+ return () => {
2878
+ surface?.scope(null);
2879
+ release();
2880
+ unlift();
2881
+ if (returnTo?.isConnected) returnTo.focus({ preventScroll: true });
2882
+ };
2883
+ }, [expanded, layers, surface]);
2884
+ useEffect(() => {
2885
+ if (!expanded) return;
2886
+ const onKey = (e) => {
2887
+ if (e.key !== "Escape" || e.defaultPrevented) return;
2888
+ e.preventDefault();
2889
+ set(false);
2890
+ };
2891
+ document.addEventListener("keydown", onKey);
2892
+ return () => document.removeEventListener("keydown", onKey);
2893
+ }, [expanded, set]);
2894
+ const onDoubleClick = (e) => {
2895
+ if (disabled || !expandOnDoubleClick || !opensLightbox(e, e.currentTarget)) return;
2896
+ e.preventDefault();
2897
+ set(!expanded);
2898
+ };
2899
+ const onClick = (e) => {
2900
+ if (expanded && e.target === e.currentTarget) set(false);
2901
+ };
2902
+ const classes = ["lk-lightbox"];
2903
+ if (expanded) classes.push("lk-lightbox--expanded");
2904
+ if (className) classes.push(className);
2905
+ let control = null;
2906
+ if (expanded) {
2907
+ control = /* @__PURE__ */ jsx(
2908
+ "button",
2909
+ {
2910
+ ref: closeRef,
2911
+ type: "button",
2912
+ className: "lk-lightbox__button lk-lightbox__close",
2913
+ "aria-label": "Close expanded view",
2914
+ title: "Close (Esc)",
2915
+ onClick: () => set(false),
2916
+ children: /* @__PURE__ */ jsx(V, { size: 16 })
2917
+ }
2918
+ );
2919
+ } else if (!disabled && claims === 0) {
2920
+ control = /* @__PURE__ */ jsx(
2921
+ "button",
2922
+ {
2923
+ type: "button",
2924
+ className: "lk-lightbox__button lk-lightbox__open",
2925
+ "aria-label": "Expand",
2926
+ title: "Expand",
2927
+ onClick: () => set(true),
2928
+ children: /* @__PURE__ */ jsx(de, { size: 16 })
2929
+ }
2930
+ );
2931
+ }
2932
+ return /* @__PURE__ */ jsx(LightboxContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
2933
+ "div",
2934
+ {
2935
+ ref: rootRef,
2936
+ className: classes.join(" "),
2937
+ ...expanded ? dialogProps(label) : null,
2938
+ onDoubleClick,
2939
+ onClick,
2940
+ children: [
2941
+ /* @__PURE__ */ jsx("div", { className: "lk-lightbox__frame", children }),
2942
+ control
2943
+ ]
2944
+ }
2945
+ ) });
2946
+ }
2232
2947
  var ZONE_ID = asNodeId("lk-workspace");
2233
2948
  var STRATEGIES = { grid: gridStrategy, floating: floatingStrategy };
2234
2949
  var FLOAT_ZONE_ID = asNodeId("lk-workspace-floating");
@@ -2253,11 +2968,11 @@ function createWorkspaceStore(grid) {
2253
2968
  return store;
2254
2969
  }
2255
2970
  function extentOf(store, id) {
2256
- const p4 = store.getNode(id)?.membership?.placement;
2257
- if (!p4) return null;
2971
+ const p5 = store.getNode(id)?.membership?.placement;
2972
+ if (!p5) return null;
2258
2973
  const out = {};
2259
- if (p4.size) out.size = p4.size;
2260
- if (p4.span) out.span = p4.span;
2974
+ if (p5.size) out.size = p5.size;
2975
+ if (p5.span) out.span = p5.span;
2261
2976
  return Object.keys(out).length > 0 ? out : null;
2262
2977
  }
2263
2978
  function PanelFrame({ panel, floating = false }) {
@@ -2275,14 +2990,16 @@ function Workspace({
2275
2990
  onLayoutChange,
2276
2991
  gap = 12,
2277
2992
  padding = 0,
2278
- viewport
2993
+ viewport,
2994
+ lightbox = true,
2995
+ expandOnDoubleClick = false
2279
2996
  }) {
2280
2997
  const items = Children.toArray(children);
2281
- const tilePanels = useMemo(() => (panels ?? []).filter((p4) => p4.as === "tile"), [panels]);
2282
- const floatPanels = useMemo(() => (panels ?? []).filter((p4) => p4.as === "floating"), [panels]);
2998
+ const tilePanels = useMemo(() => (panels ?? []).filter((p5) => p5.as === "tile"), [panels]);
2999
+ const floatPanels = useMemo(() => (panels ?? []).filter((p5) => p5.as === "floating"), [panels]);
2283
3000
  const idKey = JSON.stringify([
2284
3001
  ...items.map((_2, i4) => ids?.[i4] ?? `lk-ws-${i4}`),
2285
- ...tilePanels.map((p4) => `lk-panel-${p4.key}`)
3002
+ ...tilePanels.map((p5) => `lk-panel-${p5.key}`)
2286
3003
  ]);
2287
3004
  const nodeIds = useMemo(() => JSON.parse(idKey).map(asNodeId), [idKey]);
2288
3005
  const layoutRef = useLatest(layout);
@@ -2332,9 +3049,11 @@ function Workspace({
2332
3049
  [onReorder]
2333
3050
  );
2334
3051
  const chrome = useMemo(() => {
3052
+ const lightboxFor = typeof lightbox === "function" ? lightbox : () => lightbox;
3053
+ const doubleClickFor = typeof expandOnDoubleClick === "function" ? expandOnDoubleClick : () => expandOnDoubleClick;
2335
3054
  const byId = new Map(nodeIds.map((id, i4) => [id, items[i4]]));
2336
3055
  const panelByNode = new Map(
2337
- tilePanels.map((p4) => [`lk-panel-${p4.key}`, p4])
3056
+ tilePanels.map((p5) => [`lk-panel-${p5.key}`, p5])
2338
3057
  );
2339
3058
  return {
2340
3059
  [PANEL_KIND]: ({ node }) => {
@@ -2343,16 +3062,29 @@ function Workspace({
2343
3062
  return /* @__PURE__ */ jsx(PanelFrame, { panel });
2344
3063
  },
2345
3064
  [KIND]: ({ node }) => {
2346
- const content = byId.get(node.id) ?? null;
2347
- if (!reorderable) return content;
3065
+ const id = String(node.id);
3066
+ const tile = (
3067
+ // `disabled` rather than leaving the wrapper out, so turning the
3068
+ // lightbox off cannot remount the tile.
3069
+ /* @__PURE__ */ jsx(
3070
+ Lightbox,
3071
+ {
3072
+ className: "lk-trial-tile",
3073
+ disabled: !lightboxFor(id),
3074
+ expandOnDoubleClick: doubleClickFor(id),
3075
+ children: byId.get(node.id) ?? null
3076
+ }
3077
+ )
3078
+ );
3079
+ if (!reorderable) return tile;
2348
3080
  return (
2349
3081
  // No grip strip: the trial's own title bar is the drag surface.
2350
- /* @__PURE__ */ jsx(TrialDragContext.Provider, { value: { nodeId: node.id }, children: /* @__PURE__ */ jsx("div", { className: "lk-trial-tile", children: content }) })
3082
+ /* @__PURE__ */ jsx(TrialDragContext.Provider, { value: { nodeId: node.id }, children: tile })
2351
3083
  );
2352
3084
  }
2353
3085
  };
2354
- }, [nodeIds, items, reorderable, tilePanels]);
2355
- const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p4) => /* @__PURE__ */ jsx(PanelFrame, { panel: p4, floating: true }, p4.key)) });
3086
+ }, [nodeIds, items, reorderable, tilePanels, lightbox, expandOnDoubleClick]);
3087
+ const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p5) => /* @__PURE__ */ jsx(PanelFrame, { panel: p5, floating: true }, p5.key)) });
2356
3088
  const grid = /* @__PURE__ */ jsx(
2357
3089
  Container,
2358
3090
  {
@@ -2374,42 +3106,6 @@ function Workspace({
2374
3106
  ] })
2375
3107
  ) : reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
2376
3108
  }
2377
- function seedDefaultTrial(store, instruments, defaultInstrument) {
2378
- if (store.getState().trials.length > 0) return;
2379
- const record = addTrial([], instruments, defaultInstrument)[0];
2380
- if (!record) return;
2381
- const { undoStack: _undoStack, ...rest } = record;
2382
- store.getState().addTrial(rest);
2383
- }
2384
- function openUnstoredLab({ instruments, defaultInstrument, mode }) {
2385
- const store = createLabStore({ initialMode: mode ?? "auto", instruments });
2386
- seedDefaultTrial(store, instruments, defaultInstrument);
2387
- const records = createRecordCache({ storage: noneAdapter, prefix: "", writable: false });
2388
- return { store, records, close: () => records.close(), marks: /* @__PURE__ */ new Map() };
2389
- }
2390
- async function openStoredLab({ instruments, defaultInstrument, mode }, storageKey, storage) {
2391
- const opened = await openLabStore({
2392
- storageKey,
2393
- storage: storage ?? await defaultStorage(),
2394
- initialMode: mode ?? "auto",
2395
- instruments
2396
- });
2397
- seedDefaultTrial(opened.store, instruments, defaultInstrument);
2398
- const marks = /* @__PURE__ */ new Map();
2399
- await Promise.all(
2400
- opened.store.getState().trials.map(async (trial) => {
2401
- const kept = instruments.find((i4) => i4.name === trial.instrumentName)?.annotations?.storage;
2402
- if (!kept) return;
2403
- try {
2404
- marks.set(trial.id, await kept.load());
2405
- } catch (error) {
2406
- console.warn(`[labkit] could not load the marks of trial "${trial.id}"`, error);
2407
- marks.set(trial.id, null);
2408
- }
2409
- })
2410
- );
2411
- return { ...opened, marks };
2412
- }
2413
3109
  function LabFallback({
2414
3110
  title,
2415
3111
  mode,
@@ -2438,24 +3134,6 @@ function LabFallback({
2438
3134
  }
2439
3135
  );
2440
3136
  }
2441
- var NEBULA_SLOTS = [
2442
- { cx: "18%", cy: "30%", sx: "60%", sy: "80%", stop: "70%" },
2443
- { cx: "78%", cy: "70%", sx: "55%", sy: "90%", stop: "65%" },
2444
- { cx: "50%", cy: "50%", sx: "50%", sy: "70%", stop: "75%" },
2445
- { cx: "12%", cy: "82%", sx: "50%", sy: "70%", stop: "70%" },
2446
- { cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
2447
- ];
2448
- function buildNebula(list, theme, mode, density) {
2449
- const ctx = { theme, resolved: resolveTheme(theme, { mode, density }) };
2450
- const n6 = colorCount(list, ctx) ?? NEBULA_SLOTS.length;
2451
- const colors = Array.from({ length: n6 }, (_2, i4) => colorAt(list, i4, ctx));
2452
- const blobs = colors.map((c7, i4) => {
2453
- const p4 = NEBULA_SLOTS[i4 % NEBULA_SLOTS.length];
2454
- return `radial-gradient(ellipse ${p4.sx} ${p4.sy} at ${p4.cx} ${p4.cy}, color-mix(in srgb, ${c7} 22%, transparent), transparent ${p4.stop})`;
2455
- });
2456
- blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
2457
- return blobs.join(", ");
2458
- }
2459
3137
  function LabPanes({
2460
3138
  contributions,
2461
3139
  children
@@ -2471,7 +3149,7 @@ function LabPanes({
2471
3149
  void 0,
2472
3150
  { scope: "lab" }
2473
3151
  );
2474
- const main = contributions.some((c7) => c7.region === "aside") ? /* @__PURE__ */ jsx(
3152
+ const main = contributions.some((c6) => c6.region === "aside") ? /* @__PURE__ */ jsx(
2475
3153
  Split,
2476
3154
  {
2477
3155
  className: "lk-lab__panes",
@@ -2481,15 +3159,15 @@ function LabPanes({
2481
3159
  label: "Lab aside",
2482
3160
  defaultWidth: 320,
2483
3161
  width: asideWidth,
2484
- onWidthChange: (w2) => {
2485
- setAsideWidth(w2);
3162
+ onWidthChange: (w3) => {
3163
+ setAsideWidth(w3);
2486
3164
  surface?.invalidateRects();
2487
3165
  },
2488
3166
  sidebar: /* @__PURE__ */ jsx(LabAsideRegion, { contributions }),
2489
3167
  children
2490
3168
  }
2491
3169
  ) : children;
2492
- if (!contributions.some((c7) => c7.region === "sidebar")) return main;
3170
+ if (!contributions.some((c6) => c6.region === "sidebar")) return main;
2493
3171
  return /* @__PURE__ */ jsx(
2494
3172
  Split,
2495
3173
  {
@@ -2499,8 +3177,8 @@ function LabPanes({
2499
3177
  label: "Lab sidebar",
2500
3178
  defaultWidth: 260,
2501
3179
  width: sidebarWidth,
2502
- onWidthChange: (w2) => {
2503
- setSidebarWidth(w2);
3180
+ onWidthChange: (w3) => {
3181
+ setSidebarWidth(w3);
2504
3182
  surface?.invalidateRects();
2505
3183
  },
2506
3184
  sidebar: /* @__PURE__ */ jsx(LabSidebarRegion, { contributions }),
@@ -2512,13 +3190,21 @@ function Lab(props) {
2512
3190
  if (process.env.NODE_ENV !== "production" && props.instruments.length === 0) {
2513
3191
  throw new Error("[labkit] <Lab> requires a non-empty `instruments` array");
2514
3192
  }
2515
- const { storageKey, storage } = props;
2516
- useWarnIgnoredChange("<Lab>", { storageKey, storage });
2517
- const opened = useOpenOnce(
2518
- () => storageKey === void 0 ? openUnstoredLab(props) : openStoredLab(props, storageKey, storage)
2519
- );
3193
+ const { storageKey, storage, present } = props;
3194
+ useWarnIgnoredChange("<Lab>", { storageKey, storage, present });
3195
+ const [startsPresenting] = useState(() => present === true || hasPresentParam());
3196
+ if (process.env.NODE_ENV !== "production" && props.seed && !startsPresenting) {
3197
+ warnSeedIgnored();
3198
+ }
3199
+ const opened = useOpenOnce(() => {
3200
+ const seed = startsPresenting ? props.seed ?? {} : void 0;
3201
+ if (storageKey === void 0) return openUnstoredLab(props, seed);
3202
+ const key = startsPresenting ? presentStorageKey(storageKey) : storageKey;
3203
+ return openStoredLab(props, key, storage, seed);
3204
+ });
2520
3205
  if (!opened) {
2521
3206
  if (props.fallback !== void 0) return props.fallback;
3207
+ if (startsPresenting) return null;
2522
3208
  return /* @__PURE__ */ jsx(
2523
3209
  LabFallback,
2524
3210
  {
@@ -2531,7 +3217,13 @@ function Lab(props) {
2531
3217
  }
2532
3218
  );
2533
3219
  }
2534
- return /* @__PURE__ */ jsx(LabRuntime, { ...props, opened });
3220
+ return /* @__PURE__ */ jsx(LabRuntime, { ...props, opened, startsPresenting });
3221
+ }
3222
+ var seedWarned = false;
3223
+ function warnSeedIgnored() {
3224
+ if (seedWarned) return;
3225
+ seedWarned = true;
3226
+ console.warn("[labkit] <Lab> reads `seed` only when it starts presenting; it is ignored here");
2535
3227
  }
2536
3228
  function LabRuntime({
2537
3229
  instruments,
@@ -2546,100 +3238,58 @@ function LabRuntime({
2546
3238
  chrome,
2547
3239
  labChrome,
2548
3240
  suppress,
3241
+ expandOnDoubleClick = false,
2549
3242
  addTrial: addTrial2,
2550
3243
  zoom = true,
2551
3244
  tools,
2552
3245
  configRules,
2553
3246
  controls,
2554
3247
  children,
2555
- opened
3248
+ opened,
3249
+ startsPresenting
2556
3250
  }) {
2557
3251
  const { store } = opened;
2558
3252
  useLayoutEffect(() => {
2559
3253
  store.getState().setInstruments(instruments);
2560
3254
  }, [instruments, store]);
2561
- const trials = useStore(store, (s3) => s3.trials);
3255
+ const trials = useStore(store, (s4) => s4.trials);
2562
3256
  const [cameras] = useState(createCameraRegistry);
3257
+ const [clocks] = useState(createClockRegistry);
3258
+ useClockLoop(clocks);
2563
3259
  const [focusPick, setFocusPick] = useState(null);
2564
3260
  const focusedTrialId = trials.some((t) => t.id === focusPick) ? focusPick : trials[0]?.id ?? null;
2565
- const savedSnapshots = useStore(store, (s3) => s3.savedSnapshots);
2566
- const modeValue = useStore(store, (s3) => s3.mode);
2567
- const layout = useStore(store, (s3) => s3.layout);
2568
- const undockedPanels = useStore(store, (s3) => s3.undockedPanels);
3261
+ const [presentedBy, setPresentedBy] = useState(
3262
+ startsPresenting ? "mount" : null
3263
+ );
3264
+ const presenting = presentedBy !== null;
3265
+ const presentation = useMemo(
3266
+ () => ({
3267
+ active: presenting,
3268
+ enter: () => setPresentedBy((by) => by ?? "enter"),
3269
+ exit: () => setPresentedBy(null),
3270
+ trialId: presenting ? focusedTrialId : null
3271
+ }),
3272
+ [presenting, focusedTrialId]
3273
+ );
3274
+ const savedSnapshots = useStore(store, (s4) => s4.savedSnapshots);
3275
+ const modeValue = useStore(store, (s4) => s4.mode);
3276
+ const layout = useStore(store, (s4) => s4.layout);
3277
+ const undockedPanels = useStore(store, (s4) => s4.undockedPanels);
2569
3278
  const panelHostsRef = useRef(null);
2570
3279
  if (panelHostsRef.current === null) panelHostsRef.current = createPanelHostRegistry();
2571
- const outerSurface = useSurfaceOptional();
2572
- const outerOver = useSurfaceCanvas("over");
2573
- const outerUnder = useSurfaceCanvas("under");
2574
- const [ownOver, setOwnOver] = useState(null);
2575
- const [ownUnder, setOwnUnder] = useState(null);
2576
- const ownOverRef = useRef(null);
2577
- const ownUnderRef = useRef(null);
2578
- const bufferRef = useRef({ w: 0, h: 0 });
2579
- const surfaceRef = useRef(null);
2580
- const onFrame = useCallback((frame) => {
2581
- const canvases = [ownUnderRef.current, ownOverRef.current].filter((c7) => c7 !== null);
2582
- if (canvases.length === 0) return;
2583
- const w2 = Math.round(frame.size.width * frame.dpr);
2584
- const h2 = Math.round(frame.size.height * frame.dpr);
2585
- if (bufferRef.current.w !== w2 || bufferRef.current.h !== h2) {
2586
- bufferRef.current = { w: w2, h: h2 };
2587
- for (const c7 of canvases) {
2588
- c7.width = w2;
2589
- c7.height = h2;
2590
- c7.style.width = `${frame.size.width}px`;
2591
- c7.style.height = `${frame.size.height}px`;
2592
- }
2593
- surfaceRef.current?.invalidateAll();
2594
- return;
2595
- }
2596
- }, []);
2597
- const ownSurface = useTiledSurface({ onFrame });
2598
- useInsertionEffect(() => {
2599
- surfaceRef.current = ownSurface;
2600
- }, [ownSurface]);
2601
3280
  const [labBody, setLabBody] = useState(null);
2602
- const attachOwnSurface = ownSurface.containerRef;
2603
- const labBodyRef = useCallback(
2604
- (el) => {
2605
- setLabBody(el);
2606
- if (!outerSurface) attachOwnSurface(el);
2607
- },
2608
- [outerSurface, attachOwnSurface]
2609
- );
2610
3281
  useLabFitWarning(labBody);
3282
+ useFocusPick(labBody, store, setFocusPick);
2611
3283
  useEffect(() => {
2612
- if (!labBody) return;
2613
- const pick = (node) => {
2614
- const owned = new Set(store.getState().trials.map((t) => t.id));
2615
- const nearest = (from) => from?.closest(".lk-trial[data-trial-id]") ?? null;
2616
- let trial = typeof node?.closest === "function" ? nearest(node) : null;
2617
- while (trial && !owned.has(trial.dataset.trialId ?? "")) trial = nearest(trial.parentElement);
2618
- if (trial?.dataset.trialId) setFocusPick(trial.dataset.trialId);
2619
- };
2620
- const onInput = (event) => pick(event.target);
2621
- let pending;
3284
+ if (presentedBy !== "enter" || !labBody) return;
2622
3285
  const doc = labBody.ownerDocument;
2623
- const win = doc.defaultView;
2624
- const onBlur = () => {
2625
- clearTimeout(pending);
2626
- pending = setTimeout(() => pick(doc.activeElement), 0);
2627
- };
2628
- labBody.addEventListener("pointerdown", onInput, true);
2629
- labBody.addEventListener("focusin", onInput, true);
2630
- win?.addEventListener("blur", onBlur);
2631
- return () => {
2632
- clearTimeout(pending);
2633
- labBody.removeEventListener("pointerdown", onInput, true);
2634
- labBody.removeEventListener("focusin", onInput, true);
2635
- win?.removeEventListener("blur", onBlur);
3286
+ const onKeyDown = (event) => {
3287
+ if (event.key !== "Escape" || event.defaultPrevented) return;
3288
+ setPresentedBy(null);
2636
3289
  };
2637
- }, [labBody, store]);
2638
- const surface = outerSurface ?? ownSurface;
2639
- const surfaceCanvases = useMemo(
2640
- () => outerSurface ? { over: outerOver, under: outerUnder } : { over: ownOver, under: ownUnder },
2641
- [outerSurface, outerOver, outerUnder, ownOver, ownUnder]
2642
- );
3290
+ doc.addEventListener("keydown", onKeyDown);
3291
+ return () => doc.removeEventListener("keydown", onKeyDown);
3292
+ }, [presentedBy, labBody]);
2643
3293
  const workspacePanels = useMemo(
2644
3294
  () => Object.entries(undockedPanels).map(([key, panel]) => ({
2645
3295
  key,
@@ -2654,8 +3304,8 @@ function LabRuntime({
2654
3304
  () => labContributions([...annotationTools, ...tools ?? []], labChrome),
2655
3305
  [annotationTools, tools, labChrome]
2656
3306
  );
2657
- const hasFooterChrome = labChromeAll.some((c7) => c7.region === "footer");
2658
- const hasPaneChrome = labChromeAll.some((c7) => c7.region === "sidebar" || c7.region === "aside");
3307
+ const hasFooterChrome = labChromeAll.some((c6) => c6.region === "footer");
3308
+ const hasPaneChrome = labChromeAll.some((c6) => c6.region === "sidebar" || c6.region === "aside");
2659
3309
  useEffect(() => {
2660
3310
  if (mode && mode !== store.getState().mode) {
2661
3311
  store.getState().setMode(mode);
@@ -2663,14 +3313,14 @@ function LabRuntime({
2663
3313
  }, [mode, store]);
2664
3314
  const contextValue = useMemo(() => {
2665
3315
  const replaceTrials = (next) => {
2666
- const currentById = new Map(store.getState().trials.map((w2) => [w2.id, w2]));
2667
- const merged = next.map((w2) => currentById.get(w2.id) ?? w2);
3316
+ const currentById = new Map(store.getState().trials.map((w3) => [w3.id, w3]));
3317
+ const merged = next.map((w3) => currentById.get(w3.id) ?? w3);
2668
3318
  store.setState({ trials: merged });
2669
3319
  };
2670
3320
  const replaceAndFocusAdded = (next) => {
2671
- const before = new Set(store.getState().trials.map((w2) => w2.id));
3321
+ const before = new Set(store.getState().trials.map((w3) => w3.id));
2672
3322
  replaceTrials(next);
2673
- const added = next.find((w2) => !before.has(w2.id));
3323
+ const added = next.find((w3) => !before.has(w3.id));
2674
3324
  if (added) setFocusPick(added.id);
2675
3325
  };
2676
3326
  return {
@@ -2695,7 +3345,7 @@ function LabRuntime({
2695
3345
  const current = store.getState();
2696
3346
  const next = swapTrial(current.trials, instruments, id, instrumentName, options);
2697
3347
  if (next === current.trials) return;
2698
- const record = next[current.trials.findIndex((w2) => w2.id === id)];
3348
+ const record = next[current.trials.findIndex((w3) => w3.id === id)];
2699
3349
  const { [id]: extent, ...layoutRest } = current.layout;
2700
3350
  if (extent) current.setLayout({ ...layoutRest, [record.id]: extent });
2701
3351
  replaceTrials(next);
@@ -2705,17 +3355,18 @@ function LabRuntime({
2705
3355
  focusTrial: (id) => setFocusPick(id),
2706
3356
  resetTrial: (id) => {
2707
3357
  const next = resetTrial(store.getState().trials, id, instruments);
2708
- const record = next.find((w2) => w2.id === id);
3358
+ const record = next.find((w3) => w3.id === id);
2709
3359
  if (!record) return;
2710
- store.setState((s3) => ({
2711
- trials: s3.trials.map(
2712
- (w2) => w2.id === id ? { ...w2, config: record.config, state: record.state, view: record.view } : w2
3360
+ store.setState((s4) => ({
3361
+ trials: s4.trials.map(
3362
+ (w3) => w3.id === id ? { ...w3, config: record.config, state: record.state, view: record.view } : w3
2713
3363
  )
2714
3364
  }));
3365
+ clocks.get(id)?.reset();
2715
3366
  },
2716
3367
  savedSnapshots,
2717
3368
  saveSnapshot: (trialId, name) => {
2718
- const taken = store.getState().savedSnapshots.filter((s3) => s3.trialId === trialId).length;
3369
+ const taken = store.getState().savedSnapshots.filter((s4) => s4.trialId === trialId).length;
2719
3370
  store.getState().saveSnapshot(trialId, name ?? `Snapshot ${taken + 1}`);
2720
3371
  },
2721
3372
  loadSnapshot: (trialId, snapshotId) => {
@@ -2732,6 +3383,7 @@ function LabRuntime({
2732
3383
  controls
2733
3384
  };
2734
3385
  }, [
3386
+ clocks,
2735
3387
  instruments,
2736
3388
  trials,
2737
3389
  focusedTrialId,
@@ -2753,21 +3405,25 @@ function LabRuntime({
2753
3405
  Workspace,
2754
3406
  {
2755
3407
  panels: workspacePanels,
2756
- ids: trials.map((w2) => w2.id),
3408
+ ids: trials.map((w3) => w3.id),
2757
3409
  resizable: true,
2758
3410
  reorderable: true,
2759
3411
  onReorder: (ids) => contextValue.reorderTrials(ids),
2760
3412
  layout,
2761
3413
  onLayoutChange: (next) => store.getState().setLayout(next),
2762
- children: trials.map((w2) => /* @__PURE__ */ jsx(Trial, { id: w2.id, chrome, suppress }, w2.id))
3414
+ expandOnDoubleClick: typeof expandOnDoubleClick === "function" ? (id) => {
3415
+ const trial = trials.find((t) => t.id === id);
3416
+ return trial ? expandOnDoubleClick(trial) : false;
3417
+ } : expandOnDoubleClick,
3418
+ children: trials.map((w3) => /* @__PURE__ */ jsx(Trial, { id: w3.id, chrome, suppress }, w3.id))
2763
3419
  }
2764
3420
  );
2765
- return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(PersistenceContext.Provider, { value: opened.records, children: /* @__PURE__ */ jsx(AnnotationPreloadContext.Provider, { value: opened.marks, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(CameraRegistryContext.Provider, { value: cameras, children: /* @__PURE__ */ jsx(
3421
+ return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(PersistenceContext.Provider, { value: opened.records, children: /* @__PURE__ */ jsx(AnnotationPreloadContext.Provider, { value: opened.marks, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(PresentationContext.Provider, { value: presentation, children: /* @__PURE__ */ jsx(CameraRegistryContext.Provider, { value: cameras, children: /* @__PURE__ */ jsx(ClockRegistryContext.Provider, { value: clocks, children: /* @__PURE__ */ jsx(
2766
3422
  ThemeProvider,
2767
3423
  {
2768
3424
  theme,
2769
3425
  selection: { mode: resolvedMode, density: density ?? "comfortable" },
2770
- className: "lk-lab",
3426
+ className: presenting ? "lk-lab lk-lab--present" : "lk-lab",
2771
3427
  style: backdropStyle,
2772
3428
  children: /* @__PURE__ */ jsx(
2773
3429
  LabShell,
@@ -2782,53 +3438,22 @@ function LabRuntime({
2782
3438
  ] }) : footer,
2783
3439
  header: /* @__PURE__ */ jsxs(Fragment, { children: [
2784
3440
  /* @__PURE__ */ jsx(LabHeader, { ...addTrial2 !== void 0 ? { addTrial: addTrial2 } : {} }),
2785
- zoom ? /* @__PURE__ */ jsx(LabZoom, {}) : null,
3441
+ zoom && !presenting ? /* @__PURE__ */ jsx(LabZoom, {}) : null,
2786
3442
  children,
2787
3443
  /* @__PURE__ */ jsx(LabHeaderRegion, { contributions: labChromeAll }),
2788
3444
  /* @__PURE__ */ jsx(LabThemeSwitcher, {})
2789
3445
  ] }),
2790
- children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: surfaceCanvases, children: /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", ref: labBodyRef, children: [
2791
- outerSurface ? null : (
2792
- // Two buffers stacked around the trials, both inert —
2793
- // each tile takes input from its own box. A tile's marks
2794
- // annotate the instrument's DOM from over it; an opaque
2795
- // renderer sits under it, so the pane can still hold a
2796
- // label. The under one is first so it paints first.
2797
- /* @__PURE__ */ jsxs(Fragment, { children: [
2798
- /* @__PURE__ */ jsx(
2799
- "canvas",
2800
- {
2801
- className: "lk-lab__surface lk-lab__surface--under",
2802
- ref: (el) => {
2803
- ownUnderRef.current = el;
2804
- setOwnUnder(el);
2805
- }
2806
- }
2807
- ),
2808
- /* @__PURE__ */ jsx(
2809
- "canvas",
2810
- {
2811
- className: "lk-lab__surface lk-lab__surface--over",
2812
- ref: (el) => {
2813
- ownOverRef.current = el;
2814
- setOwnOver(el);
2815
- }
2816
- }
2817
- )
2818
- ] })
2819
- ),
2820
- hasPaneChrome ? /* @__PURE__ */ jsxs(LabPanes, { contributions: labChromeAll, children: [
2821
- /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
2822
- workspace
2823
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2824
- /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
2825
- workspace
2826
- ] })
2827
- ] }) }) }) })
3446
+ children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(LabSurface, { bodyRef: setLabBody, children: hasPaneChrome ? /* @__PURE__ */ jsxs(LabPanes, { contributions: labChromeAll, children: [
3447
+ /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
3448
+ workspace
3449
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3450
+ /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
3451
+ workspace
3452
+ ] }) }) })
2828
3453
  }
2829
3454
  )
2830
3455
  }
2831
- ) }) }) }) }) });
3456
+ ) }) }) }) }) }) }) });
2832
3457
  }
2833
3458
  function LabBoundary({ children }) {
2834
3459
  return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: null, children: /* @__PURE__ */ jsx(PersistenceContext.Provider, { value: null, children: /* @__PURE__ */ jsx(AnnotationPreloadContext.Provider, { value: null, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: null, children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: { over: null, under: null }, children: /* @__PURE__ */ jsx(TrialIdContext.Provider, { value: null, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: null, children: /* @__PURE__ */ jsx(CanvasStackContext.Provider, { value: null, children: /* @__PURE__ */ jsx(TrialDragContext.Provider, { value: null, children: /* @__PURE__ */ jsx(ThemeContext.Provider, { value: null, children }) }) }) }) }) }) }) }) }) }) }) }) });
@@ -2940,9 +3565,9 @@ function ButtonsAndToggles() {
2940
3565
  /* @__PURE__ */ jsx(Xt, { status: "muted", children: "MUTED" })
2941
3566
  ] }) }),
2942
3567
  /* @__PURE__ */ jsx(Cell, { label: "Checkbox, Switch", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2943
- /* @__PURE__ */ jsx(c6, { isSelected: snap, onChange: setSnap, children: "Snap to grid" }),
2944
- /* @__PURE__ */ jsx(c6, { isIndeterminate: true, children: "Mixed" }),
2945
- /* @__PURE__ */ jsx(a6, { isSelected: wifi, onChange: setWifi, children: "Wifi" })
3568
+ /* @__PURE__ */ jsx(c4, { isSelected: snap, onChange: setSnap, children: "Snap to grid" }),
3569
+ /* @__PURE__ */ jsx(c4, { isIndeterminate: true, children: "Mixed" }),
3570
+ /* @__PURE__ */ jsx(a5, { isSelected: wifi, onChange: setWifi, children: "Wifi" })
2946
3571
  ] }) }),
2947
3572
  /* @__PURE__ */ jsx(Cell, { label: "RadioGroup", children: /* @__PURE__ */ jsxs(l5, { label: "Snap mode", orientation: "horizontal", defaultValue: "grid", children: [
2948
3573
  /* @__PURE__ */ jsx(u3, { value: "off", children: "Off" }),
@@ -2951,7 +3576,7 @@ function ButtonsAndToggles() {
2951
3576
  ] }) }),
2952
3577
  /* @__PURE__ */ jsxs(Cell, { label: "ToggleBar", children: [
2953
3578
  /* @__PURE__ */ jsx(
2954
- a5,
3579
+ s3,
2955
3580
  {
2956
3581
  ariaLabel: "Align",
2957
3582
  value: align,
@@ -2964,7 +3589,7 @@ function ButtonsAndToggles() {
2964
3589
  }
2965
3590
  ),
2966
3591
  /* @__PURE__ */ jsx(
2967
- a5,
3592
+ s3,
2968
3593
  {
2969
3594
  ariaLabel: "Align, flat",
2970
3595
  variant: "flat",
@@ -2990,7 +3615,7 @@ function ButtonsAndToggles() {
2990
3615
  }
2991
3616
  ),
2992
3617
  /* @__PURE__ */ jsx(
2993
- a8,
3618
+ a7,
2994
3619
  {
2995
3620
  items: [
2996
3621
  { value: "undo", label: "Undo", onAction: () => {
@@ -3003,7 +3628,7 @@ function ButtonsAndToggles() {
3003
3628
  ] }) }),
3004
3629
  /* @__PURE__ */ jsx(Cell, { label: "MenuButton, Disclosure", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
3005
3630
  /* @__PURE__ */ jsx(
3006
- p4,
3631
+ p6,
3007
3632
  {
3008
3633
  label: "Add trial\u2026",
3009
3634
  onAction: () => {
@@ -3017,19 +3642,19 @@ function ButtonsAndToggles() {
3017
3642
  /* @__PURE__ */ jsx(i3, { open, onToggle: () => setOpen((o4) => !o4), label: "Shapes" })
3018
3643
  ] }) }),
3019
3644
  /* @__PURE__ */ jsx(Cell, { label: "Tabs", children: /* @__PURE__ */ jsxs(o5, { children: [
3020
- /* @__PURE__ */ jsxs(s4, { "aria-label": "Sections", children: [
3021
- /* @__PURE__ */ jsx(c7, { id: "props", children: "Props" }),
3022
- /* @__PURE__ */ jsx(c7, { id: "events", children: "Events" }),
3023
- /* @__PURE__ */ jsx(c7, { id: "snapshot", children: "Snapshot" })
3645
+ /* @__PURE__ */ jsxs(s5, { "aria-label": "Sections", children: [
3646
+ /* @__PURE__ */ jsx(c5, { id: "props", children: "Props" }),
3647
+ /* @__PURE__ */ jsx(c5, { id: "events", children: "Events" }),
3648
+ /* @__PURE__ */ jsx(c5, { id: "snapshot", children: "Snapshot" })
3024
3649
  ] }),
3025
- /* @__PURE__ */ jsx(l9, { id: "props", children: "Props panel." }),
3026
- /* @__PURE__ */ jsx(l9, { id: "events", children: "Events panel." }),
3027
- /* @__PURE__ */ jsx(l9, { id: "snapshot", children: "Snapshot panel." })
3650
+ /* @__PURE__ */ jsx(l8, { id: "props", children: "Props panel." }),
3651
+ /* @__PURE__ */ jsx(l8, { id: "events", children: "Events panel." }),
3652
+ /* @__PURE__ */ jsx(l8, { id: "snapshot", children: "Snapshot panel." })
3028
3653
  ] }) }),
3029
3654
  /* @__PURE__ */ jsx(Cell, { label: "Keycaps", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
3030
3655
  /* @__PURE__ */ jsx(p, { keys: [{ label: "\u2318" }, { label: "\u21E7" }, { label: "K" }], separator: "+" }),
3031
- /* @__PURE__ */ jsx(f2, { label: "K" }),
3032
- /* @__PURE__ */ jsx(f2, { label: "\u2325", variant: "minimal" })
3656
+ /* @__PURE__ */ jsx(f3, { label: "K" }),
3657
+ /* @__PURE__ */ jsx(f3, { label: "\u2325", variant: "minimal" })
3033
3658
  ] }) })
3034
3659
  ] });
3035
3660
  }
@@ -3089,9 +3714,9 @@ function FieldsAndPickers() {
3089
3714
  )
3090
3715
  ] }),
3091
3716
  /* @__PURE__ */ jsxs(Cell, { label: "RangeSlider, Slider", children: [
3092
- /* @__PURE__ */ jsx(l10, { label: "Range", defaultValue: [20, 80], minValue: 0, maxValue: 100 }),
3717
+ /* @__PURE__ */ jsx(l9, { label: "Range", defaultValue: [20, 80], minValue: 0, maxValue: 100 }),
3093
3718
  /* @__PURE__ */ jsx(
3094
- l10,
3719
+ l9,
3095
3720
  {
3096
3721
  label: "Opacity",
3097
3722
  defaultValue: 75,
@@ -3101,7 +3726,7 @@ function FieldsAndPickers() {
3101
3726
  }
3102
3727
  ),
3103
3728
  /* @__PURE__ */ jsx(
3104
- S,
3729
+ w2,
3105
3730
  {
3106
3731
  ariaLabel: "Pair",
3107
3732
  min: 0,
@@ -3116,7 +3741,7 @@ function FieldsAndPickers() {
3116
3741
  ] }),
3117
3742
  /* @__PURE__ */ jsxs(Cell, { label: "DetentSlider, InlineRange", children: [
3118
3743
  /* @__PURE__ */ jsx(
3119
- c8,
3744
+ c6,
3120
3745
  {
3121
3746
  items: [0.25, 0.5, 1, 2, 4],
3122
3747
  value: rate,
@@ -3171,9 +3796,9 @@ function PanelsAndRows() {
3171
3796
  ]);
3172
3797
  const [panelCollapsed, setPanelCollapsed] = useState(false);
3173
3798
  return /* @__PURE__ */ jsxs(Section, { title: "Panels & property rows", children: [
3174
- /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(ae2, { title: "Shape", children: /* @__PURE__ */ jsxs(oe, { children: [
3799
+ /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(ce2, { title: "Shape", children: /* @__PURE__ */ jsxs(le, { children: [
3175
3800
  /* @__PURE__ */ jsx(
3176
- de,
3801
+ ye,
3177
3802
  {
3178
3803
  kind: "number",
3179
3804
  control: "slider",
@@ -3186,7 +3811,7 @@ function PanelsAndRows() {
3186
3811
  }
3187
3812
  ),
3188
3813
  /* @__PURE__ */ jsx(
3189
- de,
3814
+ ye,
3190
3815
  {
3191
3816
  kind: "number",
3192
3817
  control: "slider",
@@ -3199,7 +3824,7 @@ function PanelsAndRows() {
3199
3824
  }
3200
3825
  ),
3201
3826
  /* @__PURE__ */ jsx(
3202
- de,
3827
+ ye,
3203
3828
  {
3204
3829
  kind: "color",
3205
3830
  label: "Fill",
@@ -3209,9 +3834,9 @@ function PanelsAndRows() {
3209
3834
  onAlphaChange: setAlpha
3210
3835
  }
3211
3836
  ),
3212
- /* @__PURE__ */ jsx(de, { kind: "color", label: "Stroke", value: stroke, onChange: setStroke }),
3837
+ /* @__PURE__ */ jsx(ye, { kind: "color", label: "Stroke", value: stroke, onChange: setStroke }),
3213
3838
  /* @__PURE__ */ jsx(
3214
- de,
3839
+ ye,
3215
3840
  {
3216
3841
  kind: "number",
3217
3842
  label: "Count",
@@ -3223,7 +3848,7 @@ function PanelsAndRows() {
3223
3848
  }
3224
3849
  ),
3225
3850
  /* @__PURE__ */ jsx(
3226
- de,
3851
+ ye,
3227
3852
  {
3228
3853
  kind: "enum",
3229
3854
  label: "Line cap",
@@ -3236,7 +3861,7 @@ function PanelsAndRows() {
3236
3861
  }
3237
3862
  ),
3238
3863
  /* @__PURE__ */ jsx(
3239
- de,
3864
+ ye,
3240
3865
  {
3241
3866
  kind: "enum",
3242
3867
  control: "toggle",
@@ -3250,10 +3875,10 @@ function PanelsAndRows() {
3250
3875
  ]
3251
3876
  }
3252
3877
  ),
3253
- /* @__PURE__ */ jsx(de, { kind: "string", label: "Label", value: label, onChange: setLabel }),
3254
- /* @__PURE__ */ jsx(de, { kind: "boolean", label: "Visible", value: visible, onChange: setVisible }),
3878
+ /* @__PURE__ */ jsx(ye, { kind: "string", label: "Label", value: label, onChange: setLabel }),
3879
+ /* @__PURE__ */ jsx(ye, { kind: "boolean", label: "Visible", value: visible, onChange: setVisible }),
3255
3880
  /* @__PURE__ */ jsx(b3, { title: "Bevel", defaultCollapsed: true, children: /* @__PURE__ */ jsx(
3256
- de,
3881
+ ye,
3257
3882
  {
3258
3883
  kind: "number",
3259
3884
  control: "slider",
@@ -3276,10 +3901,10 @@ function PanelsAndRows() {
3276
3901
  }
3277
3902
  ) }),
3278
3903
  /* @__PURE__ */ jsx(Cell, { label: "LayerList", children: /* @__PURE__ */ jsx(
3279
- D2,
3904
+ A2,
3280
3905
  {
3281
3906
  items: layers,
3282
- onReorder: (move) => setLayers(S2(layers, move)),
3907
+ onReorder: (move) => setLayers(T(layers, move)),
3283
3908
  renderBody: (item) => /* @__PURE__ */ jsxs("div", { children: [
3284
3909
  "settings for ",
3285
3910
  item.label
@@ -3287,7 +3912,7 @@ function PanelsAndRows() {
3287
3912
  }
3288
3913
  ) }),
3289
3914
  /* @__PURE__ */ jsx(Cell, { label: "Sidebar, SidebarPanel", children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__box", children: /* @__PURE__ */ jsx(n6, { side: "right", ariaLabel: "Specimen sidebar", children: /* @__PURE__ */ jsx(
3290
- a9,
3915
+ a8,
3291
3916
  {
3292
3917
  title: "Selection",
3293
3918
  collapsed: panelCollapsed,
@@ -3312,7 +3937,7 @@ function OverlaysAndFeedback() {
3312
3937
  ] }),
3313
3938
  /* @__PURE__ */ jsx("button", { type: "button", ref: calloutAnchor, onClick: () => setCalloutOpen(true), children: "Callout" }),
3314
3939
  /* @__PURE__ */ jsx(
3315
- v2,
3940
+ v3,
3316
3941
  {
3317
3942
  triggerRef: calloutAnchor,
3318
3943
  isOpen: calloutOpen,
@@ -3324,7 +3949,7 @@ function OverlaysAndFeedback() {
3324
3949
  ),
3325
3950
  /* @__PURE__ */ jsx(c, { onClick: () => setDialogOpen(true), children: "Dialog" }),
3326
3951
  /* @__PURE__ */ jsx(
3327
- d3,
3952
+ d2,
3328
3953
  {
3329
3954
  isOpen: dialogOpen,
3330
3955
  onOpenChange: setDialogOpen,
@@ -3376,7 +4001,7 @@ function ListsAndData() {
3376
4001
  }
3377
4002
  ) }),
3378
4003
  /* @__PURE__ */ jsx(Cell, { label: "DataGrid", children: /* @__PURE__ */ jsx(
3379
- f,
4004
+ f2,
3380
4005
  {
3381
4006
  rows: [
3382
4007
  { id: "1", name: "accent", count: 3 },
@@ -3407,14 +4032,14 @@ function ToolChrome() {
3407
4032
  },
3408
4033
  id
3409
4034
  )) }) }),
3410
- /* @__PURE__ */ jsx(Cell, { label: "ToolOptionsBar", children: /* @__PURE__ */ jsxs(c5, { label: "Text", children: [
4035
+ /* @__PURE__ */ jsx(Cell, { label: "ToolOptionsBar", children: /* @__PURE__ */ jsxs(p4, { label: "Text", children: [
3411
4036
  /* @__PURE__ */ jsx(c, { size: "sm", variant: "ghost", iconOnly: true, ariaLabel: "Bold", children: "B" }),
3412
4037
  /* @__PURE__ */ jsx(c, { size: "sm", variant: "ghost", iconOnly: true, ariaLabel: "Italic", children: "I" })
3413
4038
  ] }) }),
3414
4039
  /* @__PURE__ */ jsx(Cell, { label: "ResizeHandle", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__box", children: [
3415
4040
  /* @__PURE__ */ jsx("div", { className: "lk-specimen__box-main", children: "content" }),
3416
4041
  /* @__PURE__ */ jsx(
3417
- l8,
4042
+ l7,
3418
4043
  {
3419
4044
  value: paneWidth,
3420
4045
  min: 100,
@@ -3538,7 +4163,7 @@ function EditorsAndPlots() {
3538
4163
  }
3539
4164
  ) }),
3540
4165
  /* @__PURE__ */ jsx(Cell, { label: "Timeline", wide: true, children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__timeline", children: /* @__PURE__ */ jsx(
3541
- xe2,
4166
+ xe,
3542
4167
  {
3543
4168
  tracks,
3544
4169
  duration: 2e3,
@@ -3563,10 +4188,6 @@ function LabChrome() {
3563
4188
  /* @__PURE__ */ jsx(Toolbar.Group, { end: true, children: /* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => {
3564
4189
  }, children: "Save" }) })
3565
4190
  ] }) }),
3566
- /* @__PURE__ */ jsx(Cell, { label: "StatusBar", children: /* @__PURE__ */ jsxs(StatusBar, { children: [
3567
- /* @__PURE__ */ jsx(StatusBar.Section, { children: "Items: 12" }),
3568
- /* @__PURE__ */ jsx(StatusBar.Section, { children: "Zoom: 100%" })
3569
- ] }) }),
3570
4191
  /* @__PURE__ */ jsxs(Cell, { label: "Legend, JobProgress", children: [
3571
4192
  /* @__PURE__ */ jsx(Legend, { entries: LEGEND }),
3572
4193
  /* @__PURE__ */ jsx(JobProgress, { job: JOB })
@@ -3601,6 +4222,6 @@ function Specimen({ className }) {
3601
4222
  ] });
3602
4223
  }
3603
4224
 
3604
- export { AnnotationOverlay, AnnotationTargets, Lab, LabBoundary, LabPalette, LabRoot, LabShell, LabSwitcher, LabZoom, SPECIMEN_SECTIONS, Specimen, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationsFromJSON, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, fitView, fromWeaselView, interstellarTheme, isStale, reorderTrials, resetTrial, resolveMarkStyle, seenFrom, toWeaselView, validateConfigSchema };
4225
+ export { AnnotationOverlay, AnnotationTargets, Lab, LabBoundary, LabPalette, LabRoot, LabShell, LabSwitcher, LabZoom, Lightbox, LightboxLayers, SPECIMEN_SECTIONS, Specimen, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationsFromJSON, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, fitView, fromWeaselView, interstellarTheme, isStale, reorderTrials, resetTrial, resolveMarkStyle, seenFrom, toWeaselView, useClockFrame, useLightbox, useLightboxControl, useLightboxLayers, usePresentation, useTrialClock, validateConfigSchema };
3605
4226
  //# sourceMappingURL=index.js.map
3606
4227
  //# sourceMappingURL=index.js.map