@weasel-js/labkit 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +6 -6
  2. package/dist/_dts/{DrawCommand-uKHt4Vul.d.ts → DrawCommand-BkZztJsW.d.ts} +3 -1
  3. package/dist/_dts/{index-CFlDPeZh.d.ts → index-iAP6XbH3.d.ts} +17 -7
  4. package/dist/_dts/{useExperimentState-vrttakTt.d.ts → useTrialState-DYe2vUwN.d.ts} +62 -34
  5. package/dist/canvas/index.d.ts +1 -1
  6. package/dist/chunk-3TYUJR7Z.js +488 -0
  7. package/dist/chunk-3TYUJR7Z.js.map +1 -0
  8. package/dist/{chunk-N5KTQKQA.js → chunk-C6GJKPUI.js} +835 -770
  9. package/dist/chunk-C6GJKPUI.js.map +1 -0
  10. package/dist/{chunk-3WPOGKUP.js → chunk-DJLDIRFN.js} +543 -226
  11. package/dist/chunk-DJLDIRFN.js.map +1 -0
  12. package/dist/{chunk-73PXCRCR.js → chunk-NRKWVTVT.js} +6 -6
  13. package/dist/chunk-NRKWVTVT.js.map +1 -0
  14. package/dist/dragdrop/index.d.ts +1 -1
  15. package/dist/index.d.ts +197 -148
  16. package/dist/index.js +275 -161
  17. package/dist/index.js.map +1 -1
  18. package/dist/layers/index.d.ts +2 -2
  19. package/dist/passthrough/weasel-canvas.d.ts +1 -1
  20. package/dist/passthrough/weasel-canvas.js +1 -1
  21. package/dist/passthrough/weasel-ui.d.ts +10 -3
  22. package/dist/passthrough/weasel-ui.js +2 -2
  23. package/dist/state/index.d.ts +14 -14
  24. package/dist/state/index.js +5 -5
  25. package/dist/state/index.js.map +1 -1
  26. package/dist/styles.css +179 -14
  27. package/dist/ui/layers/index.js +3 -3
  28. package/dist/undo/index.d.ts +1 -1
  29. package/package.json +2 -1
  30. package/src/canvas/AGENTS.md +6 -2
  31. package/src/dragdrop/dragDrop.test.tsx +4 -7
  32. package/src/index.test.ts +52 -0
  33. package/src/index.ts +11 -7
  34. package/src/instrument/SineWave.smoke.test.tsx +1 -1
  35. package/src/instrument/capabilityDetector.ts +1 -1
  36. package/src/instrument/types.ts +18 -6
  37. package/src/lab/Lab.less +16 -0
  38. package/src/lab/Lab.stories.tsx +5 -5
  39. package/src/lab/Lab.test.tsx +25 -25
  40. package/src/lab/Lab.tsx +56 -44
  41. package/src/lab/LabContext.ts +12 -11
  42. package/src/lab/LabShell.tsx +1 -1
  43. package/src/lab/Workspace.less +40 -0
  44. package/src/lab/{WorkspaceGrid.stories.tsx → Workspace.stories.tsx} +27 -11
  45. package/src/lab/Workspace.test.tsx +114 -0
  46. package/src/lab/Workspace.tsx +196 -0
  47. package/src/lab/index.ts +2 -4
  48. package/src/layers/AGENTS.md +3 -3
  49. package/src/primitives/Toolbar.stories.tsx +2 -2
  50. package/src/state/SingletonExperiment.test.tsx +8 -8
  51. package/src/state/SingletonExperiment.tsx +9 -9
  52. package/src/state/context.tsx +12 -12
  53. package/src/state/document.test.ts +369 -0
  54. package/src/state/document.ts +194 -0
  55. package/src/state/helpers.test.ts +74 -46
  56. package/src/state/helpers.ts +23 -31
  57. package/src/state/index.ts +16 -8
  58. package/src/state/store.test.ts +440 -40
  59. package/src/state/store.ts +166 -90
  60. package/src/state/types.ts +30 -10
  61. package/src/state/{useExperimentState.test.tsx → useTrialState.test.tsx} +18 -18
  62. package/src/state/useTrialState.ts +29 -0
  63. package/src/styles.less +2 -2
  64. package/src/test-setup.ts +19 -0
  65. package/src/theme/base.less +1 -1
  66. package/src/{workspace → trial}/DefaultSidebar.tsx +3 -3
  67. package/src/{workspace → trial}/DefaultStatusBar.tsx +5 -4
  68. package/src/{workspace → trial}/DefaultToolbar.tsx +9 -8
  69. package/src/trial/Trial.canvas.test.tsx +80 -0
  70. package/src/{workspace/Workspace.less → trial/Trial.less} +1 -1
  71. package/src/{workspace/Workspace.stories.tsx → trial/Trial.stories.tsx} +13 -17
  72. package/src/{workspace/Workspace.test.tsx → trial/Trial.test.tsx} +26 -25
  73. package/src/{workspace/Workspace.tsx → trial/Trial.tsx} +48 -43
  74. package/src/{workspace/WorkspaceChrome.tsx → trial/TrialChrome.tsx} +42 -42
  75. package/src/{workspace → trial}/index.ts +13 -12
  76. package/src/{workspace → trial}/slotTypes.ts +16 -16
  77. package/src/{workspace/workspaceOps.test.ts → trial/trialOps.test.ts} +60 -33
  78. package/src/trial/trialOps.ts +99 -0
  79. package/src/ui/format.test.ts +33 -0
  80. package/src/ui/format.ts +11 -0
  81. package/src/ui/properties/CurveField.tsx +6 -6
  82. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +3 -3
  83. package/dist/chunk-3WPOGKUP.js.map +0 -1
  84. package/dist/chunk-73PXCRCR.js.map +0 -1
  85. package/dist/chunk-N5KTQKQA.js.map +0 -1
  86. package/dist/chunk-T7OKNJTY.js +0 -351
  87. package/dist/chunk-T7OKNJTY.js.map +0 -1
  88. package/src/lab/WorkspaceGrid.less +0 -8
  89. package/src/lab/WorkspaceGrid.test.tsx +0 -40
  90. package/src/lab/WorkspaceGrid.tsx +0 -22
  91. package/src/lab/gridDims.test.ts +0 -35
  92. package/src/lab/gridDims.ts +0 -13
  93. package/src/state/useExperimentState.ts +0 -31
  94. package/src/workspace/workspaceOps.ts +0 -86
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- export { LayerStack } from './chunk-73PXCRCR.js';
2
- import { p, F } from './chunk-N5KTQKQA.js';
3
- import './chunk-3WPOGKUP.js';
1
+ export { LayerStack } from './chunk-NRKWVTVT.js';
2
+ import { p, F } from './chunk-C6GJKPUI.js';
3
+ import './chunk-DJLDIRFN.js';
4
4
  import { Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
5
5
  export { FpsMeter, ScaleIndicator, Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
6
- import { LabStoreContext, createLabStore, noneAdapter } from './chunk-T7OKNJTY.js';
7
- export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createMemoryAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId } from './chunk-T7OKNJTY.js';
6
+ import { LabStoreContext, createLabStore, noneAdapter } from './chunk-3TYUJR7Z.js';
7
+ export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createMemoryAdapter, labDocumentKey, localStorageAdapter, noneAdapter, quarantineKey, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState } from './chunk-3TYUJR7Z.js';
8
8
  import { ControlPanel } from './chunk-5R2ATYPJ.js';
9
9
  export { ControlPanel } from './chunk-5R2ATYPJ.js';
10
10
  import { CanvasStack, screenToWorld } from './chunk-574LJAV4.js';
@@ -14,10 +14,12 @@ import { LayerList } from './chunk-SFL7NFKN.js';
14
14
  export { LayerList } from './chunk-SFL7NFKN.js';
15
15
  import { createEventBus, redo, undo, pushSnapshot } from './chunk-RL2LOLNI.js';
16
16
  export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
17
- import { createContext, useContext, useMemo, useRef, useState, Children, useEffect, useCallback, useLayoutEffect } from 'react';
17
+ import { createContext, useContext, useMemo, useRef, useState, Children, useLayoutEffect, useEffect, useCallback } from 'react';
18
18
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
19
19
  import { useStore } from 'zustand/react';
20
20
  import { createPortal } from 'react-dom';
21
+ import { asNodeId, Store, createNode, gridStrategy } from 'windease';
22
+ import { DragHandle, Provider, StrategyRegistryProvider, DragProvider, Container } from 'windease/react';
21
23
 
22
24
  // src/instrument/capabilityDetector.ts
23
25
  function detectCapabilities(instrument) {
@@ -88,15 +90,7 @@ function validateConfigSchema(fields) {
88
90
  return { valid: errors.length === 0, errors };
89
91
  }
90
92
 
91
- // src/lab/gridDims.ts
92
- function gridDims(count) {
93
- if (count <= 1) return { cols: 1, rows: 1 };
94
- const cols = Math.ceil(Math.sqrt(count));
95
- const rows = Math.ceil(count / cols);
96
- return { cols, rows };
97
- }
98
-
99
- // ../theme/dist/chunk-STFENPNX.js
93
+ // ../theme/dist/chunk-E3FOFGMD.js
100
94
  var THEME_SOURCES = {
101
95
  "weasel": {
102
96
  "primitives": {
@@ -636,19 +630,20 @@ function resolveTheme(theme, mode) {
636
630
  return out;
637
631
  }
638
632
  var STYLE_ID = "wzl-themes";
639
- var emitted = /* @__PURE__ */ new Set();
633
+ var emitted = /* @__PURE__ */ new Map();
640
634
  var sheet = null;
641
635
  var styleEl = null;
642
636
  function canAdopt() {
643
637
  return typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && Array.isArray(document.adoptedStyleSheets);
644
638
  }
645
- function appendRule(css) {
639
+ function flushRules() {
640
+ const css = [...emitted.values()].join("\n");
646
641
  if (canAdopt()) {
647
642
  if (!sheet) {
648
643
  sheet = new CSSStyleSheet();
649
644
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
650
645
  }
651
- sheet.insertRule(css, sheet.cssRules.length);
646
+ sheet.replaceSync(css);
652
647
  return;
653
648
  }
654
649
  if (!styleEl) {
@@ -656,17 +651,17 @@ function appendRule(css) {
656
651
  styleEl.id = STYLE_ID;
657
652
  document.head.appendChild(styleEl);
658
653
  }
659
- styleEl.textContent += `${css}
660
- `;
654
+ styleEl.textContent = css;
661
655
  }
662
656
  function applyTheme(el, theme, mode) {
663
657
  if (typeof document === "undefined") return;
664
658
  const key = `${theme.name}::${mode}`;
665
- if (!emitted.has(key)) {
666
- const resolved = resolveTheme(theme, mode);
667
- const body = Object.entries(resolved).map(([name, value]) => `${name}: ${value};`).join(" ");
668
- appendRule(`[data-wzl-theme='${theme.name}'][data-wzl-mode='${mode}'] { ${body} }`);
669
- emitted.add(key);
659
+ const resolved = resolveTheme(theme, mode);
660
+ const body = Object.entries(resolved).map(([name, value]) => `${name}: ${value};`).join(" ");
661
+ const rule = `[data-wzl-theme='${theme.name}'][data-wzl-mode='${mode}'] { ${body} }`;
662
+ if (emitted.get(key) !== rule) {
663
+ emitted.set(key, rule);
664
+ flushRules();
670
665
  }
671
666
  el.setAttribute("data-wzl-theme", theme.name);
672
667
  el.setAttribute("data-wzl-mode", mode);
@@ -955,13 +950,27 @@ function DefaultSidebar({ ctx }) {
955
950
  }
956
951
  ) });
957
952
  }
953
+
954
+ // src/ui/format.ts
955
+ var MINUS = "\u2212";
956
+ function formatNumber(n, fractionDigits) {
957
+ if (!Number.isFinite(n)) return String(n);
958
+ const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
959
+ return s.startsWith("-") ? MINUS + s.slice(1) : s;
960
+ }
961
+ function parseSignedNumber(s) {
962
+ return Number(s.replace(MINUS, "-"));
963
+ }
964
+ function formatZoom(zoom) {
965
+ if (!Number.isFinite(zoom)) return String(zoom);
966
+ if (zoom <= 2) return `${formatNumber(Math.round(zoom * 100))}%`;
967
+ if (zoom >= 100) return `${Math.round(zoom).toLocaleString("en-US")}x`;
968
+ return `${formatNumber(Math.round(zoom * 10) / 10)}x`;
969
+ }
958
970
  function DefaultStatusBar({ ctx }) {
959
971
  return /* @__PURE__ */ jsxs(StatusBar, { children: [
960
972
  /* @__PURE__ */ jsx(StatusBar.Section, { children: ctx.instrumentName }),
961
- /* @__PURE__ */ jsxs(StatusBar.Section, { children: [
962
- Math.round(ctx.zoom * 100),
963
- "%"
964
- ] })
973
+ /* @__PURE__ */ jsx(StatusBar.Section, { children: formatZoom(ctx.zoom) })
965
974
  ] });
966
975
  }
967
976
  function DefaultToolbar({ ctx }) {
@@ -981,10 +990,7 @@ function DefaultToolbar({ ctx }) {
981
990
  ] }),
982
991
  ctx.hasCanvas && /* @__PURE__ */ jsxs(Fragment, { children: [
983
992
  /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomOut, title: "Zoom out", children: "\u2212" }),
984
- /* @__PURE__ */ jsxs("span", { className: "lk-toolbar__zoom-label", children: [
985
- Math.round(ctx.zoom * 100),
986
- "%"
987
- ] }),
993
+ /* @__PURE__ */ jsx("span", { className: "lk-toolbar__zoom-label", children: formatZoom(ctx.zoom) }),
988
994
  /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomIn, title: "Zoom in", children: "+" }),
989
995
  /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.resetZoom, title: "Reset zoom", children: "1:1" }),
