@bian-womp/spark-workbench 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cjs/index.cjs CHANGED
@@ -1535,17 +1535,23 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1535
1535
  const snap = await runner.snapshotFull();
1536
1536
  const remoteDef = snap?.def;
1537
1537
  if (remoteDef && Array.isArray(remoteDef.nodes)) {
1538
+ // Mutate current def in-place to avoid emitting graphChanged and causing update loop
1538
1539
  const cur = wb.export();
1539
- const merged = {
1540
- ...cur,
1541
- nodes: cur.nodes.map((n) => {
1542
- const rn = (remoteDef.nodes || []).find((m) => m.nodeId === n.nodeId);
1543
- if (rn && rn.resolvedHandles)
1544
- return { ...n, resolvedHandles: rn.resolvedHandles };
1545
- return n;
1546
- }),
1547
- };
1548
- await wb.load(merged);
1540
+ const byId = new Map((remoteDef.nodes || []).map((n) => [n.nodeId, n]));
1541
+ let changed = false;
1542
+ for (const n of cur.nodes) {
1543
+ const rn = byId.get(n.nodeId);
1544
+ if (rn && rn.resolvedHandles) {
1545
+ const before = JSON.stringify(n.resolvedHandles || {});
1546
+ const after = JSON.stringify(rn.resolvedHandles || {});
1547
+ if (before !== after) {
1548
+ n.resolvedHandles = rn.resolvedHandles;
1549
+ changed = true;
1550
+ }
1551
+ }
1552
+ }
1553
+ if (changed)
1554
+ wb.refreshValidation();
1549
1555
  }
1550
1556
  }
1551
1557
  catch { }
@@ -1557,8 +1563,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1557
1563
  ...s,
1558
1564
  [nodeId]: { ...s[nodeId], invalidated: true },
1559
1565
  }));
1560
- // On any input change, refresh resolved handles (covers context-driven dynamic ports)
1561
- refreshResolvedHandles();
1562
1566
  }
1563
1567
  return add("runner", "value")(e);
1564
1568
  });
@@ -1602,9 +1606,10 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1602
1606
  return next;
1603
1607
  });
1604
1608
  }
1605
- // After build/update, pull resolved handles from remote snapshot and merge into local def for UI
1606
- if (e?.reason === "graph-updated" || e?.reason === "graph-built")
1609
+ // After build/update, pull resolved handles and merge in-place (no graphChanged)
1610
+ if (e?.reason === "graph-updated" || e?.reason === "graph-built") {
1607
1611
  refreshResolvedHandles();
1612
+ }
1608
1613
  return add("runner", "invalidate")(e);
1609
1614
  });
1610
1615
  const off3b = runner.on("stats", (s) => {
@@ -1769,17 +1774,17 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1769
1774
  const arr = inputs[d.nodeId] ?? (inputs[d.nodeId] = []);
1770
1775
  arr.push({ handle: String(d.input), level, message, code });
1771
1776
  const nodeArr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
1772
- nodeArr.push({ level, message, code });
1777
+ nodeArr.push(is);
1773
1778
  }
1774
1779
  if (d.output) {
1775
1780
  const arr = outputs[d.nodeId] ?? (outputs[d.nodeId] = []);
1776
1781
  arr.push({ handle: String(d.output), level, message, code });
1777
1782
  const nodeArr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
1778
- nodeArr.push({ level, message, code });
1783
+ nodeArr.push(is);
1779
1784
  }
1780
1785
  if (!d.input && !d.output) {
1781
1786
  const arr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
1782
- arr.push({ level, message, code });
1787
+ arr.push(is);
1783
1788
  }
1784
1789
  }
1785
1790
  }
@@ -1791,11 +1796,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1791
1796
  return list;
1792
1797
  for (const is of validation.issues ?? []) {
1793
1798
  const d = is.data;
1794
- const level = is.level;
1795
- const code = String(is.code ?? "");
1796
- const message = String(is.message ?? code);
1797
1799
  if (!d || (!d.nodeId && !d.edgeId)) {
1798
- list.push({ level, code, message });
1800
+ list.push(is);
1799
1801
  }
1800
1802
  }
1801
1803
  return list;
@@ -1808,13 +1810,11 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1808
1810
  for (const is of validation.issues ?? []) {
1809
1811
  const d = is.data;
1810
1812
  const level = is.level;
1811
- const code = String(is.code ?? "");
1812
- const message = String(is.message ?? code);
1813
1813
  if (d?.edgeId) {
1814
1814
  if (level === "error")
1815
1815
  errors[d.edgeId] = true;
1816
1816
  const arr = issues[d.edgeId] ?? (issues[d.edgeId] = []);
1817
- arr.push({ level, message, code });
1817
+ arr.push(is);
1818
1818
  }
1819
1819
  }
1820
1820
  return { errors, issues };
@@ -2014,11 +2014,29 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2014
2014
  setOriginals(nextOriginals);
2015
2015
  }, [selectedNodeId, selectedDesc, valuesTick]);
2016
2016
  const widthClass = debug ? "w-[480px]" : "w-[320px]";
2017
- return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: "Type" }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: "DataTypeId" })] }), jsxRuntime.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) => {
2017
+ const { wb } = useWorkbenchContext();
2018
+ const deleteEdgeById = (edgeId) => {
2019
+ if (!edgeId)
2020
+ return;
2021
+ try {
2022
+ wb.disconnect(edgeId);
2023
+ }
2024
+ catch { }
2025
+ };
2026
+ return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2027
+ e.stopPropagation();
2028
+ deleteEdgeById(m.data?.edgeId);
2029
+ }, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2030
+ e.stopPropagation();
2031
+ deleteEdgeById(selectedEdge.id);
2032
+ }, title: "Delete this edge", children: "Delete edge" }) }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: "Type" }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: "DataTypeId" })] }), jsxRuntime.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) => {
2018
2033
  const v = e.target.value;
2019
2034
  const next = v === "" ? undefined : v;
2020
2035
  updateEdgeType(selectedEdge.id, next);
2021
- }, children: [jsxRuntime.jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsxRuntime.jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.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 ??
2036
+ }, children: [jsxRuntime.jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsxRuntime.jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2037
+ e.stopPropagation();
2038
+ deleteEdgeById(selectedEdge.id);
2039
+ }, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.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 ??
2022
2040
  selectedNodeStatus.lastError) }))] })), jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
2023
2041
  const typeId = sparkGraph.getInputTypeId(selectedDesc?.inputs, h);
2024
2042
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
@@ -2076,7 +2094,10 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2076
2094
  .map((v) => `${v.code}: ${v.message}`)
2077
2095
  .join("; ");
2078
2096
  return (jsxRuntime.jsx(IssueBadge, { level: outErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: outTitle }));
2079
- })()] }, h))))] }), selectedNodeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedNodeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) }), debug && (jsxRuntime.jsx("div", { className: "mt-3 flex-none min-h-0 h-[50%]", children: jsxRuntime.jsx(DebugEvents, { autoScroll: !!autoScroll, hideWorkbench: !!hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange }) }))] }));
2097
+ })()] }, h))))] }), selectedNodeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedNodeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2098
+ e.stopPropagation();
2099
+ deleteEdgeById(m.data?.edgeId);
2100
+ }, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) }), debug && (jsxRuntime.jsx("div", { className: "mt-3 flex-none min-h-0 h-[50%]", children: jsxRuntime.jsx(DebugEvents, { autoScroll: !!autoScroll, hideWorkbench: !!hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange }) }))] }));
2080
2101
  }
2081
2102
 
2082
2103
  function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray-600", outputClassName = "!w-2 !h-2 !bg-gray-600", getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {