@bian-womp/spark-workbench 0.3.70 → 0.3.72

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 (81) hide show
  1. package/lib/cjs/index.cjs +143 -352
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/adapters/cli/index.d.ts.map +1 -1
  4. package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
  5. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
  6. package/lib/cjs/src/core/contracts.d.ts.map +1 -1
  7. package/lib/cjs/src/core/ui-extensions.d.ts.map +1 -1
  8. package/lib/cjs/src/examples/reactflow/App.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/DebugEvents.d.ts.map +1 -1
  10. package/lib/cjs/src/misc/DefaultEdge.d.ts.map +1 -1
  11. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  12. package/lib/cjs/src/misc/DefaultNodeContent.d.ts +1 -1
  13. package/lib/cjs/src/misc/DefaultNodeContent.d.ts.map +1 -1
  14. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts.map +1 -1
  15. package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
  16. package/lib/cjs/src/misc/IssueBadge.d.ts.map +1 -1
  17. package/lib/cjs/src/misc/KeyboardShortcutToast.d.ts +1 -1
  18. package/lib/cjs/src/misc/KeyboardShortcutToast.d.ts.map +1 -1
  19. package/lib/cjs/src/misc/NodeHandles.d.ts.map +1 -1
  20. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  21. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  22. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  23. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  24. package/lib/cjs/src/misc/context-menu/ContextMenuButton.d.ts +1 -1
  25. package/lib/cjs/src/misc/context-menu/ContextMenuButton.d.ts.map +1 -1
  26. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  27. package/lib/cjs/src/misc/context-menu/DefaultContextMenu.d.ts.map +1 -1
  28. package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  29. package/lib/cjs/src/misc/context-menu/SelectionContextMenu.d.ts.map +1 -1
  30. package/lib/cjs/src/misc/hooks.d.ts.map +1 -1
  31. package/lib/cjs/src/misc/layout.d.ts.map +1 -1
  32. package/lib/cjs/src/misc/load.d.ts.map +1 -1
  33. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  34. package/lib/cjs/src/misc/merge-utils.d.ts.map +1 -1
  35. package/lib/cjs/src/misc/thumbnail-utils.d.ts.map +1 -1
  36. package/lib/cjs/src/misc/value.d.ts.map +1 -1
  37. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  38. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  39. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  40. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  41. package/lib/esm/index.js +143 -352
  42. package/lib/esm/index.js.map +1 -1
  43. package/lib/esm/src/adapters/cli/index.d.ts.map +1 -1
  44. package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
  45. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
  46. package/lib/esm/src/core/contracts.d.ts.map +1 -1
  47. package/lib/esm/src/core/ui-extensions.d.ts.map +1 -1
  48. package/lib/esm/src/examples/reactflow/App.d.ts.map +1 -1
  49. package/lib/esm/src/misc/DebugEvents.d.ts.map +1 -1
  50. package/lib/esm/src/misc/DefaultEdge.d.ts.map +1 -1
  51. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  52. package/lib/esm/src/misc/DefaultNodeContent.d.ts +1 -1
  53. package/lib/esm/src/misc/DefaultNodeContent.d.ts.map +1 -1
  54. package/lib/esm/src/misc/DefaultNodeHeader.d.ts.map +1 -1
  55. package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
  56. package/lib/esm/src/misc/IssueBadge.d.ts.map +1 -1
  57. package/lib/esm/src/misc/KeyboardShortcutToast.d.ts +1 -1
  58. package/lib/esm/src/misc/KeyboardShortcutToast.d.ts.map +1 -1
  59. package/lib/esm/src/misc/NodeHandles.d.ts.map +1 -1
  60. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  61. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  62. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  63. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  64. package/lib/esm/src/misc/context-menu/ContextMenuButton.d.ts +1 -1
  65. package/lib/esm/src/misc/context-menu/ContextMenuButton.d.ts.map +1 -1
  66. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  67. package/lib/esm/src/misc/context-menu/DefaultContextMenu.d.ts.map +1 -1
  68. package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  69. package/lib/esm/src/misc/context-menu/SelectionContextMenu.d.ts.map +1 -1
  70. package/lib/esm/src/misc/hooks.d.ts.map +1 -1
  71. package/lib/esm/src/misc/layout.d.ts.map +1 -1
  72. package/lib/esm/src/misc/load.d.ts.map +1 -1
  73. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  74. package/lib/esm/src/misc/merge-utils.d.ts.map +1 -1
  75. package/lib/esm/src/misc/thumbnail-utils.d.ts.map +1 -1
  76. package/lib/esm/src/misc/value.d.ts.map +1 -1
  77. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  78. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  79. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  80. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  81. package/package.json +6 -4
package/lib/esm/index.js CHANGED
@@ -194,13 +194,11 @@ class InMemoryWorkbench extends AbstractWorkbench {
194
194
  // Clean up extData for removed nodes/edges
195
195
  if (this.customData.nodes) {
196
196
  const filteredExtNodes = Object.fromEntries(Object.entries(this.customData.nodes).filter(([id]) => defNodeIds.has(id)));
197
- this.customData.nodes =
198
- Object.keys(filteredExtNodes).length > 0 ? filteredExtNodes : undefined;
197
+ this.customData.nodes = Object.keys(filteredExtNodes).length > 0 ? filteredExtNodes : undefined;
199
198
  }
200
199
  if (this.customData.edges) {
201
200
  const filteredExtEdges = Object.fromEntries(Object.entries(this.customData.edges).filter(([id]) => defEdgeIds.has(id)));
202
- this.customData.edges =
203
- Object.keys(filteredExtEdges).length > 0 ? filteredExtEdges : undefined;
201
+ this.customData.edges = Object.keys(filteredExtEdges).length > 0 ? filteredExtEdges : undefined;
204
202
  }
205
203
  this.positions = filteredPositions;
206
204
  this.sizes = filteredSizes;
@@ -253,8 +251,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
253
251
  this.refreshValidation();
254
252
  }
255
253
  addNode(node, options) {
256
- const id = node.nodeId ??
257
- this.genId("n", new Set(this._def.nodes.map((n) => n.nodeId)));
254
+ const id = node.nodeId ?? this.genId("n", new Set(this._def.nodes.map((n) => n.nodeId)));
258
255
  this._def.nodes.push({
259
256
  nodeId: id,
260
257
  typeId: node.typeId,
@@ -441,9 +438,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
441
438
  const filteredNodeNames = Object.fromEntries(Object.entries(this.nodeNames).filter(([id]) => defNodeIds.has(id)));
442
439
  const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
443
440
  return {
444
- positions: Object.keys(filteredPositions).length > 0
445
- ? filteredPositions
446
- : undefined,
441
+ positions: Object.keys(filteredPositions).length > 0 ? filteredPositions : undefined,
447
442
  selection: filteredNodes.length > 0 || filteredEdges.length > 0
448
443
  ? {
449
444
  nodes: filteredNodes,
@@ -451,9 +446,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
451
446
  }
452
447
  : undefined,
453
448
  viewport: this.viewport ? { ...this.viewport } : undefined,
454
- nodeNames: Object.keys(filteredNodeNames).length > 0
455
- ? filteredNodeNames
456
- : undefined,
449
+ nodeNames: Object.keys(filteredNodeNames).length > 0 ? filteredNodeNames : undefined,
457
450
  sizes: Object.keys(filteredSizes).length > 0 ? filteredSizes : undefined,
458
451
  };
459
452
  }
@@ -598,17 +591,14 @@ class InMemoryWorkbench extends AbstractWorkbench {
598
591
  : undefined,
599
592
  size,
600
593
  inputs: inputsToCopy,
601
- customData: customNodeData !== undefined
602
- ? lod.cloneDeep(customNodeData)
603
- : undefined,
594
+ customData: customNodeData !== undefined ? lod.cloneDeep(customNodeData) : undefined,
604
595
  originalNodeId: node.nodeId,
605
596
  };
606
597
  });
607
598
  // Collect edges between copied nodes
608
599
  const copiedEdges = this.def.edges
609
600
  .filter((edge) => {
610
- return (selectedNodeSet.has(edge.source.nodeId) &&
611
- selectedNodeSet.has(edge.target.nodeId));
601
+ return selectedNodeSet.has(edge.source.nodeId) && selectedNodeSet.has(edge.target.nodeId);
612
602
  })