990
996
  /* @__PURE__ */ jsx(Toolbar.Spacer, {})
@@ -1010,24 +1016,24 @@ function DefaultToolbar({ ctx }) {
1010
1016
  }
1011
1017
  ),
1012
1018
  /* @__PURE__ */ jsx(Toolbar.Spacer, {}),
1013
- /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone workspace", children: "Clone" }),
1014
- /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset workspace", children: "Reset" }),
1019
+ /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone trial", children: "Clone" }),
1020
+ /* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset trial", children: "Reset" }),
1015
1021
  /* @__PURE__ */ jsx(
1016
1022
  Toolbar.Button,
1017
1023
  {
1018
1024
  onClick: ctx.close,
1019
- disabled: ctx.isLastWorkspace,
1020
- title: ctx.isLastWorkspace ? "Cannot close the last workspace" : "Close workspace",
1025
+ disabled: ctx.isLastTrial,
1026
+ title: ctx.isLastTrial ? "Cannot close the last trial" : "Close trial",
1021
1027
  children: "Close"
1022
1028
  }
1023
1029
  )
1024
1030
  ] });
1025
1031
  }
1026
- function WorkspaceChrome({
1027
- workspaceId,
1032
+ function TrialChrome({
1033
+ trialId,
1028
1034
  record,
1029
1035
  instrument,
1030
- isLastWorkspace,
1036
+ isLastTrial,
1031
1037
  toolbar,
1032
1038
  sidebar,
1033
1039
  statusBar,
@@ -1037,16 +1043,16 @@ function WorkspaceChrome({
1037
1043
  }) {
1038
1044
  const lab = useLabContext();
1039
1045
  const storeCtx = useContext(LabStoreContext);
1040
- if (!storeCtx) throw new Error("[labkit] WorkspaceChrome requires <LabStoreProvider>");
1041
- const updateWorkspaceView = useStore(storeCtx.store, (s) => s.updateWorkspaceView);
1042
- const updateWorkspaceConfig = useStore(storeCtx.store, (s) => s.updateWorkspaceConfig);
1043
- const updateWorkspaceState = useStore(storeCtx.store, (s) => s.updateWorkspaceState);
1046
+ if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
1047
+ const updateTrialView = useStore(storeCtx.store, (s) => s.updateTrialView);
1048
+ const updateTrialConfig = useStore(storeCtx.store, (s) => s.updateTrialConfig);
1049
+ const updateTrialState = useStore(storeCtx.store, (s) => s.updateTrialState);
1044
1050
  const toolbarCtx = useMemo(() => {
1045
1051
  const setZoom = (z) => {
1046
- updateWorkspaceView(workspaceId, { ...record.view, zoom: z });
1052
+ updateTrialView(trialId, { ...record.view, zoom: z });
1047
1053
  };
1048
1054
  return {
1049
- workspaceId,
1055
+ trialId,
1050
1056
  instrumentName: record.instrumentName,
1051
1057
  hasUndo: instrument.undo != null,
1052
1058
  canUndo: undoBindings?.canUndo ?? false,
@@ -1061,18 +1067,18 @@ function WorkspaceChrome({
1061
1067
  zoomOut: () => setZoom(record.view.zoom * 0.8),
1062
1068
  resetZoom: () => setZoom(1),
1063
1069
  hasCanvas: instrument.canvas != null,
1064
- savedSnapshots: lab.savedSnapshots.filter((s) => s.workspaceId === workspaceId),
1065
- saveSnapshot: (name) => lab.saveSnapshot(workspaceId, name),
1066
- loadSnapshot: (snapshotId) => lab.loadSnapshot(workspaceId, snapshotId),
1067
- clone: () => lab.cloneWorkspace(workspaceId),
1068
- reset: () => lab.resetWorkspace(workspaceId),
1069
- close: () => lab.closeWorkspace(workspaceId),
1070
- isLastWorkspace
1070
+ savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
1071
+ saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
1072
+ loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
1073
+ clone: () => lab.cloneTrial(trialId),
1074
+ reset: () => lab.resetTrial(trialId),
1075
+ close: () => lab.closeTrial(trialId),
1076
+ isLastTrial
1071
1077
  };
1072
- }, [workspaceId, record, instrument, lab, isLastWorkspace, updateWorkspaceView, undoBindings]);
1078
+ }, [trialId, record, instrument, lab, isLastTrial, updateTrialView, undoBindings]);
1073
1079
  const sidebarCtx = useMemo(
1074
1080
  () => ({
1075
- workspaceId,
1081
+ trialId,
1076
1082
  instrumentName: record.instrumentName,
1077
1083
  configFields: instrument.configSchema?.() ?? [],
1078
1084
  config: record.config,
@@ -1083,18 +1089,18 @@ function WorkspaceChrome({
1083
1089
  `[labkit] setConfig: unknown key "${key}" for instrument "${record.instrumentName}"`
1084
1090
  );
1085
1091
  }
1086
- updateWorkspaceConfig(workspaceId, key, value);
1092
+ updateTrialConfig(trialId, key, value);
1087
1093
  if (instrument.onConfigChange) {
1088
1094
  const nextConfig = { ...prevConfig, [key]: value };
1089
1095
  const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
1090
- updateWorkspaceState(workspaceId, nextState);
1096
+ updateTrialState(trialId, nextState);
1091
1097
  }
1092
1098
  }
1093
1099
  }),
1094
- [workspaceId, record, instrument, updateWorkspaceConfig, updateWorkspaceState]
1100
+ [trialId, record, instrument, updateTrialConfig, updateTrialState]
1095
1101
  );
1096
1102
  const statusCtx = {
1097
- workspaceId,
1103
+ trialId,
1098
1104
  instrumentName: record.instrumentName,
1099
1105
  zoom: record.view.zoom
1100
1106
  };
@@ -1113,57 +1119,57 @@ function WorkspaceChrome({
1113
1119
  return /* @__PURE__ */ jsxs(
1114
1120
  "section",
1115
1121
  {
1116
- className: "lk-workspace",
1117
- "aria-label": `Workspace ${record.instrumentName}`,
1122
+ className: "lk-trial",
1123
+ "aria-label": `Trial ${record.instrumentName}`,
1118
1124
  onKeyDown: handleKeyDown,
1119
1125
  children: [
1120
- /* @__PURE__ */ jsx("div", { className: "lk-workspace__toolbar", children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DefaultToolbar, { ctx: toolbarCtx }) }),
1121
- /* @__PURE__ */ jsxs("div", { className: "lk-workspace__body", children: [
1122
- /* @__PURE__ */ jsxs("div", { className: "lk-workspace__sidebar", children: [
1126
+ /* @__PURE__ */ jsx("div", { className: "lk-trial__toolbar", children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DefaultToolbar, { ctx: toolbarCtx }) }),
1127
+ /* @__PURE__ */ jsxs("div", { className: "lk-trial__body", children: [
1128
+ /* @__PURE__ */ jsxs("div", { className: "lk-trial__sidebar", children: [
1123
1129
  sidebar ? sidebar(sidebarCtx) : /* @__PURE__ */ jsx(DefaultSidebar, { ctx: sidebarCtx }),
1124
1130
  sidebarExtras
1125
1131
  ] }),
1126
- /* @__PURE__ */ jsx("div", { className: "lk-workspace__content", children })
1132
+ /* @__PURE__ */ jsx("div", { className: "lk-trial__content", children })
1127
1133
  ] }),
1128
- /* @__PURE__ */ jsx("div", { className: "lk-workspace__status", children: statusBar ? statusBar(statusCtx) : /* @__PURE__ */ jsx(DefaultStatusBar, { ctx: statusCtx }) })
1134
+ /* @__PURE__ */ jsx("div", { className: "lk-trial__status", children: statusBar ? statusBar(statusCtx) : /* @__PURE__ */ jsx(DefaultStatusBar, { ctx: statusCtx }) })
1129
1135
  ]
1130
1136
  }
1131
1137
  );
1132
1138
  }
1133
- function Workspace({ id }) {
1139
+ function Trial({ id }) {
1134
1140
  const lab = useLabContext();
1135
1141
  const storeCtx = useContext(LabStoreContext);
1136
- if (!storeCtx) throw new Error("[labkit] <Workspace> requires <LabStoreProvider>");
1137
- const record = useStore(storeCtx.store, (s) => s.workspaces.find((w) => w.id === id));
1142
+ if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
1143
+ const record = useStore(storeCtx.store, (s) => s.trials.find((w) => w.id === id));
1138
1144
  if (!record) {
1139
- return /* @__PURE__ */ jsxs("div", { className: "lk-workspace lk-workspace--unknown", children: [
1140
- "Workspace not found: ",
1145
+ return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
1146
+ "Trial not found: ",
1141
1147
  id
1142
1148
  ] });
1143
1149
  }
1144
1150
  const instrument = lab.instruments.find((i) => i.name === record.instrumentName);
1145
1151
  if (!instrument) {
1146
- return /* @__PURE__ */ jsxs("div", { className: "lk-workspace lk-workspace--unknown", children: [
1152
+ return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
1147
1153
  "Unknown instrument: ",
1148
1154
  record.instrumentName
1149
1155
  ] });
1150
1156
  }
1151
1157
  return /* @__PURE__ */ jsx(
1152
- WorkspaceRuntime,
1158
+ TrialRuntime,
1153
1159
  {
1154
1160
  record,
1155
1161
  instrument,
1156
1162
  store: storeCtx.store,
1157
- isLast: lab.workspaces.length <= 1
1163
+ isLast: lab.trials.length <= 1
1158
1164
  }
1159
1165
  );
1160
1166
  }
1161
- function WorkspaceRuntime({ record, instrument, store, isLast }) {
1167
+ function TrialRuntime({ record, instrument, store, isLast }) {
1162
1168
  const canvasContainerRef = useRef(null);
1163
- const updateWorkspaceState = useStore(store, (s) => s.updateWorkspaceState);
1164
- const updateWorkspaceConfig = useStore(store, (s) => s.updateWorkspaceConfig);
1165
- const updateWorkspaceView = useStore(store, (s) => s.updateWorkspaceView);
1166
- const updateWorkspaceUndoStack = useStore(store, (s) => s.updateWorkspaceUndoStack);
1169
+ const updateTrialState = useStore(store, (s) => s.updateTrialState);
1170
+ const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
1171
+ const updateTrialView = useStore(store, (s) => s.updateTrialView);
1172
+ const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
1167
1173
  const busRef = useRef(null);
1168
1174
  if (busRef.current === null) busRef.current = createEventBus();
1169
1175
  const bus = busRef.current;
@@ -1174,32 +1180,32 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1174
1180
  const maxDepth = undoCap?.maxDepth ?? 50;
1175
1181
  const snapshotIfNeeded = (event) => {
1176
1182
  if (!undoCap || !undoEvents.has(event)) return;
1177
- const current = store.getState().workspaces.find((w) => w.id === record.id);
1183
+ const current = store.getState().trials.find((w) => w.id === record.id);
1178
1184
  if (!current) return;
1179
1185
  const snap = structuredClone(current.state);
1180
- updateWorkspaceUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
1186
+ updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
1181
1187
  };
1182
- const setView = (v) => updateWorkspaceView(record.id, v);
1188
+ const setView = (v) => updateTrialView(record.id, v);
1183
1189
  const renderCtx = {
1184
1190
  state: record.state,
1185
1191
  config: record.config,
1186
1192
  setState: (next) => {
1187
1193
  snapshotIfNeeded("state.change");
1188
- updateWorkspaceState(record.id, next);
1194
+ updateTrialState(record.id, next);
1189
1195
  bus.emit("state.change");
1190
1196
  },
1191
1197
  setConfig: (key, value) => {
1192
1198
  const evt = `config.change:${String(key)}`;
1193
1199
  snapshotIfNeeded("config.change");
1194
1200
  snapshotIfNeeded(evt);
1195
- updateWorkspaceConfig(record.id, key, value);
1201
+ updateTrialConfig(record.id, key, value);
1196
1202
  bus.emit("config.change");
1197
1203
  bus.emit(evt);
1198
1204
  },
1199
- workspace: {
1205
+ trial: {
1200
1206
  id: record.id,
1201
1207
  zoom: record.view.zoom,
1202
- setZoom: (z) => updateWorkspaceView(record.id, { ...record.view, zoom: z })
1208
+ setZoom: (z) => updateTrialView(record.id, { ...record.view, zoom: z })
1203
1209
  },
1204
1210
  emit: (event) => {
1205
1211
  snapshotIfNeeded(event);
@@ -1212,14 +1218,14 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1212
1218
  undo: () => {
1213
1219
  const result = undo(record.undoStack, structuredClone(record.state));
1214
1220
  if (!result) return;
1215
- updateWorkspaceState(record.id, result.snapshot);
1216
- updateWorkspaceUndoStack(record.id, result.stack);
1221
+ updateTrialState(record.id, result.snapshot);
1222
+ updateTrialUndoStack(record.id, result.stack);
1217
1223
  },
1218
1224
  redo: () => {
1219
1225
  const result = redo(record.undoStack, structuredClone(record.state));
1220
1226
  if (!result) return;
1221
- updateWorkspaceState(record.id, result.snapshot);
1222
- updateWorkspaceUndoStack(record.id, result.stack);
1227
+ updateTrialState(record.id, result.snapshot);
1228
+ updateTrialUndoStack(record.id, result.stack);
1223
1229
  }
1224
1230
  } : void 0;
1225
1231
  const canvasLayers = useMemo(() => {
@@ -1229,7 +1235,11 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1229
1235
  return ordered.map((layer) => ({
1230
1236
  id: layer.id,
1231
1237
  visible: layerVisibility[layer.id] !== false,
1232
- render: (ctx, view) => layer.draw(ctx, { state: record.state, config: record.config, zoom: view.zoom })
1238
+ render: (ctx, view) => {
1239
+ ctx.translate(view.pan.x, view.pan.y);
1240
+ ctx.scale(view.zoom, view.zoom);
1241
+ layer.draw(ctx, { state: record.state, config: record.config, zoom: view.zoom });
1242
+ }
1233
1243
  }));
1234
1244
  }, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
1235
1245
  const layerDescriptors = useMemo(() => {
@@ -1244,7 +1254,7 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1244
1254
  config: record.config,
1245
1255
  setState: (next) => {
1246
1256
  snapshotIfNeeded("canvas.itemAdded");
1247
- updateWorkspaceState(record.id, next);
1257
+ updateTrialState(record.id, next);
1248
1258
  },
1249
1259
  emit: (evt) => {
1250
1260
  snapshotIfNeeded(evt);
@@ -1286,10 +1296,10 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1286
1296
  "div",
1287
1297
  {
1288
1298
  ref: canvasContainerRef,
1289
- className: "lk-workspace__canvas-host",
1299
+ className: "lk-trial__canvas-host",
1290
1300
  style: { width: "100%", height: "100%", position: "relative" },
1291
1301
  children: [
1292
- /* @__PURE__ */ jsx(CanvasStack, { layers: layersWithFeedback, view: record.view, onViewChange: setView }),
1302
+ /* @__PURE__ */ jsx(CanvasStack, { layers: layersWithFeedback, view: record.view, onViewChange: setView, children: instrument.render(renderCtx) }),
1293
1303
  /* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
1294
1304
  ]
1295
1305
  }
@@ -1314,12 +1324,12 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1314
1324
  }
1315
1325
  ) : null;
1316
1326
  return /* @__PURE__ */ jsx(
1317
- WorkspaceChrome,
1327
+ TrialChrome,
1318
1328
  {
1319
- workspaceId: record.id,
1329
+ trialId: record.id,
1320
1330
  record,
1321
1331
  instrument,
1322
- isLastWorkspace: isLast,
1332
+ isLastTrial: isLast,
1323
1333
  undoBindings,
1324
1334
  sidebarExtras: /* @__PURE__ */ jsxs(Fragment, { children: [
1325
1335
  paletteNode,
@@ -1330,7 +1340,7 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
1330
1340
  );
1331
1341
  }
1332
1342
 
1333
- // src/workspace/workspaceOps.ts
1343
+ // src/trial/trialOps.ts
1334
1344
  var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
1335
1345
  function findInstrument(instruments, name) {
1336
1346
  const found = instruments.find((i) => i.name === name);
@@ -1342,7 +1352,7 @@ function findInstrument(instruments, name) {
1342
1352
  function initialView(instrument) {
1343
1353
  return instrument.canvas?.initialView ? structuredClone(instrument.canvas.initialView) : { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
1344
1354
  }
1345
- function addWorkspace(workspaces, instruments, instrumentName) {
1355
+ function addTrial(trials, instruments, instrumentName) {
1346
1356
  const instrument = findInstrument(instruments, instrumentName);
1347
1357
  const config = instrument.defaultConfig();
1348
1358
  const state = instrument.initialState(config);
@@ -1354,12 +1364,12 @@ function addWorkspace(workspaces, instruments, instrumentName) {
1354
1364
  view: initialView(instrument),
1355
1365
  undoStack: { past: [], future: [] }
1356
1366
  };
1357
- return [...workspaces, record];
1367
+ return [...trials, record];
1358
1368
  }
1359
- function cloneWorkspace(workspaces, id) {
1360
- const sourceIdx = workspaces.findIndex((w) => w.id === id);
1361
- const source = workspaces[sourceIdx];
1362
- if (!source) return workspaces;
1369
+ function cloneTrial(trials, id) {
1370
+ const sourceIdx = trials.findIndex((w) => w.id === id);
1371
+ const source = trials[sourceIdx];
1372
+ if (!source) return trials;
1363
1373
  const clone = {
1364
1374
  ...source,
1365
1375
  id: crypto.randomUUID(),
@@ -1368,17 +1378,17 @@ function cloneWorkspace(workspaces, id) {
1368
1378
  view: structuredClone(source.view),
1369
1379
  undoStack: { past: [], future: [] }
1370
1380
  };
1371
- return [...workspaces.slice(0, sourceIdx + 1), clone, ...workspaces.slice(sourceIdx + 1)];
1381
+ return [...trials.slice(0, sourceIdx + 1), clone, ...trials.slice(sourceIdx + 1)];
1372
1382
  }
1373
- function closeWorkspace(workspaces, id) {
1374
- if (workspaces.length <= 1) return workspaces;
1375
- const next = workspaces.filter((w) => w.id !== id);
1376
- return next.length === workspaces.length ? workspaces : next;
1383
+ function closeTrial(trials, id) {
1384
+ if (trials.length <= 1) return trials;
1385
+ const next = trials.filter((w) => w.id !== id);
1386
+ return next.length === trials.length ? trials : next;
1377
1387
  }
1378
- function resetWorkspace(workspaces, id, instruments) {
1379
- const idx = workspaces.findIndex((w) => w.id === id);
1380
- const current = workspaces[idx];
1381
- if (!current) return workspaces;
1388
+ function resetTrial(trials, id, instruments) {
1389
+ const idx = trials.findIndex((w) => w.id === id);
1390
+ const current = trials[idx];
1391
+ if (!current) return trials;
1382
1392
  const instrument = findInstrument(instruments, current.instrumentName);
1383
1393
  const config = instrument.defaultConfig();
1384
1394
  const state = instrument.initialState(config);
@@ -1388,7 +1398,14 @@ function resetWorkspace(workspaces, id, instruments) {
1388
1398
  state,
1389
1399
  view: initialView(instrument)
1390
1400
  };
1391
- return [...workspaces.slice(0, idx), reset, ...workspaces.slice(idx + 1)];
1401
+ return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
1402
+ }
1403
+ function reorderTrials(trials, ids) {
1404
+ const byId = new Map(trials.map((w) => [w.id, w]));
1405
+ const named = ids.map((id) => byId.get(id)).filter((w) => w !== void 0);
1406
+ const seen = new Set(named.map((w) => w.id));
1407
+ const rest = trials.filter((w) => !seen.has(w.id));
1408
+ return [...named, ...rest];
1392
1409
  }
1393
1410
  function query() {
1394
1411
  if (typeof window === "undefined" || !window.matchMedia) return null;
@@ -1421,23 +1438,116 @@ function LabShell({ title, children, header, footer, mode = "auto" }) {
1421
1438
  ] });
1422
1439
  return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
1423
1440
  }
1424
- function WorkspaceGrid({ children }) {
1425
- const count = Children.count(children);
1426
- const { cols, rows } = gridDims(count);
1427
- const style = {
1428
- "--lk-grid-cols": String(cols),
1429
- "--lk-grid-rows": String(rows)
1430
- };
1431
- return /* @__PURE__ */ jsx("div", { className: "lk-workspace-grid", style, children });
1441
+ var ZONE_ID = asNodeId("lk-workspace");
1442
+ var STRATEGIES = { grid: gridStrategy };
1443
+ var KIND = "trial";
1444
+ function extentOf(store, id) {
1445
+ const p2 = store.getNode(id)?.membership?.placement;
1446
+ if (!p2) return null;
1447
+ const out = {};
1448
+ if (p2.size) out.size = p2.size;
1449
+ if (p2.span) out.span = p2.span;
1450
+ return Object.keys(out).length > 0 ? out : null;
1451
+ }
1452
+ function Workspace({
1453
+ children,
1454
+ ids,
1455
+ resizable = false,
1456
+ reorderable = false,
1457
+ onReorder,
1458
+ layout,
1459
+ onLayoutChange,
1460
+ gap = 12,
1461
+ padding = 0,
1462
+ viewport
1463
+ }) {
1464
+ const items = Children.toArray(children);
1465
+ const idKey = ids ? ids.join(",") : `#${items.length}`;
1466
+ const nodeIds = useMemo(() => items.map((_, i) => asNodeId(ids?.[i] ?? `lk-ws-${i}`)), [idKey]);
1467
+ const layoutRef = useRef(layout);
1468
+ layoutRef.current = layout;
1469
+ const onLayoutChangeRef = useRef(onLayoutChange);
1470
+ onLayoutChangeRef.current = onLayoutChange;
1471
+ const storeRef = useRef(null);
1472
+ if (storeRef.current === null) {
1473
+ const store2 = new Store();
1474
+ store2.registerNode(
1475
+ createNode({
1476
+ kind: "zone",
1477
+ id: ZONE_ID,
1478
+ container: { strategyId: "grid", config: { resizable, gap, padding } }
1479
+ })
1480
+ );
1481
+ storeRef.current = store2;
1482
+ }
1483
+ const store = storeRef.current;
1484
+ useLayoutEffect(() => {
1485
+ store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
1486
+ }, [store, resizable, gap, padding]);
1487
+ useLayoutEffect(() => {
1488
+ const present = new Set(store.getContainerView(ZONE_ID)?.childOrder ?? []);
1489
+ const wanted = new Set(nodeIds);
1490
+ for (const id of present) {
1491
+ if (!wanted.has(id)) store.unregisterNode(id);
1492
+ }
1493
+ for (const id of nodeIds) {
1494
+ if (present.has(id)) continue;
1495
+ store.registerNode(createNode({ kind: KIND, id, parentId: ZONE_ID, focus: true }));
1496
+ store.showNode(id);
1497
+ const saved = layoutRef.current?.[id];
1498
+ if (saved) store.patchPlacement(id, saved);
1499
+ }
1500
+ store.setChildOrder(ZONE_ID, [...nodeIds]);
1501
+ }, [store, nodeIds]);
1502
+ useEffect(() => {
1503
+ if (!onLayoutChange) return;
1504
+ return store.events.on("node.placementChanged", () => {
1505
+ const next = {};
1506
+ for (const child of store.getChildren(ZONE_ID)) {
1507
+ const extent = extentOf(store, child.id);
1508
+ if (extent) next[child.id] = extent;
1509
+ }
1510
+ onLayoutChangeRef.current?.(next);
1511
+ });
1512
+ }, [store, onLayoutChange]);
1513
+ const commitOrder = useCallback(
1514
+ (nextIds) => onReorder?.(nextIds.map(String)),
1515
+ [onReorder]
1516
+ );
1517
+ const chrome = useMemo(() => {
1518
+ const byId = new Map(nodeIds.map((id, i) => [id, items[i]]));
1519
+ return {
1520
+ [KIND]: ({ node }) => {
1521
+ const content = byId.get(node.id) ?? null;
1522
+ if (!reorderable) return content;
1523
+ return /* @__PURE__ */ jsxs("div", { className: "lk-trial-tile", children: [
1524
+ /* @__PURE__ */ jsx(DragHandle, { nodeId: node.id, className: "lk-trial-tile__grip", children: /* @__PURE__ */ jsx("span", { className: "lk-trial-tile__grip-dots", "aria-hidden": "true" }) }),
1525
+ /* @__PURE__ */ jsx("div", { className: "lk-trial-tile__body", children: content })
1526
+ ] });
1527
+ }
1528
+ };
1529
+ }, [nodeIds, items, reorderable]);
1530
+ const grid = /* @__PURE__ */ jsx(
1531
+ Container,
1532
+ {
1533
+ parentId: ZONE_ID,
1534
+ chrome,
1535
+ className: "lk-workspace windease-zone",
1536
+ affordances: resizable,
1537
+ viewport,
1538
+ onChildOrderChange: reorderable ? commitOrder : void 0
1539
+ }
1540
+ );
1541
+ return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
1432
1542
  }
1433
1543
  function buildStore(instruments, defaultInstrument, storage, storageKey, initialMode) {
1434
1544
  const store = createLabStore({ storageKey, storage, initialMode });
1435
- if (store.getState().workspaces.length === 0) {
1436
- const seeded = addWorkspace([], instruments, defaultInstrument);
1545
+ if (store.getState().trials.length === 0) {
1546
+ const seeded = addTrial([], instruments, defaultInstrument);
1437
1547
  const record = seeded[0];
1438
1548
  if (record) {
1439
1549
  const { undoStack: _undoStack, ...rest } = record;
1440
- store.getState().addWorkspace(rest);
1550
+ store.getState().addTrial(rest);
1441
1551
  }
1442
1552
  }
1443
1553
  return store;
@@ -1481,9 +1591,10 @@ function Lab({
1481
1591
  );
1482
1592
  }
1483
1593
  const store = storeRef.current;
1484
- const workspaces = useStore(store, (s) => s.workspaces);
1594
+ const trials = useStore(store, (s) => s.trials);
1485
1595
  const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
1486
1596
  const modeValue = useStore(store, (s) => s.mode);
1597
+ const layout = useStore(store, (s) => s.layout);
1487
1598
  const resolvedMode = useResolvedMode(modeValue);
1488
1599
  useEffect(() => {
1489
1600
  if (mode && mode !== store.getState().mode) {
@@ -1491,44 +1602,47 @@ function Lab({
1491
1602
  }
1492
1603
  }, [mode, store]);
1493
1604
  const contextValue = useMemo(() => {
1494
- const replaceWorkspaces = (next) => {
1495
- const currentById = new Map(store.getState().workspaces.map((w) => [w.id, w]));
1605
+ const replaceTrials = (next) => {
1606
+ const currentById = new Map(store.getState().trials.map((w) => [w.id, w]));
1496
1607
  const merged = next.map((w) => currentById.get(w.id) ?? w);
1497
- store.setState({ workspaces: merged });
1608
+ store.setState({ trials: merged });
1498
1609
  store.getState().setMode(store.getState().mode);
1499
1610
  };
1500
1611
  return {
1501
1612
  instruments,
1502
- workspaces,
1503
- addWorkspace: (instrumentName) => {
1504
- const next = addWorkspace(store.getState().workspaces, instruments, instrumentName);
1505
- replaceWorkspaces(next);
1613
+ trials,
1614
+ addTrial: (instrumentName) => {
1615
+ const next = addTrial(store.getState().trials, instruments, instrumentName);
1616
+ replaceTrials(next);
1506
1617
  },
1507
- cloneWorkspace: (id) => {
1508
- const next = cloneWorkspace(store.getState().workspaces, id);
1509
- replaceWorkspaces(next);
1618
+ cloneTrial: (id) => {
1619
+ const next = cloneTrial(store.getState().trials, id);
1620
+ replaceTrials(next);
1510
1621
  },
1511
- closeWorkspace: (id) => {
1512
- const next = closeWorkspace(store.getState().workspaces, id);
1513
- replaceWorkspaces(next);
1622
+ closeTrial: (id) => {
1623
+ const next = closeTrial(store.getState().trials, id);
1624
+ replaceTrials(next);
1514
1625
  },
1515
- resetWorkspace: (id) => {
1516
- const next = resetWorkspace(store.getState().workspaces, id, instruments);
1626
+ reorderTrials: (ids) => {
1627
+ replaceTrials(reorderTrials(store.getState().trials, ids));
1628
+ },
1629
+ resetTrial: (id) => {
1630
+ const next = resetTrial(store.getState().trials, id, instruments);
1517
1631
  const record = next.find((w) => w.id === id);
1518
1632
  if (!record) return;
1519
1633
  store.setState((s) => ({
1520
- workspaces: s.workspaces.map(
1634
+ trials: s.trials.map(
1521
1635
  (w) => w.id === id ? { ...w, config: record.config, state: record.state, view: record.view } : w
1522
1636
  )
1523
1637
  }));
1524
- store.getState().updateWorkspaceView(id, record.view);
1638
+ store.getState().updateTrialView(id, record.view);
1525
1639
  },
1526
1640
  savedSnapshots,
1527
- saveSnapshot: (workspaceId, name) => {
1528
- store.getState().saveSnapshot(workspaceId, name ?? `Save ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
1641
+ saveSnapshot: (trialId, name) => {
1642
+ store.getState().saveSnapshot(trialId, name ?? `Save ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
1529
1643
  },
1530
- loadSnapshot: (workspaceId, snapshotId) => {
1531
- store.getState().loadSnapshot(snapshotId, workspaceId);
1644
+ loadSnapshot: (trialId, snapshotId) => {
1645
+ store.getState().loadSnapshot(snapshotId, trialId);
1532
1646
  },
1533
1647
  deleteSnapshot: (snapshotId) => {
1534
1648
  store.getState().deleteSnapshot(snapshotId);
@@ -1538,7 +1652,7 @@ function Lab({
1538
1652
  store.getState().setMode(m);
1539
1653
  }
1540
1654
  };
1541
- }, [instruments, workspaces, savedSnapshots, modeValue, store]);
1655
+ }, [instruments, trials, savedSnapshots, modeValue, store]);
1542
1656
  const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
1543
1657
  return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
1544
1658
  ThemeProvider,
@@ -1547,7 +1661,18 @@ function Lab({
1547
1661
  mode: resolvedMode,
1548
1662
  className: "lk-lab",
1549
1663
  style: backdropStyle,
1550
- children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(WorkspaceGrid, { children: workspaces.map((w) => /* @__PURE__ */ jsx(Workspace, { id: w.id }, w.id)) }) })
1664
+ children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(
1665
+ Workspace,
1666
+ {
1667
+ ids: trials.map((w) => w.id),
1668
+ resizable: true,
1669
+ reorderable: true,
1670
+ onReorder: (ids) => contextValue.reorderTrials(ids),
1671
+ layout,
1672
+ onLayoutChange: (next) => store.getState().setLayout(next),
1673
+ children: trials.map((w) => /* @__PURE__ */ jsx(Trial, { id: w.id }, w.id))
1674
+ }
1675
+ ) })
1551
1676
  }
1552
1677
  ) }) });
1553
1678
  }
@@ -1915,17 +2040,6 @@ function PropertyGroup({
1915
2040
  /* @__PURE__ */ jsx("div", { className: "lk-property-group__body", children })
1916
2041
  ] });
1917
2042
  }
1918
-
1919
- // src/ui/format.ts
1920
- var MINUS = "\u2212";
1921
- function formatNumber(n, fractionDigits) {
1922
- if (!Number.isFinite(n)) return String(n);
1923
- const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
1924
- return s.startsWith("-") ? MINUS + s.slice(1) : s;
1925
- }
1926
- function parseSignedNumber(s) {
1927
- return Number(s.replace(MINUS, "-"));
1928
- }
1929
2043
  function PropertyPanel({ title, children, className }) {
1930
2044
  const cls = className ? `lk-property-panel ${className}` : "lk-property-panel";
1931
2045
  return /* @__PURE__ */ jsxs("div", { className: cls, children: [
@@ -2169,6 +2283,6 @@ function ToggleRow({
2169
2283
  }) }) });
2170
2284
  }
2171
2285
 
2172
- export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities, gridDims, interstellarTheme, resetWorkspace, useLabContext, validateConfigSchema };
2286
+ export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Trial, TrialChrome, Workspace, addTrial, cloneTrial, closeTrial, defineInstrument, detectCapabilities, interstellarTheme, reorderTrials, resetTrial, useLabContext, validateConfigSchema };
2173
2287
  //# sourceMappingURL=index.js.map
2174
2288
  //# sourceMappingURL=index.js.map