@bian-womp/spark-workbench 0.3.8 → 0.3.10

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 (33) hide show
  1. package/lib/cjs/index.cjs +88 -69
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
  4. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts +4 -2
  5. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  6. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  7. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  8. package/lib/cjs/src/misc/layout.d.ts.map +1 -1
  9. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +2 -2
  10. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/IGraphRunner.d.ts +1 -1
  12. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +1 -1
  14. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  15. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +1 -2
  16. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  17. package/lib/esm/index.js +90 -72
  18. package/lib/esm/index.js.map +1 -1
  19. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
  20. package/lib/esm/src/misc/WorkbenchCanvas.d.ts +4 -2
  21. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  22. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  23. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  24. package/lib/esm/src/misc/layout.d.ts.map +1 -1
  25. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +2 -2
  26. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  27. package/lib/esm/src/runtime/IGraphRunner.d.ts +1 -1
  28. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  29. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +1 -1
  30. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  31. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +1 -2
  32. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  33. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -161,6 +161,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
161
161
  }
162
162
  setRegistry(registry) {
163
163
  this._registry = registry;
164
+ this.refreshValidation();
164
165
  this.emit("registryChanged", { registry });
165
166
  }
166
167
  /**
@@ -1034,12 +1035,12 @@ class CLIWorkbench {
1034
1035
  }
1035
1036
 
1036
1037
  class AbstractGraphRunner {
1037
- constructor(registry, backend) {
1038
- this.registry = registry;
1038
+ constructor(backend, registry) {
1039
1039
  this.backend = backend;
1040
1040
  this.listeners = new Map();
1041
1041
  this.stagedInputs = {};
1042
1042
  this.runnerId = "";
1043
+ this.registry = registry ?? sparkGraph.createSimpleGraphRegistry();
1043
1044
  }
1044
1045
  async whenIdle() {
1045
1046
  await this.engine?.whenIdle();
@@ -1099,7 +1100,7 @@ class AbstractGraphRunner {
1099
1100
  let localRunnerCounter = 0;
1100
1101
  class LocalGraphRunner extends AbstractGraphRunner {
1101
1102
  constructor(registry) {
1102
- super(registry, { kind: "local" });
1103
+ super({ kind: "local" }, registry);
1103
1104
  this.extData = {};
1104
1105
  this.setEnvironment = (env, opts) => {
1105
1106
  if (!this.runtime)
@@ -1600,8 +1601,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1600
1601
  })();
1601
1602
  return this.clientPromise;
1602
1603
  }
1603
- constructor(registry, backend) {
1604
- super(registry, backend);
1604
+ constructor(backend) {
1605
+ super(backend);
1605
1606
  this.disposed = false;
1606
1607
  this.valueCache = new Map();
1607
1608
  this.listenersBound = false;
@@ -1890,7 +1891,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1890
1891
  return await client.api.snapshotFull();
1891
1892
  }
1892
1893
  catch {
1893
- return { def: undefined, environment: {}, inputs: {}, outputs: {} };
1894
+ return { inputs: {}, outputs: {} };
1894
1895
  }
1895
1896
  }
1896
1897
  async applySnapshotFull(payload, options) {
@@ -2192,7 +2193,34 @@ const HANDLE_SIZE_PX = 12;
2192
2193
  function computeEffectiveHandles(node, registry) {
2193
2194
  const desc = registry.nodes.get(node.typeId);
2194
2195
  const resolved = node.resolvedHandles || {};
2195
- const inputs = { ...desc?.inputs, ...resolved.inputs };
2196
+ // Merge inputs properly, handling metadata
2197
+ const inputs = {};
2198
+ // First, add all static handles
2199
+ if (desc?.inputs) {
2200
+ for (const [handle, staticDesc] of Object.entries(desc.inputs)) {
2201
+ inputs[handle] = staticDesc;
2202
+ }
2203
+ }
2204
+ // Then, merge dynamic handles (dynamic can override/extend static)
2205
+ if (resolved.inputs) {
2206
+ for (const [handle, dynamicDesc] of Object.entries(resolved.inputs)) {
2207
+ const staticDesc = desc?.inputs?.[handle];
2208
+ const merged = sparkGraph.mergeInputHandleDescriptors(staticDesc, dynamicDesc);
2209
+ if (merged) {
2210
+ inputs[handle] = merged;
2211
+ }
2212
+ }
2213
+ }
2214
+ // Finally, apply overrides from node definition
2215
+ if (node.resolvedHandles?.inputs) {
2216
+ for (const [handle, overrideDesc] of Object.entries(node.resolvedHandles.inputs)) {
2217
+ const existingDesc = inputs[handle];
2218
+ const merged = sparkGraph.mergeInputHandleDescriptors(existingDesc, overrideDesc);
2219
+ if (merged) {
2220
+ inputs[handle] = merged;
2221
+ }
2222
+ }
2223
+ }
2196
2224
  const outputs = { ...desc?.outputs, ...resolved.outputs };
2197
2225
  const inputDefaults = { ...desc?.inputDefaults, ...resolved.inputDefaults };
2198
2226
  return { inputs, outputs, inputDefaults };
@@ -4437,7 +4465,6 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4437
4465
  const offRunnerRegistry = runner.on("registry", async (newReg) => {
4438
4466
  try {
4439
4467
  wb.setRegistry(newReg);
4440
- // Increment registry version to trigger UI updates
4441
4468
  // Trigger a graph update so the UI revalidates with new types/enums/nodes
4442
4469
  try {
4443
4470
  await runner.update(wb.def);
@@ -5732,7 +5759,8 @@ const SelectionBoundOverlay = ({ selection, rfInstance }) => {
5732
5759
  } }));
5733
5760
  };
5734
5761
 
5735
- const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize, reactFlowProps }, ref) => {
5762
+ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
5763
+ const { showValues, toString, toElement, getDefaultNodeSize, reactFlowProps, } = props;
5736
5764
  const { wb, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, overrides, runNode, runFromHere, runMode, } = useWorkbenchContext();
5737
5765
  const nodeValidation = validationByNode;
5738
5766
  const edgeValidation = validationByEdge.errors;
@@ -5815,7 +5843,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5815
5843
  },
5816
5844
  }), []);
5817
5845
  const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb);
5818
- const ui = wb.getUI();
5846
+ const ui = React.useMemo(() => wb.getUI(), [wb, uiVersion]);
5819
5847
  const { nodeTypes, resolveNodeType } = React.useMemo(() => {
5820
5848
  // Build nodeTypes map using UI extension registry
5821
5849
  const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
@@ -5845,8 +5873,17 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5845
5873
  const customEdgeRenderer = ui.getEdgeRenderer();
5846
5874
  return { default: customEdgeRenderer || DefaultEdge };
5847
5875
  }, [uiVersion, ui]);
5848
- const { nodes, edges } = React.useMemo(() => {
5849
- const sel = wb.getSelection();
5876
+ // Track selection state to prevent unnecessary re-renders
5877
+ const [selection, setSelection] = React.useState(() => wb.getSelection());
5878
+ React.useEffect(() => {
5879
+ const off = wb.on("selectionChanged", (sel) => {
5880
+ setSelection({ nodes: sel.nodes, edges: sel.edges });
5881
+ });
5882
+ return () => off();
5883
+ }, [wb]);
5884
+ // Memoize customData to prevent unnecessary recomputations
5885
+ const customData = React.useMemo(() => wb.getCustomData(), [wb]);
5886
+ const rfData = React.useMemo(() => {
5850
5887
  const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), wb.registry, {
5851
5888
  showValues,
5852
5889
  inputs: inputsMap,
@@ -5859,11 +5896,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5859
5896
  edgeStatus,
5860
5897
  nodeValidation,
5861
5898
  edgeValidation,
5862
- selectedNodeIds: new Set(sel.nodes),
5863
- selectedEdgeIds: new Set(sel.edges),
5899
+ selectedNodeIds: new Set(selection.nodes),
5900
+ selectedEdgeIds: new Set(selection.edges),
5864
5901
  getDefaultNodeSize,
5865
5902
  ui,
5866
- customData: wb.getCustomData(),
5903
+ customData,
5867
5904
  });
5868
5905
  // Retain references for unchanged items
5869
5906
  const stableNodes = retainStabilityById(prevNodesRef.current, out.nodes, isSameNode);
@@ -5957,8 +5994,10 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5957
5994
  }, [
5958
5995
  showValues,
5959
5996
  inputsMap,
5997
+ inputDefaultsMap,
5960
5998
  outputsMap,
5961
5999
  valuesTick,
6000
+ registryVersion,
5962
6001
  toString,
5963
6002
  toElement,
5964
6003
  nodeStatus,
@@ -5966,8 +6005,14 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5966
6005
  nodeValidation,
5967
6006
  edgeValidation,
5968
6007
  resolveNodeType,
6008
+ selection,
6009
+ customData,
6010
+ ui,
6011
+ getDefaultNodeSize,
6012
+ wb,
6013
+ uiVersion,
5969
6014
  ]);
5970
- const throttled = useThrottledValue({ nodes, edges }, 100);
6015
+ const throttled = useThrottledValue(rfData, 100);
5971
6016
  const [menuState, setMenuState] = React.useState(null);
5972
6017
  // Compute the rectangular screen-space bounds of the current selection
5973
6018
  const getSelectionScreenBounds = () => {
@@ -6178,7 +6223,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6178
6223
  }, [menuState, wb, wb.registry, registryVersion, outputTypesMap]);
6179
6224
  // Keyboard shortcuts configuration
6180
6225
  const enableKeyboardShortcuts = overrides?.enableKeyboardShortcuts !== false; // Default to true
6181
- const keyboardShortcuts = overrides?.keyboardShortcuts || {
6226
+ const keyboardShortcuts = React.useMemo(() => overrides?.keyboardShortcuts || {
6182
6227
  undo: "⌘/Ctrl + Z",
6183
6228
  redo: "⌘/Ctrl + Shift + Z",
6184
6229
  copy: "⌘/Ctrl + C",
@@ -6187,7 +6232,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6187
6232
  duplicateWithEdges: "⌘/Ctrl + Shift + E",
6188
6233
  selectAll: "⌘/Ctrl + A",
6189
6234
  delete: "Delete",
6190
- };
6235
+ }, [overrides?.keyboardShortcuts]);
6191
6236
  // Toast notification for keyboard shortcuts
6192
6237
  const { toast, showToast, hideToast } = useKeyboardShortcutToast();
6193
6238
  // Keyboard shortcut handler
@@ -6245,8 +6290,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6245
6290
  const modKeyLabel = isMac ? "⌘" : "Ctrl";
6246
6291
  showToast(`Copy (${modKeyLabel} + C)`);
6247
6292
  // If single node selected, use node context menu handler; otherwise use selection handler
6248
- if (selection.nodes.length === 1 &&
6249
- nodeContextMenuHandlers?.onCopy) {
6293
+ if (selection.nodes.length === 1 && nodeContextMenuHandlers?.onCopy) {
6250
6294
  nodeContextMenuHandlers.onCopy();
6251
6295
  }
6252
6296
  else if (selectionContextMenuHandlers.onCopy) {
@@ -6384,8 +6428,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
6384
6428
  (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, ...(enableKeyboardShortcuts !== false
6385
6429
  ? { enableKeyboardShortcuts, keyboardShortcuts }
6386
6430
  : {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), menuState?.type === "selection" &&
6387
- (SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }), jsxRuntime.jsx(SelectionBoundOverlay, { selection: wb.getSelection(), rfInstance: rfInstanceRef.current })] }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6431
+ (SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }), jsxRuntime.jsx(SelectionBoundOverlay, { selection: selection, rfInstance: rfInstanceRef.current })] }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6388
6432
  });
6433
+ const WorkbenchCanvas = WorkbenchCanvasComponent;
6389
6434
 
6390
6435
  function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6391
6436
  const { wb, runner, selectedNodeId, runAutoLayout, runMode, setRunMode, isRunning, } = useWorkbenchContext();
@@ -6412,7 +6457,10 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6412
6457
  if (isConnecting) {
6413
6458
  return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-gray-500 border-gray-400 flex items-center gap-1 disabled:opacity-50", disabled: true, title: "Connecting to backend...", children: [jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 16, className: "animate-spin" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Connecting..." })] }));
6414
6459
  }
6415
- return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-green-700 border-green-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300 disabled:opacity-50", onClick: (evt) => {
6460
+ if (isGraphRunning) {
6461
+ return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-white border-none bg-green-500 flex items-center gap-1 cursor-default pointer-events-none", title: "Running", children: [jsxRuntime.jsx(react$1.RocketLaunchIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Running" })] }));
6462
+ }
6463
+ return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300 disabled:opacity-50", onClick: (evt) => {
6416
6464
  if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
6417
6465
  return;
6418
6466
  try {
@@ -6427,7 +6475,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6427
6475
  }
6428
6476
  }, disabled: !canControl, title: !canControl
6429
6477
  ? "Waiting for connection"
6430
- : `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsxRuntime.jsx(react$1.PlayIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6478
+ : `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsxRuntime.jsx(react$1.RocketIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6431
6479
  }, [transportStatus, isGraphRunning, runner, runMode, wb, backendKind]);
6432
6480
  const defaultExamples = React.useMemo(() => [
6433
6481
  {
@@ -6471,10 +6519,6 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6471
6519
  const canvasRef = React.useRef(null);
6472
6520
  const canvasContainerRef = React.useRef(null);
6473
6521
  const uploadInputRef = React.useRef(null);
6474
- const [registryReady, setRegistryReady] = React.useState(() => {
6475
- // For local backends, registry is always ready
6476
- return backendKind === "local";
6477
- });
6478
6522
  // Expose init callback with setInitialGraph helper
6479
6523
  // Note: This runs whenever runner changes
6480
6524
  React.useEffect(() => {
@@ -6594,21 +6638,6 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6594
6638
  const off = runner.on("transport", setTransportStatus);
6595
6639
  return () => off();
6596
6640
  }, [runner]);
6597
- // Track registry readiness for remote backends
6598
- React.useEffect(() => {
6599
- // For local backends, registry is always ready
6600
- if (backendKind === "local") {
6601
- setRegistryReady(true);
6602
- return;
6603
- }
6604
- // Reset readiness when switching to remote backend
6605
- setRegistryReady(false);
6606
- // For remote backends, wait for registry event
6607
- const off = runner.on("registry", () => {
6608
- setRegistryReady(true);
6609
- });
6610
- return () => off();
6611
- }, [runner, backendKind]);
6612
6641
  React.useEffect(() => {
6613
6642
  if (isGraphRunning)
6614
6643
  return;
@@ -6806,56 +6835,45 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6806
6835
  } }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
6807
6836
  }
6808
6837
  function WorkbenchStudio({ example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
6809
- const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
6810
6838
  const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
6811
- // Store previous runner for cleanup
6812
- const prevRunnerRef = React.useRef(null);
6839
+ const backendKindToUse = React.useMemo(() => {
6840
+ if (!backendOptions)
6841
+ return "local";
6842
+ return backendKind;
6843
+ }, [backendOptions, backendKind]);
6813
6844
  const runner = React.useMemo(() => {
6814
6845
  let newRunner;
6815
- if (backendKind === "remote-http") {
6846
+ if (backendKindToUse === "remote-http" && backendOptions) {
6816
6847
  const backend = {
6817
6848
  kind: "remote-http",
6818
6849
  baseUrl: httpBaseUrl,
6819
6850
  connectOptions: backendOptions?.connectOptions,
6820
6851
  onCustomEvent: backendOptions?.onCustomEvent,
6821
6852
  };
6822
- newRunner = new RemoteGraphRunner(registry, backend);
6853
+ newRunner = new RemoteGraphRunner(backend);
6823
6854
  }
6824
- else if (backendKind === "remote-ws") {
6855
+ else if (backendKindToUse === "remote-ws" && backendOptions) {
6825
6856
  const backend = {
6826
6857
  kind: "remote-ws",
6827
6858
  url: wsUrl,
6828
6859
  connectOptions: backendOptions?.connectOptions,
6829
6860
  onCustomEvent: backendOptions?.onCustomEvent,
6830
6861
  };
6831
- newRunner = new RemoteGraphRunner(registry, backend);
6862
+ newRunner = new RemoteGraphRunner(backend);
6832
6863
  }
6833
6864
  else {
6834
- newRunner = new LocalGraphRunner(registry);
6865
+ newRunner = new LocalGraphRunner();
6835
6866
  }
6836
6867
  return newRunner;
6837
- }, [registry, backendKind, httpBaseUrl, wsUrl, backendOptions]);
6838
- // Dispose previous runner after commit; dispose current on unmount
6868
+ }, [backendKindToUse, httpBaseUrl, wsUrl, backendOptions]);
6869
+ // Dispose runner when it changes or component unmounts
6839
6870
  React.useEffect(() => {
6840
- const previous = prevRunnerRef.current;
6841
- prevRunnerRef.current = runner;
6842
- if (previous && previous !== runner) {
6871
+ return () => {
6843
6872
  try {
6844
- previous.dispose();
6873
+ runner.dispose();
6845
6874
  }
6846
6875
  catch (err) {
6847
- console.warn("Error disposing previous runner:", err);
6848
- }
6849
- }
6850
- return () => {
6851
- // Only dispose if this runner is still the current one
6852
- if (prevRunnerRef.current === runner) {
6853
- try {
6854
- runner.dispose();
6855
- }
6856
- catch (err) {
6857
- console.warn("Error disposing runner on unmount:", err);
6858
- }
6876
+ console.warn("Error disposing runner:", err);
6859
6877
  }
6860
6878
  };
6861
6879
  }, [runner]);
@@ -6875,7 +6893,7 @@ function WorkbenchStudio({ example, onExampleChange, backendKind, onBackendKindC
6875
6893
  runner.dispose();
6876
6894
  onBackendKindChange(v);
6877
6895
  }, [isGraphRunning]);
6878
- return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
6896
+ return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, backendKind: backendKindToUse, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
6879
6897
  }
6880
6898
 
6881
6899
  exports.AbstractWorkbench = AbstractWorkbench;
@@ -6891,6 +6909,7 @@ exports.LocalGraphRunner = LocalGraphRunner;
6891
6909
  exports.NodeHandles = NodeHandles;
6892
6910
  exports.RemoteGraphRunner = RemoteGraphRunner;
6893
6911
  exports.WorkbenchCanvas = WorkbenchCanvas;
6912
+ exports.WorkbenchCanvasComponent = WorkbenchCanvasComponent;
6894
6913
  exports.WorkbenchContext = WorkbenchContext;
6895
6914
  exports.WorkbenchProvider = WorkbenchProvider;
6896
6915
  exports.WorkbenchStudio = WorkbenchStudio;