613
603
  .map((edge) => ({
614
604
  sourceNodeId: edge.source.nodeId,
@@ -1324,13 +1314,9 @@ class LocalGraphRunner extends AbstractGraphRunner {
1324
1314
  const out = {};
1325
1315
  for (const n of def.nodes) {
1326
1316
  const staged = this.stagedInputs[n.nodeId] ?? {};
1327
- const runtimeInputs = this.runtime
1328
- ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
1329
- : {};
1317
+ const runtimeInputs = this.runtime ? (this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}) : {};
1330
1318
  // Build inbound handle set for this node from current def
1331
- const inbound = new Set(def.edges
1332
- .filter((e) => e.target.nodeId === n.nodeId)
1333
- .map((e) => e.target.handle));
1319
+ const inbound = new Set(def.edges.filter((e) => e.target.nodeId === n.nodeId).map((e) => e.target.handle));
1334
1320
  // Merge staged only for non-inbound handles so UI reflects runtime values for wired inputs
1335
1321
  const merged = { ...runtimeInputs };
1336
1322
  for (const [h, v] of Object.entries(staged)) {
@@ -1386,9 +1372,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
1386
1372
  this.extData = { ...this.extData, ...data };
1387
1373
  }
1388
1374
  async updateExtData(updates) {
1389
- if (!this.extData ||
1390
- typeof this.extData !== "object" ||
1391
- Array.isArray(this.extData)) {
1375
+ if (!this.extData || typeof this.extData !== "object" || Array.isArray(this.extData)) {
1392
1376
  this.extData = {};
1393
1377
  }
1394
1378
  let hasCustomUpdate = false;
@@ -1715,10 +1699,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1715
1699
  // Wrap custom event handler to intercept viewport events and emit viewport event
1716
1700
  const wrappedOnCustomEvent = (event) => {
1717
1701
  const msg = event?.message;
1718
- if (msg &&
1719
- typeof msg === "object" &&
1720
- "type" in msg &&
1721
- msg.type === "viewport") {
1702
+ if (msg && typeof msg === "object" && "type" in msg && msg.type === "viewport") {
1722
1703
  const viewport = msg.payload?.viewport;
1723
1704
  if (isValidViewport(viewport)) {
1724
1705
  this.emit("viewport", { viewport });
@@ -2161,9 +2142,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
2161
2142
  const handles = Object.keys(resolved ?? desc?.inputs ?? {});
2162
2143
  const cur = {};
2163
2144
  // Build inbound handle set for this node to honor wiring precedence
2164
- const inbound = new Set(def.edges
2165
- .filter((e) => e.target.nodeId === n.nodeId)
2166
- .map((e) => e.target.handle));
2145
+ const inbound = new Set(def.edges.filter((e) => e.target.nodeId === n.nodeId).map((e) => e.target.handle));
2167
2146
  for (const h of handles) {
2168
2147
  const rec = cache.get(this.getCacheKey(n.nodeId, h, "input"));
2169
2148
  if (rec)
@@ -2336,9 +2315,7 @@ function summarizeDeep(value) {
2336
2315
  const out = {};
2337
2316
  for (const [k, v] of Object.entries(obj)) {
2338
2317
  // Special-case any 'url' field
2339
- if (typeof v === "string" &&
2340
- k.toLowerCase() === "url" &&
2341
- v.startsWith("data:")) {
2318
+ if (typeof v === "string" && k.toLowerCase() === "url" && v.startsWith("data:")) {
2342
2319
  out[k] = formatDataUrlAsLabel(v);
2343
2320
  continue;
2344
2321
  }
@@ -2413,19 +2390,14 @@ function estimateNodeSize(args) {
2413
2390
  * Used for positioning handles in React Flow.
2414
2391
  */
2415
2392
  function getHandleLayoutY(rowIndex) {
2416
- return (NODE_HEADER_HEIGHT_PX +
2417
- rowIndex * NODE_ROW_HEIGHT_PX +
2418
- NODE_ROW_HEIGHT_PX / 2);
2393
+ return NODE_HEADER_HEIGHT_PX + rowIndex * NODE_ROW_HEIGHT_PX + NODE_ROW_HEIGHT_PX / 2;
2419
2394
  }
2420
2395
  /**
2421
2396
  * Calculate the Y position for handle bounds (top + centering offset).
2422
2397
  * Used for hit-testing and edge routing.
2423
2398
  */
2424
2399
  function getHandleBoundsY(rowIndex) {
2425
- return (NODE_HEADER_HEIGHT_PX +
2426
- rowIndex * NODE_ROW_HEIGHT_PX +
2427
- (NODE_ROW_HEIGHT_PX - HANDLE_SIZE_PX) / 2 +
2428
- 1);
2400
+ return NODE_HEADER_HEIGHT_PX + rowIndex * NODE_ROW_HEIGHT_PX + (NODE_ROW_HEIGHT_PX - HANDLE_SIZE_PX) / 2 + 1;
2429
2401
  }
2430
2402
  /**
2431
2403
  * Calculate the X position for handle bounds based on position and node width.
@@ -2464,7 +2436,7 @@ function createHandleLayout(args) {
2464
2436
  };
2465
2437
  }
2466
2438
  function layoutNode(args, overrides) {
2467
- const { node, registry, showValues, overrides: sizeOverrides, missingInputs = [], missingOutputs = [], } = args;
2439
+ const { node, registry, showValues, overrides: sizeOverrides, missingInputs = [], missingOutputs = [] } = args;
2468
2440
  const { inputs, outputs } = computeEffectiveHandles(node, registry);
2469
2441
  const inputOrder = Object.keys(inputs).filter((k) => !isInputPrivate(inputs, k));
2470
2442
  const outputOrder = Object.keys(outputs);
@@ -2577,14 +2549,11 @@ function useWorkbenchBridge(wb, handlesMap) {
2577
2549
  // Check if target input handle is a non-array type (typeId doesn't end with [])
2578
2550
  // If so, remove any existing connections to that handle before connecting
2579
2551
  const targetHandles = handlesMap?.[params.target];
2580
- const targetTypeId = targetHandles
2581
- ? getInputTypeId(targetHandles.inputs, params.targetHandle)
2582
- : undefined;
2552
+ const targetTypeId = targetHandles ? getInputTypeId(targetHandles.inputs, params.targetHandle) : undefined;
2583
2553
  const isArrayInput = targetTypeId?.endsWith("[]") ?? false;
2584
2554
  if (!isArrayInput) {
2585
2555
  // Find existing edges to this input handle
2586
- const existingEdges = wb.def.edges.filter((e) => e.target.nodeId === params.target &&
2587
- e.target.handle === params.targetHandle);
2556
+ const existingEdges = wb.def.edges.filter((e) => e.target.nodeId === params.target && e.target.handle === params.targetHandle);
2588
2557
  // Remove existing edges (without committing yet)
2589
2558
  for (const edge of existingEdges) {
2590
2559
  wb.disconnect(edge.id, { commit: false });
@@ -2747,9 +2716,7 @@ function useThrottledValue(value, intervalMs) {
2747
2716
  const lastSetAtRef = useRef(0);
2748
2717
  const timeoutRef = useRef(null);
2749
2718
  useEffect(() => {
2750
- const now = typeof performance !== "undefined" && performance.now
2751
- ? performance.now()
2752
- : Date.now();
2719
+ const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
2753
2720
  const elapsed = now - lastSetAtRef.current;
2754
2721
  if (elapsed >= intervalMs) {
2755
2722
  lastSetAtRef.current = now;
@@ -2760,10 +2727,7 @@ function useThrottledValue(value, intervalMs) {
2760
2727
  window.clearTimeout(timeoutRef.current);
2761
2728
  }
2762
2729
  timeoutRef.current = window.setTimeout(() => {
2763
- lastSetAtRef.current =
2764
- typeof performance !== "undefined" && performance.now
2765
- ? performance.now()
2766
- : Date.now();
2730
+ lastSetAtRef.current = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
2767
2731
  setThrottled(value);
2768
2732
  timeoutRef.current = null;
2769
2733
  }, Math.max(0, intervalMs - elapsed));
@@ -2925,9 +2889,7 @@ function toReactFlow(def, positions, sizes, registry, opts) {
2925
2889
  const geom = computeLayout(n, sizeOverrides, extraInputs, extraOutputs);
2926
2890
  const renderWidth = customSize?.width ?? geom.width;
2927
2891
  const renderHeight = customSize?.height ?? geom.height;
2928
- const initialGeom = customSize
2929
- ? computeLayout(n, overrideSize, extraInputs, extraOutputs)
2930
- : geom;
2892
+ const initialGeom = customSize ? computeLayout(n, overrideSize, extraInputs, extraOutputs) : geom;
2931
2893
  const inputHandles = geom.inputOrder.map((id) => ({
2932
2894
  id,
2933
2895
  typeId: getInputTypeId(inputSource, id) ?? "unknown",
@@ -2948,10 +2910,7 @@ function toReactFlow(def, positions, sizes, registry, opts) {
2948
2910
  inputHandles,
2949
2911
  outputHandles,
2950
2912
  handles: opts.handles[n.nodeId],
2951
- inputConnected: Object.fromEntries(inputHandles.map((h) => [
2952
- h.id,
2953
- !!connectedInputs[n.nodeId]?.has(h.id),
2954
- ])),
2913
+ inputConnected: Object.fromEntries(inputHandles.map((h) => [h.id, !!connectedInputs[n.nodeId]?.has(h.id)])),
2955
2914
  handleLayout,
2956
2915
  showValues: opts.showValues,
2957
2916
  renderWidth,
@@ -2975,17 +2934,13 @@ function toReactFlow(def, positions, sizes, registry, opts) {
2975
2934
  toElement: opts.toElement,
2976
2935
  };
2977
2936
  const customNodeData = opts.customData?.nodes;
2978
- const mergedData = customNodeData?.[n.nodeId]
2979
- ? { ...baseData, custom: customNodeData[n.nodeId] }
2980
- : baseData;
2937
+ const mergedData = customNodeData?.[n.nodeId] ? { ...baseData, custom: customNodeData[n.nodeId] } : baseData;
2981
2938
  return {
2982
2939
  id: n.nodeId,
2983
2940
  data: mergedData,
2984
2941
  position: positions[n.nodeId] ?? { x: 0, y: 0 },
2985
2942
  type: opts.resolveNodeType?.(n.typeId) ?? "spark-default",
2986
- selected: opts.selectedNodeIds
2987
- ? opts.selectedNodeIds.has(n.nodeId)
2988
- : undefined,
2943
+ selected: opts.selectedNodeIds ? opts.selectedNodeIds.has(n.nodeId) : undefined,
2989
2944
  measured: {
2990
2945
  width: renderWidth,
2991
2946
  height: renderHeight,
@@ -3017,11 +2972,10 @@ function toReactFlow(def, positions, sizes, registry, opts) {
3017
2972
  const sourceNode = def.nodes.find((n) => n.nodeId === e.source.nodeId);
3018
2973
  const targetNode = def.nodes.find((n) => n.nodeId === e.target.nodeId);
3019
2974
  const sourceHandleTypeId = sourceHandles?.outputs[e.source.handle]
3020
- ? formatDeclaredTypeSignature(sourceHandles.outputs[e.source.handle]) ??
3021
- "unknown"
2975
+ ? (formatDeclaredTypeSignature(sourceHandles.outputs[e.source.handle]) ?? "unknown")
3022
2976
  : "unknown";
3023
2977
  const targetHandleTypeId = targetHandles?.inputs[e.target.handle]
3024
- ? getInputTypeId(targetHandles.inputs, e.target.handle) ?? "unknown"
2978
+ ? (getInputTypeId(targetHandles.inputs, e.target.handle) ?? "unknown")
3025
2979
  : "unknown";
3026
2980
  const baseEdgeData = {
3027
2981
  sourceNodeId: e.source.nodeId,
@@ -3039,18 +2993,14 @@ function toReactFlow(def, positions, sizes, registry, opts) {
3039
2993
  isMissing,
3040
2994
  };
3041
2995
  const customEdgeData = opts.customData?.edges;
3042
- const mergedEdgeData = customEdgeData?.[e.id]
3043
- ? { ...baseEdgeData, custom: customEdgeData[e.id] }
3044
- : baseEdgeData;
2996
+ const mergedEdgeData = customEdgeData?.[e.id] ? { ...baseEdgeData, custom: customEdgeData[e.id] } : baseEdgeData;
3045
2997
  return {
3046
2998
  id: e.id,
3047
2999
  source: e.source.nodeId,
3048
3000
  target: e.target.nodeId,
3049
3001
  sourceHandle: e.source.handle,
3050
3002
  targetHandle: e.target.handle,
3051
- selected: opts.selectedEdgeIds
3052
- ? opts.selectedEdgeIds.has(e.id)
3053
- : undefined,
3003
+ selected: opts.selectedEdgeIds ? opts.selectedEdgeIds.has(e.id) : undefined,
3054
3004
  animated: isRunning && !isMissing,
3055
3005
  style,
3056
3006
  label: edgeTypeId,
@@ -3073,13 +3023,7 @@ function getNodeBorderClassNames(args) {
3073
3023
  // Keep border width constant to avoid layout reflow on selection toggles
3074
3024
  const borderWidth = "border";
3075
3025
  const borderStyle = isInvalid ? "border-dashed" : "border-solid";
3076
- const severity = hasError || hasValidationError
3077
- ? "red"
3078
- : hasValidationWarning
3079
- ? "amber"
3080
- : isRunning
3081
- ? "blue"
3082
- : "gray";
3026
+ const severity = hasError || hasValidationError ? "red" : hasValidationWarning ? "amber" : isRunning ? "blue" : "gray";
3083
3027
  const borderBySeverity = {
3084
3028
  red: "border-red-500",
3085
3029
  amber: "border-amber-500",
@@ -3148,10 +3092,7 @@ function downloadJSON(payload, filename) {
3148
3092
  function isSnapshotPayload(parsed) {
3149
3093
  return (parsed !== null &&
3150
3094
  typeof parsed === "object" &&
3151
- ("def" in parsed ||
3152
- "inputs" in parsed ||
3153
- "outputs" in parsed ||
3154
- "environment" in parsed));
3095
+ ("def" in parsed || "inputs" in parsed || "outputs" in parsed || "environment" in parsed));
3155
3096
  }
3156
3097
  async function download(wb, runner) {
3157
3098
  try {
@@ -3257,18 +3198,13 @@ function mergeUIState(targetUI, sourceUI, nodeIdMap, anchorPos, sourceDef) {
3257
3198
  // Simple remapping without offset
3258
3199
  result.positions = {
3259
3200
  ...(targetUI?.positions || {}),
3260
- ...Object.fromEntries(Object.entries(sourceUI.positions).map(([oldId, pos]) => [
3261
- nodeIdMap[oldId] || oldId,
3262
- pos,
3263
- ])),
3201
+ ...Object.fromEntries(Object.entries(sourceUI.positions).map(([oldId, pos]) => [nodeIdMap[oldId] || oldId, pos])),
3264
3202
  };
3265
3203
  }
3266
3204
  }
3267
3205
  // Merge selection: remap node IDs and edge IDs
3268
3206
  if (sourceUI.selection) {
3269
- const remappedNodes = (sourceUI.selection.nodes || [])
3270
- .map((id) => nodeIdMap[id] || id)
3271
- .filter((id) => id); // Filter out invalid mappings
3207
+ const remappedNodes = (sourceUI.selection.nodes || []).map((id) => nodeIdMap[id] || id).filter((id) => id); // Filter out invalid mappings
3272
3208
  const remappedEdges = sourceUI.selection.edges || []; // Edge IDs don't need remapping typically
3273
3209
  result.selection = {
3274
3210
  nodes: [...(targetUI?.selection?.nodes || []), ...remappedNodes],
@@ -3279,19 +3215,13 @@ function mergeUIState(targetUI, sourceUI, nodeIdMap, anchorPos, sourceDef) {
3279
3215
  if (sourceUI.nodeNames) {
3280
3216
  result.nodeNames = {
3281
3217
  ...(targetUI?.nodeNames || {}),
3282
- ...Object.fromEntries(Object.entries(sourceUI.nodeNames).map(([oldId, name]) => [
3283
- nodeIdMap[oldId] || oldId,
3284
- name,
3285
- ])),
3218
+ ...Object.fromEntries(Object.entries(sourceUI.nodeNames).map(([oldId, name]) => [nodeIdMap[oldId] || oldId, name])),
3286
3219
  };
3287
3220
  }
3288
3221
  if (sourceUI.sizes) {
3289
3222
  result.sizes = {
3290
3223
  ...(targetUI?.sizes || {}),
3291
- ...Object.fromEntries(Object.entries(sourceUI.sizes).map(([oldId, size]) => [
3292
- nodeIdMap[oldId] || oldId,
3293
- size,
3294
- ])),
3224
+ ...Object.fromEntries(Object.entries(sourceUI.sizes).map(([oldId, size]) => [nodeIdMap[oldId] || oldId, size])),
3295
3225
  };
3296
3226
  }
3297
3227
  return result;
@@ -3326,9 +3256,7 @@ function parseViewportTransform(transform) {
3326
3256
  if (!translateMatch) {
3327
3257
  const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
3328
3258
  if (matrixMatch) {
3329
- const values = matrixMatch[1]
3330
- .split(",")
3331
- .map((v) => parseFloat(v.trim()));
3259
+ const values = matrixMatch[1].split(",").map((v) => parseFloat(v.trim()));
3332
3260
  if (values.length >= 6) {
3333
3261
  scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);
3334
3262
  translateX = values[4];
@@ -3385,9 +3313,7 @@ function parseBorderRadius(borderRadiusStr) {
3385
3313
  */
3386
3314
  function extractStrokeColor(element) {
3387
3315
  if (element instanceof SVGPathElement) {
3388
- return (element.getAttribute("stroke") ||
3389
- window.getComputedStyle(element).stroke ||
3390
- "#b1b1b7");
3316
+ return element.getAttribute("stroke") || window.getComputedStyle(element).stroke || "#b1b1b7";
3391
3317
  }
3392
3318
  const style = window.getComputedStyle(element);
3393
3319
  return (style.borderColor || style.borderTopColor || "#6b7280" // gray-500 default
@@ -3410,10 +3336,7 @@ function extractStrokeWidth(element, minWidth = 1) {
3410
3336
  * Checks if a rectangle intersects with visible bounds
3411
3337
  */
3412
3338
  function isRectVisible(x, y, width, height, bounds) {
3413
- return (x + width >= bounds.minX &&
3414
- x <= bounds.maxX &&
3415
- y + height >= bounds.minY &&
3416
- y <= bounds.maxY);
3339
+ return x + width >= bounds.minX && x <= bounds.maxX && y + height >= bounds.minY && y <= bounds.maxY;
3417
3340
  }
3418
3341
  /**
3419
3342
  * Parses path data to get bounding box
@@ -3529,8 +3452,7 @@ function extractNodeStyles(nodeContent, nodeBackgroundColor) {
3529
3452
  * Determines if a handle is a source (output) or target (input)
3530
3453
  */
3531
3454
  function isHandleSource(handleEl) {
3532
- return (handleEl.classList.contains("react-flow__handle-right") ||
3533
- handleEl.classList.contains("react-flow__handle-source"));
3455
+ return (handleEl.classList.contains("react-flow__handle-right") || handleEl.classList.contains("react-flow__handle-source"));
3534
3456
  }
3535
3457
  /**
3536
3458
  * Extracts handle position and calculates absolute coordinates
@@ -3625,7 +3547,7 @@ function extractNodeTitle(nodeEl, nodeX, nodeY) {
3625
3547
  * Extracts visible nodes from React Flow viewport
3626
3548
  */
3627
3549
  function extractNodes(viewport, visibleBounds, options = {}) {
3628
- const { exportHandles = true, exportNodeTitle = true, nodeBackgroundColor, } = options;
3550
+ const { exportHandles = true, exportNodeTitle = true, nodeBackgroundColor } = options;
3629
3551
  const nodes = [];
3630
3552
  let minX = Infinity;
3631
3553
  let minY = Infinity;
@@ -3640,15 +3562,10 @@ function extractNodes(viewport, visibleBounds, options = {}) {
3640
3562
  if (!nodeContent)
3641
3563
  return;
3642
3564
  const styles = extractNodeStyles(nodeContent, nodeBackgroundColor);
3643
- const handles = exportHandles
3644
- ? extractNodeHandles(nodeEl, position.x, position.y, dimensions.width)
3645
- : [];
3646
- const title = exportNodeTitle
3647
- ? extractNodeTitle(nodeEl, position.x, position.y)
3648
- : undefined;
3565
+ const handles = exportHandles ? extractNodeHandles(nodeEl, position.x, position.y, dimensions.width) : [];
3566
+ const title = exportNodeTitle ? extractNodeTitle(nodeEl, position.x, position.y) : undefined;
3649
3567
  // Only include node if it's within visible viewport (or if ignoring viewport)
3650
- if (!visibleBounds ||
3651
- isRectVisible(position.x, position.y, dimensions.width, dimensions.height, visibleBounds)) {
3568
+ if (!visibleBounds || isRectVisible(position.x, position.y, dimensions.width, dimensions.height, visibleBounds)) {
3652
3569
  nodes.push({
3653
3570
  x: position.x,
3654
3571
  y: position.y,
@@ -3681,7 +3598,7 @@ function extractNodes(viewport, visibleBounds, options = {}) {
3681
3598
  * Creates SVG element with dot pattern background (matching React Flow)
3682
3599
  */
3683
3600
  function createSVGElement(width, height, options = {}) {
3684
- const { exportBackgroundPattern = true, backgroundPatternColor = "#f1f1f1", backgroundColor = "#ffffff", } = options;
3601
+ const { exportBackgroundPattern = true, backgroundPatternColor = "#f1f1f1", backgroundColor = "#ffffff" } = options;
3685
3602
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3686
3603
  svg.setAttribute("width", String(width));
3687
3604
  svg.setAttribute("height", String(height));
@@ -3831,9 +3748,7 @@ async function captureCanvasThumbnail(containerElement, options = {}) {
3831
3748
  const transform = parseViewportTransform(viewportTransform);
3832
3749
  // Calculate visible bounds (null if ignoring viewport)
3833
3750
  const viewportRect = reactFlowViewport.getBoundingClientRect();
3834
- const visibleBounds = ignoreViewport
3835
- ? null
3836
- : calculateVisibleBounds(viewportRect, transform);
3751
+ const visibleBounds = ignoreViewport ? null : calculateVisibleBounds(viewportRect, transform);
3837
3752
  // Extract edges and nodes (pass null bounds if ignoring viewport to get all)
3838
3753
  const { edges, bounds: edgeBounds } = extractEdgePaths(reactFlowViewport, visibleBounds);
3839
3754
  const { nodes, bounds: nodeBounds } = extractNodes(reactFlowViewport, visibleBounds, {
@@ -4213,13 +4128,9 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4213
4128
  // Subscribe to runner/workbench events
4214
4129
  useEffect(() => {
4215
4130
  const add = (source, type) => (payload) => setEvents((prev) => {
4216
- if (source === "workbench" &&
4217
- (type === "graphChanged" || type === "graphUiChanged")) {
4218
- const changeType = payload
4219
- .change?.type;
4220
- if (changeType === "moveNode" ||
4221
- changeType === "moveNodes" ||
4222
- changeType === "resizeNodes")
4131
+ if (source === "workbench" && (type === "graphChanged" || type === "graphUiChanged")) {
4132
+ const changeType = payload.change?.type;
4133
+ if (changeType === "moveNode" || changeType === "moveNodes" || changeType === "resizeNodes")
4223
4134
  return prev;
4224
4135
  }
4225
4136
  const nextNo = prev.length > 0 ? (prev[0]?.no ?? 0) + 1 : 1;
@@ -4356,8 +4267,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4356
4267
  // Track custom errors for UI display
4357
4268
  setSystemErrors((prev) => {
4358
4269
  // Avoid duplicates by checking message and code
4359
- if (prev.some((err) => err.message === systemError.message &&
4360
- err.code === systemError.code)) {
4270
+ if (prev.some((err) => err.message === systemError.message && err.code === systemError.code)) {
4361
4271
  return prev;
4362
4272
  }
4363
4273
  return [...prev, systemError];
@@ -4387,7 +4297,10 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4387
4297
  // Validate runId is a non-empty string
4388
4298
  const isValidRunId = runId && typeof runId === "string" && runId.length > 0;
4389
4299
  if (!isValidRunId) {
4390
- console.warn(`[WorkbenchContext] node-start event missing or invalid runId for node ${id}`, { runId, event: s });
4300
+ console.warn(`[WorkbenchContext] node-start event missing or invalid runId for node ${id}`, {
4301
+ runId,
4302
+ event: s,
4303
+ });
4391
4304
  }
4392
4305
  setNodeStatus((prev) => {
4393
4306
  const current = prev[id]?.activeRuns ?? 0;
@@ -4397,9 +4310,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4397
4310
  [id]: {
4398
4311
  ...prev[id],
4399
4312
  activeRuns: current + 1,
4400
- activeRunIds: isValidRunId
4401
- ? [...currentRunIds, runId]
4402
- : currentRunIds,
4313
+ activeRunIds: isValidRunId ? [...currentRunIds, runId] : currentRunIds,
4403
4314
  progress: 0,
4404
4315
  },
4405
4316
  };
@@ -4443,13 +4354,14 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4443
4354
  const current = prev[id]?.activeRuns ?? 0;
4444
4355
  const currentRunIds = prev[id]?.activeRunIds ?? [];
4445
4356
  if (isValidRunId && !currentRunIds.includes(runId)) {
4446
- console.warn(`[WorkbenchContext] node-done event for unknown runId: node=${id} runId=${runId}`, { event: s, currentRunIds });
4357
+ console.warn(`[WorkbenchContext] node-done event for unknown runId: node=${id} runId=${runId}`, {
4358
+ event: s,
4359
+ currentRunIds,
4360
+ });
4447
4361
  return prev; // Ignore stale event
4448
4362
  }
4449
4363
  const nextActive = Math.max(0, current - 1); // Prevent negative values
4450
- const nextRunIds = isValidRunId
4451
- ? currentRunIds.filter((rid) => rid !== runId)
4452
- : currentRunIds;
4364
+ const nextRunIds = isValidRunId ? currentRunIds.filter((rid) => rid !== runId) : currentRunIds;
4453
4365
  const keepProgress = hadError || nextActive > 0;
4454
4366
  // Clear error flag for this runId
4455
4367
  if (isValidRunId && errorRunsRef.current[id]?.[runId]) {
@@ -4575,15 +4487,12 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4575
4487
  }
4576
4488
  }
4577
4489
  }
4578
- else if (event.change?.type !== "setInputs" &&
4579
- event.change?.type !== "updateResolvedHandles") {
4490
+ else if (event.change?.type !== "setInputs" && event.change?.type !== "updateResolvedHandles") {
4580
4491
  await runner.update(event.def, { dry: event.dry });
4581
4492
  }
4582
4493
  if (event.commit) {
4583
4494
  await saveUiRuntimeMetadata(wb, runner);
4584
- const history = await runner
4585
- .commit(event.reason ?? reason)
4586
- .catch((err) => {
4495
+ const history = await runner.commit(event.reason ?? reason).catch((err) => {
4587
4496
  console.error("[WorkbenchContext] Error committing after update:", err);
4588
4497
  return undefined;
4589
4498
  });
@@ -4602,9 +4511,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4602
4511
  setSelectedEdgeId(sel.edges?.[0]);
4603
4512
  if (sel.commit) {
4604
4513
  await saveUiRuntimeMetadata(wb, runner);
4605
- const history = await runner
4606
- .commit(sel.reason ?? "selection")
4607
- .catch((err) => {
4514
+ const history = await runner.commit(sel.reason ?? "selection").catch((err) => {
4608
4515
  console.error("[WorkbenchContext] Error committing selection change:", err);
4609
4516
  return undefined;
4610
4517
  });
@@ -4656,9 +4563,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4656
4563
  }
4657
4564
  }
4658
4565
  await saveUiRuntimeMetadata(wb, runner);
4659
- const history = await runner
4660
- .commit(event.reason ?? reason)
4661
- .catch((err) => {
4566
+ const history = await runner.commit(event.reason ?? reason).catch((err) => {
4662
4567
  console.error("[WorkbenchContext] Error committing UI changes:", err);
4663
4568
  return undefined;
4664
4569
  });
@@ -4719,9 +4624,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4719
4624
  const metadata = workbenchRuntimeState;
4720
4625
  let changed = false;
4721
4626
  // If nodeId is specified, only update that node; otherwise update all nodes
4722
- const nodesToUpdate = event.nodeId
4723
- ? wb.def.nodes.filter((n) => n.nodeId === event.nodeId)
4724
- : wb.def.nodes;
4627
+ const nodesToUpdate = event.nodeId ? wb.def.nodes.filter((n) => n.nodeId === event.nodeId) : wb.def.nodes;
4725
4628
  for (const n of nodesToUpdate) {
4726
4629
  const cur = next[n.nodeId];
4727
4630
  if (!cur)
@@ -4932,12 +4835,12 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
4932
4835
  getNodeDisplayName,
4933
4836
  setNodeName,
4934
4837
  ]);
4935
- return (jsx(WorkbenchContext.Provider, { value: value, children: children }));
4838
+ return jsx(WorkbenchContext.Provider, { value: value, children: children });
4936
4839
  }
4937
4840
 
4938
4841
  function IssueBadge({ level, title, size = 12, className, }) {
4939
4842
  const colorClass = level === "error" ? "text-red-600" : "text-amber-600";
4940
- return (jsx("button", { type: "button", className: `inline-flex items-center justify-center shrink-0 ${colorClass} ${className ?? ""}`, title: title, style: { width: size, height: size }, children: level === "error" ? (jsx(XCircleIcon, { size: size, weight: "fill" })) : (jsx(WarningCircleIcon, { size: size, weight: "fill" })) }));
4843
+ return (jsx("button", { type: "button", className: `inline-flex items-center justify-center shrink-0 ${colorClass} ${className ?? ""}`, title: title, style: { width: size, height: size }, children: level === "error" ? jsx(XCircleIcon, { size: size, weight: "fill" }) : jsx(WarningCircleIcon, { size: size, weight: "fill" }) }));
4941
4844
  }
4942
4845
 
4943
4846
  function DefaultNodeHeader({ id, typeId, validation, right, showId, }) {
@@ -4972,9 +4875,7 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, }) {
4972
4875
  const trimmed = editValue.trim();
4973
4876
  const defaultDisplayName = getDefaultDisplayName();
4974
4877
  // If the trimmed value matches the default display name or typeId, clear the custom name
4975
- ctx.setNodeName(id, trimmed === defaultDisplayName || trimmed === effectiveTypeId
4976
- ? undefined
4977
- : trimmed);
4878
+ ctx.setNodeName(id, trimmed === defaultDisplayName || trimmed === effectiveTypeId ? undefined : trimmed);
4978
4879
  setIsEditing(false);
4979
4880
  }, [ctx, id, editValue, getDefaultDisplayName, effectiveTypeId, typeId]);
4980
4881
  const handleCancel = React.useCallback(() => {
@@ -5011,19 +4912,13 @@ function DefaultNodeHeader({ id, typeId, validation, right, showId, }) {
5011
4912
  }, className: "w-4 h-4 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded text-gray-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors", title: "Run from here", children: jsx(FastForwardIcon, { size: 10, weight: "fill" }) }), jsx("button", { onClick: (e) => {
5012
4913
  e.stopPropagation();
5013
4914
  ctx.abortNode(id);
5014
- }, className: "w-4 h-4 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded text-gray-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 transition-colors", title: "Abort node", children: jsx(StopIcon, { size: 10, weight: "fill" }) })] })), right, validation.issues && validation.issues.length > 0 && (jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
5015
- ? "error"
5016
- : "warning", size: 12, className: "w-3 h-3", title: validation.issues
5017
- .map((v) => `${v.code}: ${v.message}`)
5018
- .join("; ") })), showId && jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
4915
+ }, className: "w-4 h-4 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded text-gray-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 transition-colors", title: "Abort node", children: jsx(StopIcon, { size: 10, weight: "fill" }) })] })), right, validation.issues && validation.issues.length > 0 && (jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error") ? "error" : "warning", size: 12, className: "w-3 h-3", title: validation.issues.map((v) => `${v.code}: ${v.message}`).join("; ") })), showId && jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
5019
4916
  }
5020
4917
 
5021
4918
  function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
5022
4919
  return (jsxs(Fragment, { children: [jsx(Handle, { id: id, type: type, position: position, isConnectable: isConnectable, className: className, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsx("div", { className: `${labelClassName} ${kind === "input" ? " left-2" : " right-2"}`, style: {
5023
4920
  top: (y ?? 0) - 8,
5024
- ...(kind === "input"
5025
- ? { right: "50%" }
5026
- : { left: "50%", textAlign: "right" }),
4921
+ ...(kind === "input" ? { right: "50%" } : { left: "50%", textAlign: "right" }),
5027
4922
  whiteSpace: "nowrap",
5028
4923
  overflow: "hidden",
5029
4924
  textOverflow: "ellipsis",
@@ -5083,7 +4978,7 @@ function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClas
5083
4978
  })] }));
5084
4979
  }
5085
4980
 
5086
- function DefaultNodeContent({ data, isConnectable, }) {
4981
+ function DefaultNodeContent({ data, isConnectable }) {
5087
4982
  const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
5088
4983
  const inputEntries = data.inputHandles ?? [];
5089
4984
  const outputEntries = data.outputHandles ?? [];
@@ -5108,9 +5003,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
5108
5003
  const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === handleId);
5109
5004
  const hasAny = vIssues.length > 0;
5110
5005
  const hasErr = vIssues.some((v) => v.level === "error");
5111
- const title = vIssues
5112
- .map((v) => `${v.code}: ${v.message}`)
5113
- .join("; ");
5006
+ const title = vIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
5114
5007
  const valueText = (() => {
5115
5008
  if (!showValues)
5116
5009
  return undefined;
@@ -5118,8 +5011,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
5118
5011
  const value = inputValues?.[entry.id];
5119
5012
  const isDefault = value !== undefined &&
5120
5013
  inputDefaults?.[entry.id] !== undefined &&
5121
- JSON.stringify(value) ===
5122
- JSON.stringify(inputDefaults[entry.id]);
5014
+ JSON.stringify(value) === JSON.stringify(inputDefaults[entry.id]);
5123
5015
  const txt = toString(entry.typeId, value);
5124
5016
  const str = typeof txt === "string" ? txt : String(txt);
5125
5017
  return { text: str, isDefault };
@@ -5135,7 +5027,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
5135
5027
  } })] }));
5136
5028
  }
5137
5029
 
5138
- const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable, }) {
5030
+ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable }) {
5139
5031
  const nodeRef = useRef(null);
5140
5032
  const { typeId } = data;
5141
5033
  const status = data.status ?? { activeRuns: 0 };
@@ -5162,7 +5054,7 @@ const DefaultEdge = React.memo(function DefaultEdge({ id, sourceX, sourceY, targ
5162
5054
  targetY,
5163
5055
  targetPosition,
5164
5056
  });
5165
- return (jsx(BaseEdge, { id: id, path: edgePath, style: style, markerEnd: markerEnd }));
5057
+ return jsx(BaseEdge, { id: id, path: edgePath, style: style, markerEnd: markerEnd });
5166
5058
  });
5167
5059
 
5168
5060
  function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult, runNode, runFromHere) {
@@ -5226,9 +5118,7 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
5226
5118
  */
5227
5119
  function createCopyHandler(wb, runner, getDefaultNodeSize, onCopyResult) {
5228
5120
  return () => {
5229
- const getNodeSize = getDefaultNodeSize
5230
- ? (nodeId, typeId) => getDefaultNodeSize(typeId)
5231
- : undefined;
5121
+ const getNodeSize = getDefaultNodeSize ? (nodeId, typeId) => getDefaultNodeSize(typeId) : undefined;
5232
5122
  const data = wb.copySelection(runner, getNodeSize);
5233
5123
  if (onCopyResult) {
5234
5124
  onCopyResult(data);
@@ -5243,9 +5133,7 @@ function createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyRes
5243
5133
  // Select the node first, then copy
5244
5134
  const currentSelection = wb.getSelection();
5245
5135
  wb.setSelection({ nodes: [nodeId], edges: [] });
5246
- const getNodeSize = getDefaultNodeSize
5247
- ? (nodeId, typeId) => getDefaultNodeSize(typeId)
5248
- : undefined;
5136
+ const getNodeSize = getDefaultNodeSize ? (nodeId, typeId) => getDefaultNodeSize(typeId) : undefined;
5249
5137
  const data = wb.copySelection(runner, getNodeSize);
5250
5138
  // Restore original selection
5251
5139
  wb.setSelection(currentSelection);
@@ -5334,8 +5222,7 @@ function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
5334
5222
  const tElem = registry.types.get(base);
5335
5223
  const arrT = tArr?.bakeTarget;
5336
5224
  const elemT = tElem?.bakeTarget;
5337
- if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
5338
- (elemT && registry.nodes.has(elemT.nodeTypeId)))
5225
+ if ((arrT && registry.nodes.has(arrT.nodeTypeId)) || (elemT && registry.nodes.has(elemT.nodeTypeId)))
5339
5226
  out.push(h);
5340
5227
  }
5341
5228
  else {
@@ -5357,9 +5244,7 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
5357
5244
  const scrollRef = useRef(null);
5358
5245
  const [copied, setCopied] = useState(false);
5359
5246
  const rows = useMemo(() => {
5360
- const filtered = hideWorkbench
5361
- ? events.filter((e) => e.source !== "workbench")
5362
- : events;
5247
+ const filtered = hideWorkbench ? events.filter((e) => e.source !== "workbench") : events;
5363
5248
  return filtered.slice().reverse();
5364
5249
  }, [events, hideWorkbench]);
5365
5250
  useEffect(() => {
@@ -5428,17 +5313,17 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5428
5313
  const selectedEdge = wb.def.edges.find((e) => e.id === selectedEdgeId);
5429
5314
  // Use precomputed handles map from context
5430
5315
  const effectiveHandles = selectedNode
5431
- ? handlesMap[selectedNode.nodeId] ?? {
5316
+ ? (handlesMap[selectedNode.nodeId] ?? {
5432
5317
  inputs: {},
5433
5318
  outputs: {},
5434
5319
  inputDefaults: {},
5435
- }
5320
+ })
5436
5321
  : { inputs: {}, outputs: {}, inputDefaults: {} };
5437
5322
  const inputHandles = Object.entries(effectiveHandles.inputs)
5438
5323
  .filter(([k]) => !isInputPrivate(effectiveHandles.inputs, k))
5439
5324
  .map(([k]) => k);
5440
5325
  const outputHandles = Object.keys(effectiveHandles.outputs);
5441
- const nodeInputsRaw = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
5326
+ const nodeInputsRaw = selectedNodeId ? (inputsMap[selectedNodeId] ?? {}) : {};
5442
5327
  const nodeInputsDefaults = selectedNodeId
5443
5328
  ? {
5444
5329
  ...effectiveHandles.inputDefaults,
@@ -5447,7 +5332,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5447
5332
  : {};
5448
5333
  // Keep defaults separate for placeholder use (don't merge into nodeInputs)
5449
5334
  const nodeInputs = nodeInputsRaw;
5450
- const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
5335
+ const nodeOutputs = selectedNodeId ? (outputsMap[selectedNodeId] ?? {}) : {};
5451
5336
  // Helper to truncate long values
5452
5337
  const truncateValue = (str, maxLen = 50) => {
5453
5338
  if (str.length <= maxLen)
@@ -5468,15 +5353,9 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5468
5353
  document.body.removeChild(textarea);
5469
5354
  });
5470
5355
  };
5471
- const selectedNodeStatus = selectedNodeId
5472
- ? nodeStatus?.[selectedNodeId]
5473
- : undefined;
5474
- const selectedNodeValidation = selectedNodeId
5475
- ? nodeValidationIssues?.[selectedNodeId] ?? []
5476
- : [];
5477
- const selectedEdgeValidation = selectedEdge
5478
- ? edgeValidationIssues?.[selectedEdge.id] ?? []
5479
- : [];
5356
+ const selectedNodeStatus = selectedNodeId ? nodeStatus?.[selectedNodeId] : undefined;
5357
+ const selectedNodeValidation = selectedNodeId ? (nodeValidationIssues?.[selectedNodeId] ?? []) : [];
5358
+ const selectedEdgeValidation = selectedEdge ? (edgeValidationIssues?.[selectedEdge.id] ?? []) : [];
5480
5359
  const selectedNodeHandleValidation = selectedNodeId
5481
5360
  ? {
5482
5361
  inputs: nodeValidationHandles?.inputs?.[selectedNodeId] ?? [],
@@ -5506,10 +5385,8 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5506
5385
  if (outIssues.length === 0)
5507
5386
  return null;
5508
5387
  const outErr = outIssues.some((m) => m.level === "error");
5509
- const outTitle = outIssues
5510
- .map((v) => `${v.code}: ${v.message}`)
5511
- .join("; ");
5512
- return (jsx(IssueBadge, { level: outErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: outTitle }));
5388
+ const outTitle = outIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
5389
+ return jsx(IssueBadge, { level: outErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: outTitle });
5513
5390
  }, [selectedNodeHandleValidation]);
5514
5391
  // Render output display value
5515
5392
  const renderOutputDisplay = useCallback((outputValue, effectiveHandle) => {
@@ -5551,9 +5428,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5551
5428
  const { typeId: displayTypeId, value: displayValue } = unwrapForDisplay(typeId, current);
5552
5429
  const display = safeToString(displayTypeId, displayValue);
5553
5430
  const wasOriginal = originals[h];
5554
- const isDirty = drafts[h] !== undefined &&
5555
- wasOriginal !== undefined &&
5556
- drafts[h] !== wasOriginal;
5431
+ const isDirty = drafts[h] !== undefined && wasOriginal !== undefined && drafts[h] !== wasOriginal;
5557
5432
  if (!isDirty) {
5558
5433
  nextDrafts[h] = display;
5559
5434
  nextOriginals[h] = display;
@@ -5576,7 +5451,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5576
5451
  return (jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-auto select-none`, children: [jsxs("div", { className: "flex-1 overflow-auto", children: [contextPanel && jsx("div", { className: "mb-2", children: contextPanel }), inputValidationErrors.length > 0 && (jsxs("div", { className: "mb-2 space-y-1", children: [inputValidationErrors.map((err, i) => (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: [jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-semibold", children: "Input Validation Error" }), jsx("div", { className: "break-words", children: err.message }), jsxs("div", { className: "text-[10px] text-red-600 mt-1", children: [err.nodeId, ".", err.handle, " (type: ", err.typeId, ")"] })] }), jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeInputValidationError(i), title: "Dismiss", children: jsx(XIcon, { size: 10 }) })] }, i))), inputValidationErrors.length > 1 && (jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearInputValidationErrors, children: "Clear all" }))] })), systemErrors.length > 0 && (jsxs("div", { className: "mb-2 space-y-1", children: [systemErrors.map((err, i) => (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: [jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-semibold", children: err.code ? `Error ${err.code}` : "Error" }), jsx("div", { className: "break-words", children: err.message })] }), jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeSystemError(i), title: "Dismiss", children: jsx(XIcon, { size: 10 }) })] }, i))), systemErrors.length > 1 && (jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearSystemErrors, children: "Clear all" }))] })), registryErrors.length > 0 && (jsxs("div", { className: "mb-2 space-y-1", children: [registryErrors.map((err, i) => (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: [jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-semibold", children: "Registry Error" }), jsx("div", { className: "break-words", children: err.message }), err.attempt && err.maxAttempts && (jsxs("div", { className: "text-[10px] text-amber-600 mt-1", children: ["Attempt ", err.attempt, " of ", err.maxAttempts] }))] }), jsx("button", { className: "text-amber-500 hover:text-amber-700 text-[10px] px-1", onClick: () => removeRegistryError(i), title: "Dismiss", children: jsx(XIcon, { size: 10 }) })] }, i))), registryErrors.length > 1 && (jsx("button", { className: "text-xs text-amber-600 hover:text-amber-800 underline", onClick: clearRegistryErrors, children: "Clear all" }))] })), jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsx("div", { className: "flex-1", children: !selectedNode && !selectedEdge ? (jsxs("div", { children: [jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxs("li", { className: "flex items-center gap-1", children: [jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (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) => {
5577
5452
  e.stopPropagation();
5578
5453
  deleteEdgeById(m.data?.edgeId);
5579
- }, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) : selectedEdge ? (jsxs("div", { children: [jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), renderEdgeStatus(), jsx("div", { className: "mt-1", children: jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
5454
+ }, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) : selectedEdge ? (jsxs("div", { children: [jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192 ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), renderEdgeStatus(), jsx("div", { className: "mt-1", children: jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
5580
5455
  e.stopPropagation();
5581
5456
  deleteEdgeById(selectedEdge.id);
5582
5457
  }, title: "Delete this edge", children: "Delete edge" }) }), jsxs("div", { className: "flex items-center gap-2 mt-1", children: [jsxs("label", { className: "w-20 flex flex-col", children: [jsx("span", { children: "Type" }), jsx("span", { className: "text-gray-500 text-[11px]", children: "DataTypeId" })] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", value: selectedEdge.typeId ?? "", onChange: (e) => {
@@ -5586,26 +5461,17 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5586
5461
  }, children: [jsx("option", { value: "", children: "(infer from source)" }), Array.from(wb.registry.types.keys()).map((tid) => (jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxs("li", { className: "flex items-center gap-1", children: [jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsx("span", { children: `${m.code}: ${m.message}` }), 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) => {
5587
5462
  e.stopPropagation();
5588
5463
  deleteEdgeById(selectedEdge.id);
5589
- }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.activeRuns &&
5590
- selectedNodeStatus.activeRuns > 0 && (jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxs("div", { className: "font-semibold", children: ["Running (", selectedNodeStatus.activeRuns, ")"] }), selectedNodeStatus.activeRunIds &&
5591
- selectedNodeStatus.activeRunIds.length > 0 ? (jsxs("div", { className: "mt-1", children: [jsx("div", { className: "text-[10px] text-blue-600", children: "RunIds:" }), jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedNodeStatus.activeRunIds.map((runId, idx) => (jsx("span", { className: "text-[10px] px-1.5 py-0.5 bg-blue-100 border border-blue-300 rounded font-mono", children: runId }, idx))) })] })) : (jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "RunIds not available (some runs may have started without runId)" }))] })), !!selectedNodeStatus?.lastError && (jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
5592
- selectedNodeStatus.lastError) }))] })), jsxs("div", { className: "mb-2", children: [jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
5464
+ }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.activeRuns && selectedNodeStatus.activeRuns > 0 && (jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxs("div", { className: "font-semibold", children: ["Running (", selectedNodeStatus.activeRuns, ")"] }), selectedNodeStatus.activeRunIds && selectedNodeStatus.activeRunIds.length > 0 ? (jsxs("div", { className: "mt-1", children: [jsx("div", { className: "text-[10px] text-blue-600", children: "RunIds:" }), jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedNodeStatus.activeRunIds.map((runId, idx) => (jsx("span", { className: "text-[10px] px-1.5 py-0.5 bg-blue-100 border border-blue-300 rounded font-mono", children: runId }, idx))) })] })) : (jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "RunIds not available (some runs may have started without runId)" }))] })), !!selectedNodeStatus?.lastError && (jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ?? selectedNodeStatus.lastError) }))] })), jsxs("div", { className: "mb-2", children: [jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
5593
5465
  const typeId = getInputTypeId(effectiveHandles.inputs, h);
5594
5466
  const declaredTypes = getInputDeclaredTypes(effectiveHandles.inputs, h);
5595
- const typeLabel = Array.isArray(declaredTypes)
5596
- ? declaredTypes.join(" | ")
5597
- : typeId ?? "";
5598
- const isLinked = wb.def.edges.some((e) => e.target.nodeId === selectedNodeId &&
5599
- e.target.handle === h);
5467
+ const typeLabel = Array.isArray(declaredTypes) ? declaredTypes.join(" | ") : (typeId ?? "");
5468
+ const isLinked = wb.def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === h);
5600
5469
  const inbound = new Set(wb.def.edges
5601
- .filter((e) => e.target.nodeId === selectedNodeId &&
5602
- e.target.handle === h)
5470
+ .filter((e) => e.target.nodeId === selectedNodeId && e.target.handle === h)
5603
5471
  .map((e) => e.target.handle));
5604
5472
  const { typeId: defaultTypeId, value: defaultValue } = unwrapForDisplay(typeId, nodeInputsDefaults[h]);
5605
5473
  const hasDefault = !inbound.has(h) && nodeInputsDefaults[h] !== undefined;
5606
- const defaultStr = hasDefault
5607
- ? safeToString(defaultTypeId, defaultValue)
5608
- : undefined;
5474
+ const defaultStr = hasDefault ? safeToString(defaultTypeId, defaultValue) : undefined;
5609
5475
  const commonProps = {
5610
5476
  style: { flex: 1 },
5611
5477
  disabled: isLinked,
@@ -5643,12 +5509,8 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5643
5509
  const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
5644
5510
  const hasValidation = inIssues.length > 0;
5645
5511
  const hasErr = inIssues.some((m) => m.level === "error");
5646
- const title = inIssues
5647
- .map((v) => `${v.code}: ${v.message}`)
5648
- .join("; ");
5649
- return (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-32 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: typeLabel })] }), hasValidation && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", value: current !== undefined && current !== null
5650
- ? String(current)
5651
- : "", onChange: (e) => {
5512
+ const title = inIssues.map((v) => `${v.code}: ${v.message}`).join("; ");
5513
+ return (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-32 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: typeLabel })] }), hasValidation && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", value: current !== undefined && current !== null ? String(current) : "", onChange: (e) => {
5652
5514
  const val = e.target.value;
5653
5515
  const raw = val === "" ? undefined : Number(val);
5654
5516
  setInput(h, raw);
@@ -5656,13 +5518,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5656
5518
  const display = safeToString(typeId, raw);
5657
5519
  setDrafts((d) => ({ ...d, [h]: display }));
5658
5520
  setOriginals((o) => ({ ...o, [h]: display }));
5659
- }, ...commonProps, children: [jsx("option", { value: "", children: placeholder
5660
- ? `Default: ${placeholder}`
5661
- : "(select)" }), wb.registry.enums
5662
- .get(typeId)
5663
- ?.options.map((opt) => (jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsx("div", { className: "flex items-center gap-1 flex-1", children: jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(displayTypeId, displayValue) }) })) : (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", placeholder: placeholder
5664
- ? `Default: ${placeholder}`
5665
- : undefined, value: value, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
5521
+ }, ...commonProps, children: [jsx("option", { value: "", children: placeholder ? `Default: ${placeholder}` : "(select)" }), wb.registry.enums.get(typeId)?.options.map((opt) => (jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsx("div", { className: "flex items-center gap-1 flex-1", children: jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(displayTypeId, displayValue) }) })) : (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", placeholder: placeholder ? `Default: ${placeholder}` : undefined, value: value, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
5666
5522
  if (e.key === "Enter")
5667
5523
  commit();
5668
5524
  if (e.key === "Escape")
@@ -5676,12 +5532,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
5676
5532
 
5677
5533
  // Helper to format shortcut for current platform
5678
5534
  function formatShortcut(shortcut) {
5679
- const isMac = typeof navigator !== "undefined" &&
5680
- navigator.userAgent.toLowerCase().includes("mac");
5535
+ const isMac = typeof navigator !== "undefined" && navigator.userAgent.toLowerCase().includes("mac");
5681
5536
  return shortcut.replace(/⌘\/Ctrl/g, isMac ? "⌘" : "Ctrl");
5682
5537
  }
5683
- function ContextMenuButton({ label, onClick, disabled = false, shortcut, }) {
5684
- return (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-between", onClick: onClick, disabled: disabled, children: [jsx("span", { children: label }), shortcut && (jsx("span", { className: "text-gray-400 text-xs ml-4", children: formatShortcut(shortcut) }))] }));
5538
+ function ContextMenuButton({ label, onClick, disabled = false, shortcut }) {
5539
+ return (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-between", onClick: onClick, disabled: disabled, children: [jsx("span", { children: label }), shortcut && jsx("span", { className: "text-gray-400 text-xs ml-4", children: formatShortcut(shortcut) })] }));
5685
5540
  }
5686
5541
 
5687
5542
  function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, keyboardShortcuts = {
@@ -5694,9 +5549,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, keyb
5694
5549
  const [query, setQuery] = useState("");
5695
5550
  const [hasPasteData, setHasPasteData] = useState(false);
5696
5551
  const q = query.trim().toLowerCase();
5697
- const filteredIds = q
5698
- ? nodeIds.filter((id) => id.toLowerCase().includes(q))
5699
- : nodeIds;
5552
+ const filteredIds = q ? nodeIds.filter((id) => id.toLowerCase().includes(q)) : nodeIds;
5700
5553
  const canUndo = handlers.canUndo ?? false;
5701
5554
  const canRedo = handlers.canRedo ?? false;
5702
5555
  useEffect(() => {
@@ -5758,8 +5611,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, keyb
5758
5611
  // Clamp menu position to viewport
5759
5612
  const MENU_MIN_WIDTH = 180;
5760
5613
  const PADDING = 16; // rough padding/shadow
5761
- const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
5762
- (MENU_MIN_WIDTH + PADDING));
5614
+ const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) - (MENU_MIN_WIDTH + PADDING));
5763
5615
  const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
5764
5616
  const handleClick = (typeId) => {
5765
5617
  // project() is deprecated; use screenToFlowPosition for screen coordinates
@@ -5791,10 +5643,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, keyb
5791
5643
  return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
5792
5644
  e.preventDefault();
5793
5645
  e.stopPropagation();
5794
- }, children: [hasPasteData && handlers.onPaste && (jsx(ContextMenuButton, { label: "Paste", onClick: handlePaste, shortcut: keyboardShortcuts.paste })), (handlers.onUndo || handlers.onRedo || handlers.onSelectAll) && (jsxs(Fragment, { children: [hasPasteData && handlers.onPaste && (jsx("div", { className: "h-px bg-gray-200 my-1" })), handlers.onSelectAll && (jsx(ContextMenuButton, { label: "Select All", onClick: handlers.onSelectAll, shortcut: keyboardShortcuts.selectAll })), handlers.onSelectAll && (handlers.onUndo || handlers.onRedo) && (jsx("div", { className: "h-px bg-gray-200 my-1" })), handlers.onUndo && (jsx(ContextMenuButton, { label: "Undo", onClick: handlers.onUndo, disabled: !canUndo, shortcut: keyboardShortcuts.undo })), handlers.onRedo && (jsx(ContextMenuButton, { label: "Redo", onClick: handlers.onRedo, disabled: !canRedo, shortcut: keyboardShortcuts.redo })), (handlers.onUndo || handlers.onRedo) && (jsx("div", { className: "h-px bg-gray-200 my-1" }))] })), hasPasteData &&
5795
- handlers.onPaste &&
5796
- !handlers.onUndo &&
5797
- !handlers.onRedo && jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsx("div", { className: "px-2 pb-1", children: jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 rounded px-2 py-1 text-sm outline-none focus:border-gray-400 select-text", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
5646
+ }, children: [hasPasteData && handlers.onPaste && (jsx(ContextMenuButton, { label: "Paste", onClick: handlePaste, shortcut: keyboardShortcuts.paste })), (handlers.onUndo || handlers.onRedo || handlers.onSelectAll) && (jsxs(Fragment, { children: [hasPasteData && handlers.onPaste && jsx("div", { className: "h-px bg-gray-200 my-1" }), handlers.onSelectAll && (jsx(ContextMenuButton, { label: "Select All", onClick: handlers.onSelectAll, shortcut: keyboardShortcuts.selectAll })), handlers.onSelectAll && (handlers.onUndo || handlers.onRedo) && jsx("div", { className: "h-px bg-gray-200 my-1" }), handlers.onUndo && (jsx(ContextMenuButton, { label: "Undo", onClick: handlers.onUndo, disabled: !canUndo, shortcut: keyboardShortcuts.undo })), handlers.onRedo && (jsx(ContextMenuButton, { label: "Redo", onClick: handlers.onRedo, disabled: !canRedo, shortcut: keyboardShortcuts.redo })), (handlers.onUndo || handlers.onRedo) && jsx("div", { className: "h-px bg-gray-200 my-1" })] })), hasPasteData && handlers.onPaste && !handlers.onUndo && !handlers.onRedo && (jsx("div", { className: "h-px bg-gray-200 my-1" })), jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node ", jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsx("div", { className: "px-2 pb-1", children: jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 rounded px-2 py-1 text-sm outline-none focus:border-gray-400 select-text", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? renderTree(root) : jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" }) })] }));
5798
5647
  }
5799
5648
 
5800
5649
  function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, runMode, wb, keyboardShortcuts = {
@@ -5831,18 +5680,13 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, r
5831
5680
  if (!open || !clientPos || !nodeId)
5832
5681
  return null;
5833
5682
  // Determine if this is a start node (no inbound edges)
5834
- const isStartNode = wb
5835
- ? !wb.def.edges.some((e) => e.target.nodeId === nodeId)
5836
- : false;
5683
+ const isStartNode = wb ? !wb.def.edges.some((e) => e.target.nodeId === nodeId) : false;
5837
5684
  // Check if node has outbound edges (required for "Run workflow" / "Run from here")
5838
- const hasOutboundEdges = wb
5839
- ? wb.def.edges.some((e) => e.source.nodeId === nodeId)
5840
- : false;
5685
+ const hasOutboundEdges = wb ? wb.def.edges.some((e) => e.source.nodeId === nodeId) : false;
5841
5686
  // clamp
5842
5687
  const MENU_MIN_WIDTH = 180;
5843
5688
  const PADDING = 16;
5844
- const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
5845
- (MENU_MIN_WIDTH + PADDING));
5689
+ const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) - (MENU_MIN_WIDTH + PADDING));
5846
5690
  const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
5847
5691
  return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
5848
5692
  e.preventDefault();
@@ -5886,8 +5730,7 @@ function SelectionContextMenu({ open, clientPos, handlers, keyboardShortcuts = {
5886
5730
  // Clamp menu position to viewport
5887
5731
  const MENU_MIN_WIDTH = 180;
5888
5732
  const PADDING = 16;
5889
- const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
5890
- (MENU_MIN_WIDTH + PADDING));
5733
+ const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) - (MENU_MIN_WIDTH + PADDING));
5891
5734
  const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 100);
5892
5735
  return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
5893
5736
  e.preventDefault();
@@ -5895,7 +5738,7 @@ function SelectionContextMenu({ open, clientPos, handlers, keyboardShortcuts = {
5895
5738
  }, children: [jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Selection" }), jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy }), handlers.onDuplicate && (jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate })), jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete })] }));
5896
5739
  }
5897
5740
 
5898
- function KeyboardShortcutToast({ message, duration = 1000, onClose, }) {
5741
+ function KeyboardShortcutToast({ message, duration = 1000, onClose }) {
5899
5742
  const [isVisible, setIsVisible] = useState(true);
5900
5743
  const onCloseRef = useRef(onClose);
5901
5744
  const fadeOutTimerRef = useRef(null);
@@ -5952,7 +5795,7 @@ const SelectionActiveSync = ({ selection }) => {
5952
5795
  };
5953
5796
 
5954
5797
  const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
5955
- const { showValues, toString, toElement, getDefaultNodeSize, reactFlowProps, children, } = props;
5798
+ const { showValues, toString, toElement, getDefaultNodeSize, reactFlowProps, children } = props;
5956
5799
  const { wb, handlesMap, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, overrides, runNode, runFromHere, runMode, } = useWorkbenchContext();
5957
5800
  const nodeValidation = validationByNode;
5958
5801
  const edgeValidation = validationByEdge.errors;
@@ -6036,7 +5879,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6036
5879
  }
6037
5880
  },
6038
5881
  }), []);
6039
- const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb, handlesMap);
5882
+ const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete } = useWorkbenchBridge(wb, handlesMap);
6040
5883
  const ui = useMemo(() => wb.getUI(), [wb, uiVersion]);
6041
5884
  const { nodeTypes, resolveNodeType } = useMemo(() => {
6042
5885
  // Build nodeTypes map using UI extension registry
@@ -6052,7 +5895,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6052
5895
  for (const [typeId, comp] of custom.entries()) {
6053
5896
  types[`spark-${typeId}`] = comp;
6054
5897
  }
6055
- const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default";
5898
+ const resolver = (nodeTypeId) => (custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default");
6056
5899
  return { nodeTypes: types, resolveNodeType: resolver };
6057
5900
  // Include uiVersion to recompute when custom renderers are registered
6058
5901
  // Include registryVersion to recompute when registry enums/types change
@@ -6097,12 +5940,8 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6097
5940
  try {
6098
5941
  const prevNodeIds = new Set(prevNodesRef.current.map((n) => n.id));
6099
5942
  const nextNodeIds = new Set(out.nodes.map((n) => n.id));
6100
- const addedNodeIds = out.nodes
6101
- .filter((n) => !prevNodeIds.has(n.id))
6102
- .map((n) => n.id);
6103
- const removedNodeIds = prevNodesRef.current
6104
- .filter((n) => !nextNodeIds.has(n.id))
6105
- .map((n) => n.id);
5943
+ const addedNodeIds = out.nodes.filter((n) => !prevNodeIds.has(n.id)).map((n) => n.id);
5944
+ const removedNodeIds = prevNodesRef.current.filter((n) => !nextNodeIds.has(n.id)).map((n) => n.id);
6106
5945
  const prevNodeMap = new Map(prevNodesRef.current.map((n) => [n.id, n]));
6107
5946
  const changedNodeIds = out.nodes
6108
5947
  .filter((n) => {
@@ -6112,9 +5951,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6112
5951
  .map((n) => n.id);
6113
5952
  // Detect handle updates (ids/length changes) for targeted debug
6114
5953
  const toIds = (arr) => Array.isArray(arr)
6115
- ? arr
6116
- .map((h) => h && typeof h === "object" && "id" in h ? String(h.id) : "")
6117
- .filter(Boolean)
5954
+ ? arr.map((h) => (h && typeof h === "object" && "id" in h ? String(h.id) : "")).filter(Boolean)
6118
5955
  : [];
6119
5956
  const handlesEqual = (a, b) => {
6120
5957
  const aIds = toIds(a);
@@ -6139,12 +5976,8 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6139
5976
  .map((n) => n.id);
6140
5977
  const prevEdgeIds = new Set(prevEdgesRef.current.map((e) => e.id));
6141
5978
  const nextEdgeIds = new Set(out.edges.map((e) => e.id));
6142
- const addedEdgeIds = out.edges
6143
- .filter((e) => !prevEdgeIds.has(e.id))
6144
- .map((e) => e.id);
6145
- const removedEdgeIds = prevEdgesRef.current
6146
- .filter((e) => !nextEdgeIds.has(e.id))
6147
- .map((e) => e.id);
5979
+ const addedEdgeIds = out.edges.filter((e) => !prevEdgeIds.has(e.id)).map((e) => e.id);
5980
+ const removedEdgeIds = prevEdgesRef.current.filter((e) => !nextEdgeIds.has(e.id)).map((e) => e.id);
6148
5981
  const prevEdgeMap = new Map(prevEdgesRef.current.map((e) => [e.id, e]));
6149
5982
  const changedEdgeIds = out.edges
6150
5983
  .filter((e) => {
@@ -6152,10 +5985,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6152
5985
  return p ? !isSameEdge(p, e) : false;
6153
5986
  })
6154
5987
  .map((e) => e.id);
6155
- if (addedNodeIds.length ||
6156
- removedNodeIds.length ||
6157
- changedNodeIds.length ||
6158
- handleChanged.length) {
5988
+ if (addedNodeIds.length || removedNodeIds.length || changedNodeIds.length || handleChanged.length) {
6159
5989
  // eslint-disable-next-line no-console
6160
5990
  console.debug("[WorkbenchCanvas] node updates", {
6161
5991
  added: addedNodeIds,
@@ -6164,9 +5994,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6164
5994
  handleChanged,
6165
5995
  });
6166
5996
  }
6167
- if (addedEdgeIds.length ||
6168
- removedEdgeIds.length ||
6169
- changedEdgeIds.length) {
5997
+ if (addedEdgeIds.length || removedEdgeIds.length || changedEdgeIds.length) {
6170
5998
  // eslint-disable-next-line no-console
6171
5999
  console.debug("[WorkbenchCanvas] edge updates", {
6172
6000
  added: addedEdgeIds,
@@ -6290,10 +6118,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6290
6118
  const selectionBounds = getSelectionScreenBounds();
6291
6119
  if (selectionBounds && hasMultipleNodesSelected) {
6292
6120
  const { left, top, right, bottom } = selectionBounds;
6293
- if (e.clientX >= left &&
6294
- e.clientX <= right &&
6295
- e.clientY >= top &&
6296
- e.clientY <= bottom) {
6121
+ if (e.clientX >= left && e.clientX <= right && e.clientY >= top && e.clientY <= bottom) {
6297
6122
  // If only one node is selected (even with edges), show node menu for empty space clicks
6298
6123
  if (isSingleNodeSelected) {
6299
6124
  const nodeId = selection.nodes[0];
@@ -6367,7 +6192,9 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6367
6192
  }, runner);
6368
6193
  if (overrides?.getSelectionContextMenuHandlers) {
6369
6194
  const selection = wb.getSelection();
6370
- return overrides.getSelectionContextMenuHandlers(wb, selection, baseHandlers, { getDefaultNodeSize: overrides.getDefaultNodeSize });
6195
+ return overrides.getSelectionContextMenuHandlers(wb, selection, baseHandlers, {
6196
+ getDefaultNodeSize: overrides.getDefaultNodeSize,
6197
+ });
6371
6198
  }
6372
6199
  return baseHandlers;
6373
6200
  }, [wb, runner, overrides, onCloseSelectionMenu]);
@@ -6437,20 +6264,15 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6437
6264
  const handleKeyDown = async (e) => {
6438
6265
  // Check if target is inside WorkbenchCanvas container
6439
6266
  const target = e.target;
6440
- if (!containerRef.current ||
6441
- !(containerRef.current.contains(target) ||
6442
- containerRef.current == target)) {
6267
+ if (!containerRef.current || !(containerRef.current.contains(target) || containerRef.current == target)) {
6443
6268
  return;
6444
6269
  }
6445
6270
  // Ignore if typing in input/textarea
6446
- if (target.tagName === "INPUT" ||
6447
- target.tagName === "TEXTAREA" ||
6448
- target.isContentEditable) {
6271
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
6449
6272
  return;
6450
6273
  }
6451
6274
  // Detect Mac platform using userAgent (navigator.platform is deprecated)
6452
- const isMac = typeof navigator !== "undefined" &&
6453
- navigator.userAgent.toLowerCase().includes("mac");
6275
+ const isMac = typeof navigator !== "undefined" && navigator.userAgent.toLowerCase().includes("mac");
6454
6276
  const modKey = isMac ? e.metaKey : e.ctrlKey;
6455
6277
  const key = e.key.toLowerCase();
6456
6278
  // Undo: Cmd/Ctrl + Z
@@ -6511,16 +6333,14 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6511
6333
  if (!isShortcutEnabled("duplicate"))
6512
6334
  return;
6513
6335
  const selection = wb.getSelection();
6514
- if (selection.nodes.length === 1 &&
6515
- nodeContextMenuHandlers?.onDuplicate) {
6336
+ if (selection.nodes.length === 1 && nodeContextMenuHandlers?.onDuplicate) {
6516
6337
  // Single node selected - use node context menu handler
6517
6338
  e.preventDefault();
6518
6339
  const modKeyLabel = isMac ? "⌘" : "Ctrl";
6519
6340
  showToast(`Duplicate (${modKeyLabel} + E)`);
6520
6341
  nodeContextMenuHandlers.onDuplicate();
6521
6342
  }
6522
- else if (selection.nodes.length > 1 &&
6523
- selectionContextMenuHandlers.onDuplicate) {
6343
+ else if (selection.nodes.length > 1 && selectionContextMenuHandlers.onDuplicate) {
6524
6344
  // Multiple nodes selected - use selection context menu handler
6525
6345
  e.preventDefault();
6526
6346
  const modKeyLabel = isMac ? "⌘" : "Ctrl";
@@ -6601,9 +6421,7 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6601
6421
  // Sync viewport when workbench fires graphUiChanged with viewport event
6602
6422
  useEffect(() => {
6603
6423
  const off = wb.on("graphUiChanged", (event) => {
6604
- if (event.change?.type === "viewport" &&
6605
- rfInstanceRef.current &&
6606
- event.init) {
6424
+ if (event.change?.type === "viewport" && rfInstanceRef.current && event.init) {
6607
6425
  const viewport = wb.getViewport();
6608
6426
  if (viewport) {
6609
6427
  rfInstanceRef.current.setViewport(lod.clone(viewport));
@@ -6626,36 +6444,33 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6626
6444
  (DefaultContextMenuRenderer ? (jsx(DefaultContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, keyboardShortcuts: keyboardShortcuts })) : (jsx(DefaultContextMenu, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, keyboardShortcuts: keyboardShortcuts }))), menuState?.type === "node" &&
6627
6445
  nodeContextMenuHandlers &&
6628
6446
  (NodeContextMenuRenderer ? (jsx(NodeContextMenuRenderer, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, keyboardShortcuts: keyboardShortcuts })) : (jsx(NodeContextMenu, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), menuState?.type === "selection" &&
6629
- (SelectionContextMenuRenderer ? (jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })) : (jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })))] }), jsx(SelectionActiveSync, { selection: selection })] }), toast && (jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6447
+ (SelectionContextMenuRenderer ? (jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })) : (jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })))] }), jsx(SelectionActiveSync, { selection: selection })] }), toast && jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id)] }));
6630
6448
  });
6631
6449
  const WorkbenchCanvas = WorkbenchCanvasComponent;
6632
6450
 
6633
6451
  function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
6634
- const { wb, registryVersion, runner, selectedNodeId, handlesMap, runAutoLayout, runMode, setRunMode, isRunning, } = useWorkbenchContext();
6452
+ const { wb, registryVersion, runner, selectedNodeId, handlesMap, runAutoLayout, runMode, setRunMode, isRunning } = useWorkbenchContext();
6635
6453
  const [transportStatus, setTransportStatus] = useState({
6636
6454
  state: "local",
6637
6455
  });
6638
6456
  const selectedNode = wb.def.nodes.find((n) => n.nodeId === selectedNodeId);
6639
6457
  const effectiveHandles = selectedNode
6640
- ? handlesMap[selectedNode.nodeId] ?? {
6458
+ ? (handlesMap[selectedNode.nodeId] ?? {
6641
6459
  inputs: {},
6642
6460
  outputs: {},
6643
6461
  inputDefaults: {},
6644
- }
6462
+ })
6645
6463
  : { inputs: {}, outputs: {}, inputDefaults: {} };
6646
6464
  const [exampleState, setExampleState] = useState(example ?? "");
6647
6465
  const isGraphRunning = isRunning();
6648
6466
  // Render Start/Stop button based on transport and runner state
6649
6467
  const renderStartStopButton = useCallback(() => {
6650
6468
  // Check if transport is connecting/retrying
6651
- const isConnecting = transportStatus.state === "connecting" ||
6652
- transportStatus.state === "retrying";
6469
+ const isConnecting = transportStatus.state === "connecting" || transportStatus.state === "retrying";
6653
6470
  // Only allow Start/Stop when transport is connected or local
6654
6471
  // For local backend, always allow control (transport state is "local")
6655
6472
  // For remote backends, require connection
6656
- const canControl = transportStatus.state === "connected" ||
6657
- transportStatus.state === "local" ||
6658
- backendKind === "local"; // Always allow control for local backend
6473
+ const canControl = transportStatus.state === "connected" || transportStatus.state === "local" || backendKind === "local"; // Always allow control for local backend
6659
6474
  if (isConnecting) {
6660
6475
  return (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: [jsx(ClockClockwiseIcon, { size: 16, className: "animate-spin" }), jsx("span", { className: "font-medium ml-1", children: "Connecting..." })] }));
6661
6476
  }
@@ -6675,9 +6490,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6675
6490
  const message = err instanceof Error ? err.message : String(err);
6676
6491
  alert(message);
6677
6492
  }
6678
- }, disabled: !canControl, title: !canControl
6679
- ? "Waiting for connection"
6680
- : `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsx(RocketIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6493
+ }, disabled: !canControl, title: !canControl ? "Waiting for connection" : `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsx(RocketIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
6681
6494
  }, [transportStatus, isGraphRunning, runner, runMode, wb, backendKind]);
6682
6495
  const defaultExamples = useMemo(() => [
6683
6496
  {
@@ -6917,11 +6730,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6917
6730
  try {
6918
6731
  const parsed = JSON.parse(String(raw));
6919
6732
  if (Array.isArray(parsed)) {
6920
- value = parsed.map((v) => [
6921
- Number(v?.[0] ?? 0),
6922
- Number(v?.[1] ?? 0),
6923
- Number(v?.[2] ?? 0),
6924
- ]);
6733
+ value = parsed.map((v) => [Number(v?.[0] ?? 0), Number(v?.[1] ?? 0), Number(v?.[2] ?? 0)]);
6925
6734
  break;
6926
6735
  }
6927
6736
  }
@@ -6951,14 +6760,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6951
6760
  });
6952
6761
  }
6953
6762
  return baseSetInput;
6954
- }, [
6955
- overrides,
6956
- baseSetInput,
6957
- runner,
6958
- selectedNodeId,
6959
- wb.registry,
6960
- registryVersion,
6961
- ]);
6763
+ }, [overrides, baseSetInput, runner, selectedNodeId, wb.registry, registryVersion]);
6962
6764
  const baseToString = useCallback((typeId, value) => {
6963
6765
  if (value === undefined || value === null)
6964
6766
  return "";
@@ -6969,13 +6771,8 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6969
6771
  const pre = preformatValueForDisplay(typeId, value, wb.registry);
6970
6772
  if (pre !== undefined)
6971
6773
  return pre;
6972
- if (typeof value === "object" &&
6973
- value !== null &&
6974
- "url" in value &&
6975
- typeof value.url === "string") {
6976
- const title = "title" in value && typeof value.title === "string"
6977
- ? value.title
6978
- : "";
6774
+ if (typeof value === "object" && value !== null && "url" in value && typeof value.url === "string") {
6775
+ const title = "title" in value && typeof value.title === "string" ? value.title : "";
6979
6776
  const url = String(value.url || "");
6980
6777
  // value.ts handles data URL formatting
6981
6778
  return title || url.slice(0, 32) + (url.length > 32 ? "…" : "");
@@ -6988,23 +6785,17 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
6988
6785
  const round4 = (n) => Math.round(Number(n) * 10000) / 10000;
6989
6786
  if (typeId === "base.vec3" && Array.isArray(value)) {
6990
6787
  const a = value;
6991
- return [
6992
- round4(Number(a[0] ?? 0)),
6993
- round4(Number(a[1] ?? 0)),
6994
- round4(Number(a[2] ?? 0)),
6995
- ].join(",");
6788
+ return [round4(Number(a[0] ?? 0)), round4(Number(a[1] ?? 0)), round4(Number(a[2] ?? 0))].join(",");
6996
6789
  }
6997
6790
  const stringifyRounded = (v) => {
6998
6791
  try {
6999
- return JSON.stringify(v, (_k, val) => typeof val === "number" ? round4(val) : val);
6792
+ return JSON.stringify(v, (_k, val) => (typeof val === "number" ? round4(val) : val));
7000
6793
  }
7001
6794
  catch {
7002
6795
  return String(v);
7003
6796
  }
7004
6797
  };
7005
- if (typeId?.endsWith("[]") ||
7006
- Array.isArray(value) ||
7007
- (typeof value === "object" && value !== null)) {
6798
+ if (typeId?.endsWith("[]") || Array.isArray(value) || (typeof value === "object" && value !== null)) {
7008
6799
  return stringifyRounded(value);
7009
6800
  }
7010
6801
  if (typeof value === "number") {
@@ -7014,7 +6805,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
7014
6805
  return String(value);
7015
6806
  }, [wb.registry, registryVersion]);
7016
6807
  const baseToElement = useCallback((typeId, value) => {
7017
- return (jsx("span", { className: "ml-1 opacity-60", children: baseToString(typeId, value) }));
6808
+ return jsx("span", { className: "ml-1 opacity-60", children: baseToString(typeId, value) });
7018
6809
  }, [baseToString]);
7019
6810
  const toString = useMemo(() => {
7020
6811
  if (overrides?.toString)
@@ -7028,7 +6819,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
7028
6819
  return overrides.toElement(baseToElement, { registry: wb.registry });
7029
6820
  return baseToElement;
7030
6821
  }, [overrides, baseToElement, wb.registry, registryVersion]);
7031
- return (jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runMode === "manual" ? "Manual" : "Auto"] })) : (jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsx(PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsx(WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsx(WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching example" : undefined, children: [jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching backend" : undefined, children: [jsx("option", { value: "local", children: "Local" }), jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runMode, onChange: async (e) => {
6822
+ return (jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [isGraphRunning ? (jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runMode === "manual" ? "Manual" : "Auto"] })) : (jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && jsx(PlugsConnectedIcon, { size: 14, className: "text-gray-500" }), transportStatus.state === "connecting" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && jsx(WifiHighIcon, { size: 14, className: "text-green-600" }), transportStatus.state === "disconnected" && jsx(WifiSlashIcon, { size: 14, className: "text-red-600" }), transportStatus.state === "retrying" && (jsx(ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching example" : undefined, children: [jsx("option", { value: "", children: "Select Example\u2026" }), examples.map((ex) => (jsx("option", { value: ex.id, children: ex.label }, ex.id)))] }), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: isGraphRunning, title: isGraphRunning ? "Stop engine before switching backend" : undefined, children: [jsx("option", { value: "local", children: "Local" }), jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsx("input", { className: "border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runMode, onChange: async (e) => {
7032
6823
  const mode = e.target.value;
7033
6824
  if (mode !== runMode) {
7034
6825
  await setRunMode(mode);