@bian-womp/spark-workbench 0.2.28 → 0.2.30

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
@@ -1677,7 +1677,7 @@ function useWorkbenchContext() {
1677
1677
  return ctx;
1678
1678
  }
1679
1679
 
1680
- function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, children, }) {
1680
+ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVersion, children, }) {
1681
1681
  const [nodeStatus, setNodeStatus] = React.useState({});
1682
1682
  const [edgeStatus, setEdgeStatus] = React.useState({});
1683
1683
  const [events, setEvents] = React.useState([]);
@@ -2219,6 +2219,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
2219
2219
  runAutoLayout,
2220
2220
  updateEdgeType,
2221
2221
  triggerExternal,
2222
+ uiVersion,
2222
2223
  }), [
2223
2224
  wb,
2224
2225
  runner,
@@ -2253,6 +2254,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
2253
2254
  runAutoLayout,
2254
2255
  wb,
2255
2256
  runner,
2257
+ uiVersion,
2256
2258
  ]);
2257
2259
  return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
2258
2260
  }
@@ -2947,7 +2949,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2947
2949
  }
2948
2950
 
2949
2951
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
2950
- const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, } = useWorkbenchContext();
2952
+ const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
2951
2953
  const nodeValidation = validationByNode;
2952
2954
  const edgeValidation = validationByEdge.errors;
2953
2955
  // Keep stable references for nodes/edges to avoid unnecessary updates
@@ -3017,8 +3019,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3017
3019
  const { nodeTypes, resolveNodeType } = React.useMemo(() => {
3018
3020
  // Build nodeTypes map using UI extension registry
3019
3021
  const ui = wb.getUI();
3020
- const custom = new Map();
3021
- for (const typeId of Array.from(registry.nodes.keys())) {
3022
+ const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
3023
+ const def = wb.export();
3024
+ const ids = new Set([
3025
+ ...Array.from(registry.nodes.keys()),
3026
+ ...def.nodes.map((n) => n.typeId),
3027
+ ]);
3028
+ for (const typeId of ids) {
3022
3029
  const renderer = ui.getNodeRenderer(typeId);
3023
3030
  if (renderer)
3024
3031
  custom.set(typeId, renderer);
@@ -3032,8 +3039,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3032
3039
  }
3033
3040
  const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default";
3034
3041
  return { nodeTypes: types, resolveNodeType: resolver };
3035
- // registry is stable; ui renderers expected to be set up before mount
3036
- }, [wb, registry]);
3042
+ // Include uiVersion to recompute when custom renderers are registered
3043
+ }, [wb, registry, uiVersion]);
3037
3044
  const { nodes, edges } = React.useMemo(() => {
3038
3045
  const def = wb.export();
3039
3046
  const sel = wb.getSelection();
@@ -3073,7 +3080,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3073
3080
  })
3074
3081
  .map((n) => n.id);
3075
3082
  // Detect handle updates (ids/length changes) for targeted debug
3076
- const toIds = (arr) => Array.isArray(arr) ? arr.map((h) => (h && typeof h === "object" && "id" in h ? String(h.id) : "")).filter(Boolean) : [];
3083
+ const toIds = (arr) => Array.isArray(arr)
3084
+ ? arr
3085
+ .map((h) => h && typeof h === "object" && "id" in h ? String(h.id) : "")
3086
+ .filter(Boolean)
3087
+ : [];
3077
3088
  const handlesEqual = (a, b) => {
3078
3089
  const aIds = toIds(a);
3079
3090
  const bIds = toIds(b);
@@ -3699,15 +3710,6 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
3699
3710
  // Store previous runner for cleanup
3700
3711
  const prevRunnerRef = React.useRef(null);
3701
3712
  const runner = React.useMemo(() => {
3702
- // Dispose previous runner if it exists
3703
- if (prevRunnerRef.current) {
3704
- try {
3705
- prevRunnerRef.current.dispose();
3706
- }
3707
- catch (err) {
3708
- console.warn("Error disposing previous runner:", err);
3709
- }
3710
- }
3711
3713
  let newRunner;
3712
3714
  if (backendKind === "remote-http") {
3713
3715
  const backend = {
@@ -3738,29 +3740,43 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
3738
3740
  else {
3739
3741
  newRunner = new LocalGraphRunner(registry);
3740
3742
  }
3741
- prevRunnerRef.current = newRunner;
3742
3743
  return newRunner;
3743
3744
  }, [registry, backendKind, httpBaseUrl, wsUrl, backendOptions]);
3744
- // Cleanup runner on unmount
3745
+ // Dispose previous runner after commit; dispose current on unmount
3745
3746
  React.useEffect(() => {
3747
+ const previous = prevRunnerRef.current;
3748
+ prevRunnerRef.current = runner;
3749
+ if (previous && previous !== runner) {
3750
+ try {
3751
+ previous.dispose();
3752
+ }
3753
+ catch (err) {
3754
+ console.warn("Error disposing previous runner:", err);
3755
+ }
3756
+ }
3746
3757
  return () => {
3747
- if (prevRunnerRef.current) {
3758
+ // Only dispose if this runner is still the current one
3759
+ if (prevRunnerRef.current === runner) {
3748
3760
  try {
3749
- prevRunnerRef.current.dispose();
3761
+ runner.dispose();
3750
3762
  }
3751
3763
  catch (err) {
3752
3764
  console.warn("Error disposing runner on unmount:", err);
3753
3765
  }
3754
3766
  }
3755
3767
  };
3756
- }, []);
3768
+ }, [runner]);
3769
+ // Track UI registration version to trigger nodeTypes recomputation
3770
+ const [uiVersion, setUiVersion] = React.useState(0);
3757
3771
  // Allow external UI registration (e.g., node renderers) with access to wb
3758
3772
  React.useEffect(() => {
3759
3773
  const baseRegisterUI = (_wb) => { };
3760
3774
  overrides?.registerUI?.(baseRegisterUI, { wb, wbRunner: runner });
3775
+ // Increment UI version to trigger nodeTypes recomputation in WorkbenchCanvas
3776
+ setUiVersion((v) => v + 1);
3761
3777
  // eslint-disable-next-line react-hooks/exhaustive-deps
3762
3778
  }, [wb, runner, overrides]);
3763
- return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: (v) => {
3779
+ return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: (v) => {
3764
3780
  if (runner.isRunning())
3765
3781
  runner.dispose();
3766
3782
  onBackendKindChange(v);