@bian-womp/spark-workbench 0.2.26 → 0.2.27

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.
package/lib/cjs/index.cjs CHANGED
@@ -1647,6 +1647,16 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
1647
1647
  const [edgeStatus, setEdgeStatus] = React.useState({});
1648
1648
  const [events, setEvents] = React.useState([]);
1649
1649
  const clearEvents = React.useCallback(() => setEvents([]), []);
1650
+ const [systemErrors, setSystemErrors] = React.useState([]);
1651
+ const [registryErrors, setRegistryErrors] = React.useState([]);
1652
+ const clearSystemErrors = React.useCallback(() => setSystemErrors([]), []);
1653
+ const clearRegistryErrors = React.useCallback(() => setRegistryErrors([]), []);
1654
+ const removeSystemError = React.useCallback((index) => {
1655
+ setSystemErrors((prev) => prev.filter((_, idx) => idx !== index));
1656
+ }, []);
1657
+ const removeRegistryError = React.useCallback((index) => {
1658
+ setRegistryErrors((prev) => prev.filter((_, idx) => idx !== index));
1659
+ }, []);
1650
1660
  // Fallback progress animation: drive progress to 100% over ~2 minutes
1651
1661
  const FALLBACK_TOTAL_MS = 2 * 60 * 1000;
1652
1662
  const [fallbackStarts, setFallbackStarts] = React.useState({});
@@ -1829,7 +1839,10 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
1829
1839
  if (remoteDef && Array.isArray(remoteDef.nodes)) {
1830
1840
  // Mutate current def in-place to avoid emitting graphChanged and causing update loop
1831
1841
  const cur = wb.export();
1832
- const byId = new Map((remoteDef.nodes || []).map((n) => [n.nodeId, n]));
1842
+ const byId = new Map((remoteDef.nodes || []).map((n) => [
1843
+ n.nodeId,
1844
+ n,
1845
+ ]));
1833
1846
  let changed = false;
1834
1847
  for (const n of cur.nodes) {
1835
1848
  const rn = byId.get(n.nodeId);
@@ -1861,6 +1874,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
1861
1874
  const off2 = runner.on("error", (e) => {
1862
1875
  const edgeError = e;
1863
1876
  const nodeError = e;
1877
+ const registryError = e;
1878
+ const systemError = e;
1864
1879
  if (edgeError.kind === "edge-convert") {
1865
1880
  const edgeId = edgeError.edgeId;
1866
1881
  setEdgeStatus((s) => ({
@@ -1868,7 +1883,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
1868
1883
  [edgeId]: { ...s[edgeId], lastError: edgeError.err },
1869
1884
  }));
1870
1885
  }
1871
- else if (nodeError.nodeId) {
1886
+ else if (nodeError.kind === "node-run" && nodeError.nodeId) {
1872
1887
  const nodeId = nodeError.nodeId;
1873
1888
  const runId = nodeError.runId;
1874
1889
  setNodeStatus((s) => ({
@@ -1886,6 +1901,27 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
1886
1901
  };
1887
1902
  }
1888
1903
  }
1904
+ else if (registryError.kind === "registry") {
1905
+ // Track registry errors for UI display
1906
+ setRegistryErrors((prev) => {
1907
+ // Avoid duplicates by checking message
1908
+ if (prev.some((err) => err.message === registryError.message)) {
1909
+ return prev;
1910
+ }
1911
+ return [...prev, registryError];
1912
+ });
1913
+ }
1914
+ else if (systemError.kind === "system") {
1915
+ // Track custom errors for UI display
1916
+ setSystemErrors((prev) => {
1917
+ // Avoid duplicates by checking message and code
1918
+ if (prev.some((err) => err.message === systemError.message &&
1919
+ err.code === systemError.code)) {
1920
+ return prev;
1921
+ }
1922
+ return [...prev, systemError];
1923
+ });
1924
+ }
1889
1925
  return add("runner", "error")(e);
1890
1926
  });
1891
1927
  const off3 = runner.on("invalidate", (e) => {
@@ -2133,6 +2169,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
2133
2169
  validationGlobal,
2134
2170
  events,
2135
2171
  clearEvents,
2172
+ systemErrors,
2173
+ registryErrors,
2174
+ clearSystemErrors,
2175
+ clearRegistryErrors,
2176
+ removeSystemError,
2177
+ removeRegistryError,
2136
2178
  isRunning,
2137
2179
  engineKind,
2138
2180
  start,
@@ -2154,6 +2196,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
2154
2196
  nodeStatus,
2155
2197
  edgeStatus,
2156
2198
  valuesTick,
2199
+ systemErrors,
2200
+ registryErrors,
2201
+ clearSystemErrors,
2202
+ clearRegistryErrors,
2203
+ removeSystemError,
2204
+ removeRegistryError,
2157
2205
  inputsMap,
2158
2206
  outputsMap,
2159
2207
  validationByNode,
@@ -2220,7 +2268,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2220
2268
  return String(value ?? "");
2221
2269
  }
2222
2270
  };
2223
- const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, } = useWorkbenchContext();
2271
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2224
2272
  const nodeValidationIssues = validationByNode.issues;
2225
2273
  const edgeValidationIssues = validationByEdge.issues;
2226
2274
  const nodeValidationHandles = validationByNode;
@@ -2306,7 +2354,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2306
2354
  }
2307
2355
  catch { }
2308
2356
  };
2309
- return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2357
+ return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), systemErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [systemErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: err.code ? `Error ${err.code}` : "Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeSystemError(i), title: "Dismiss", children: "\u00D7" })] }, i))), systemErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearSystemErrors, children: "Clear all" }))] })), registryErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [registryErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Registry Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), err.attempt && err.maxAttempts && (jsxRuntime.jsxs("div", { className: "text-[10px] text-amber-600 mt-1", children: ["Attempt ", err.attempt, " of ", err.maxAttempts] }))] }), jsxRuntime.jsx("button", { className: "text-amber-500 hover:text-amber-700 text-[10px] px-1", onClick: () => removeRegistryError(i), title: "Dismiss", children: "\u00D7" })] }, i))), registryErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-amber-600 hover:text-amber-800 underline", onClick: clearRegistryErrors, children: "Clear all" }))] })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2310
2358
  e.stopPropagation();
2311
2359
  deleteEdgeById(m.data?.edgeId);
2312
2360
  }, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
@@ -3156,11 +3204,8 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3156
3204
  return backendKind === "local";
3157
3205
  });
3158
3206
  // Expose init callback with setInitialGraph helper
3159
- const initCalled = React.useRef(false);
3207
+ // Note: This runs whenever runner changes (e.g., when Flow is enabled and backendOptions changes)
3160
3208
  React.useEffect(() => {
3161
- if (initCalled.current)
3162
- return;
3163
- initCalled.current = true;
3164
3209
  if (!onInit)
3165
3210
  return;
3166
3211
  const setInitialGraph = async (d, inputs) => {
@@ -3606,7 +3651,19 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3606
3651
  function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
3607
3652
  const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
3608
3653
  const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
3654
+ // Store previous runner for cleanup
3655
+ const prevRunnerRef = React.useRef(null);
3609
3656
  const runner = React.useMemo(() => {
3657
+ // Dispose previous runner if it exists
3658
+ if (prevRunnerRef.current) {
3659
+ try {
3660
+ prevRunnerRef.current.dispose();
3661
+ }
3662
+ catch (err) {
3663
+ console.warn("Error disposing previous runner:", err);
3664
+ }
3665
+ }
3666
+ let newRunner;
3610
3667
  if (backendKind === "remote-http") {
3611
3668
  const backend = {
3612
3669
  kind: "remote-http",
@@ -3618,9 +3675,9 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
3618
3675
  onCustomEvent: backendOptions.onCustomEvent,
3619
3676
  }),
3620
3677
  };
3621
- return new RemoteGraphRunner(registry, backend);
3678
+ newRunner = new RemoteGraphRunner(registry, backend);
3622
3679
  }
3623
- if (backendKind === "remote-ws") {
3680
+ else if (backendKind === "remote-ws") {
3624
3681
  const backend = {
3625
3682
  kind: "remote-ws",
3626
3683
  url: wsUrl,
@@ -3631,10 +3688,27 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
3631
3688
  onCustomEvent: backendOptions.onCustomEvent,
3632
3689
  }),
3633
3690
  };
3634
- return new RemoteGraphRunner(registry, backend);
3691
+ newRunner = new RemoteGraphRunner(registry, backend);
3692
+ }
3693
+ else {
3694
+ newRunner = new LocalGraphRunner(registry);
3635
3695
  }
3636
- return new LocalGraphRunner(registry);
3696
+ prevRunnerRef.current = newRunner;
3697
+ return newRunner;
3637
3698
  }, [registry, backendKind, httpBaseUrl, wsUrl, backendOptions]);
3699
+ // Cleanup runner on unmount
3700
+ React.useEffect(() => {
3701
+ return () => {
3702
+ if (prevRunnerRef.current) {
3703
+ try {
3704
+ prevRunnerRef.current.dispose();
3705
+ }
3706
+ catch (err) {
3707
+ console.warn("Error disposing runner on unmount:", err);
3708
+ }
3709
+ }
3710
+ };
3711
+ }, []);
3638
3712
  // Allow external UI registration (e.g., node renderers) with access to wb
3639
3713
  React.useEffect(() => {
3640
3714
  const baseRegisterUI = (_wb) => { };