@pipelex/mthds-ui 0.12.0 → 0.14.0

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.
@@ -1,25 +1,37 @@
1
1
  "use client";
2
2
  import {
3
3
  DEFAULT_GRAPH_CONFIG,
4
- EDGE_TYPE,
5
- FOLD_MODE,
6
- GRAPH_DIRECTION,
7
- GRAPH_THEME,
8
- GRAPH_THEME_MODE,
9
4
  MAX_VISIBLE_CONTROLLER_CHILDREN,
10
5
  applyControllers,
11
6
  applyFolds,
7
+ applyValidationDecorations,
8
+ buildChildToControllerMap,
12
9
  buildGraph,
10
+ buildValidationDecorations,
13
11
  findCousinControllers,
14
12
  getLayoutedElements,
15
13
  getPaletteForTheme,
16
14
  getPipeBlueprint,
17
15
  resolveConceptRef,
16
+ resolveIssueTargetNodeId,
17
+ validateGraphSpec
18
+ } from "../../chunk-V4FKGLKA.js";
19
+ import {
20
+ EDGE_TYPE,
21
+ FOLD_MODE,
22
+ GRAPH_DIRECTION,
23
+ GRAPH_SPEC_MODE,
24
+ GRAPH_THEME,
25
+ GRAPH_THEME_MODE,
26
+ TOOLBAR_POSITION,
27
+ VALIDATION_STATE,
28
+ graphSpecMode,
29
+ isDryGraphSpec,
30
+ isStaticGraphSpec,
18
31
  stuffDigestFromId,
19
32
  toolbarOrientation,
20
- toolbarSide,
21
- validateGraphSpec
22
- } from "../../chunk-R34J4GFM.js";
33
+ toolbarSide
34
+ } from "../../chunk-GS2KODND.js";
23
35
  import {
24
36
  __spreadProps,
25
37
  __spreadValues
@@ -32,7 +44,7 @@ import "./stuff/StuffViewer.css";
32
44
  import "./viewer/GraphToolbar.css";
33
45
 
34
46
  // src/graph/react/viewer/GraphViewer.tsx
35
- import React9 from "react";
47
+ import React10 from "react";
36
48
  import {
37
49
  ReactFlow,
38
50
  useNodesState,
@@ -1264,6 +1276,8 @@ function PipeDetailPanel({ node, spec, onConceptClick }) {
1264
1276
  var _a, _b, _c, _d, _e, _f, _g;
1265
1277
  const pipeType = node.pipe_type;
1266
1278
  const isController = CONTROLLER_TYPES.has(pipeType);
1279
+ const isStatic = isStaticGraphSpec(spec);
1280
+ const showsGeneratedData = !isStatic && !isDryGraphSpec(spec);
1267
1281
  const badge = PIPE_TYPE_BADGES[pipeType];
1268
1282
  const status = node.status;
1269
1283
  const statusColor = (_a = STATUS_COLORS[status]) != null ? _a : "#6272a4";
@@ -1299,7 +1313,7 @@ function PipeDetailPanel({ node, spec, onConceptClick }) {
1299
1313
  }
1300
1314
  )
1301
1315
  ] }),
1302
- /* @__PURE__ */ jsxs14("div", { className: "detail-status", children: [
1316
+ !isStatic && /* @__PURE__ */ jsxs14("div", { className: "detail-status", children: [
1303
1317
  /* @__PURE__ */ jsx14("span", { className: "detail-status-dot", style: { background: statusColor } }),
1304
1318
  /* @__PURE__ */ jsx14("span", { className: "detail-status-label", style: { color: statusColor }, children: status }),
1305
1319
  ((_g = node.timing) == null ? void 0 : _g.duration) != null && /* @__PURE__ */ jsx14("span", { className: "detail-duration", children: formatDuration(node.timing.duration) })
@@ -1339,8 +1353,14 @@ function PipeDetailPanel({ node, spec, onConceptClick }) {
1339
1353
  ] }),
1340
1354
  (inputs.length > 0 || outputs.length > 0) && /* @__PURE__ */ jsx14("div", { style: { borderTop: "1px solid rgba(255,255,255,0.06)", margin: "4px 0" } })
1341
1355
  ] }),
1342
- blueprint && /* @__PURE__ */ jsx14(BlueprintSection, { blueprint, executionData: node.execution_data }),
1343
- node.execution_data && Object.keys(node.execution_data).length > 0 && /* @__PURE__ */ jsx14(
1356
+ blueprint && /* @__PURE__ */ jsx14(
1357
+ BlueprintSection,
1358
+ {
1359
+ blueprint,
1360
+ executionData: showsGeneratedData ? node.execution_data : void 0
1361
+ }
1362
+ ),
1363
+ showsGeneratedData && node.execution_data && Object.keys(node.execution_data).length > 0 && /* @__PURE__ */ jsx14(
1344
1364
  ExecutionDataSection,
1345
1365
  {
1346
1366
  executionData: node.execution_data,
@@ -1353,7 +1373,7 @@ function PipeDetailPanel({ node, spec, onConceptClick }) {
1353
1373
  /* @__PURE__ */ jsx14("div", { className: "detail-error-message", children: node.error.message }),
1354
1374
  node.error.stack && /* @__PURE__ */ jsx14("div", { className: "detail-error-stack", children: node.error.stack })
1355
1375
  ] }),
1356
- node.metrics && Object.keys(node.metrics).length > 0 && /* @__PURE__ */ jsxs14("div", { children: [
1376
+ showsGeneratedData && node.metrics && Object.keys(node.metrics).length > 0 && /* @__PURE__ */ jsxs14("div", { children: [
1357
1377
  /* @__PURE__ */ jsx14("div", { className: "detail-section-label", children: "Metrics" }),
1358
1378
  Object.entries(node.metrics).map(([key, value]) => /* @__PURE__ */ jsx14(
1359
1379
  KV,
@@ -1724,10 +1744,125 @@ function hydrateLabels(nodes) {
1724
1744
  }
1725
1745
 
1726
1746
  // src/graph/react/viewer/GraphToolbar.tsx
1747
+ import React8 from "react";
1727
1748
  import { Panel } from "@xyflow/react";
1728
- import { Fragment as Fragment12, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1749
+
1750
+ // src/graph/react/viewer/ValidationPanel.tsx
1751
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1752
+ function validationLabel(state, issueCount) {
1753
+ const issues = issueCount === 1 ? "1 issue" : `${issueCount} issues`;
1754
+ switch (state) {
1755
+ case VALIDATION_STATE.VALIDATING:
1756
+ return issueCount > 0 ? `Validating method\u2026 \u2014 ${issues}` : "Validating method\u2026";
1757
+ case VALIDATION_STATE.VALID:
1758
+ return issueCount > 0 ? `Method is valid \u2014 ${issues}` : "Method is valid";
1759
+ case VALIDATION_STATE.INVALID:
1760
+ return `Method is invalid \u2014 ${issues}`;
1761
+ case VALIDATION_STATE.ERROR:
1762
+ return "Validation could not run";
1763
+ default: {
1764
+ const _exhaustive = state;
1765
+ return _exhaustive;
1766
+ }
1767
+ }
1768
+ }
1769
+ function validationPanelPlacement(position) {
1770
+ switch (position) {
1771
+ case TOOLBAR_POSITION.TOP_LEFT:
1772
+ case TOOLBAR_POSITION.TOP_CENTER:
1773
+ return "down-start";
1774
+ case TOOLBAR_POSITION.TOP_RIGHT:
1775
+ return "down-end";
1776
+ case TOOLBAR_POSITION.BOTTOM_LEFT:
1777
+ case TOOLBAR_POSITION.BOTTOM_CENTER:
1778
+ return "up-start";
1779
+ case TOOLBAR_POSITION.BOTTOM_RIGHT:
1780
+ return "up-end";
1781
+ case TOOLBAR_POSITION.CENTER_LEFT:
1782
+ return "side-end";
1783
+ case TOOLBAR_POSITION.CENTER_RIGHT:
1784
+ return "side-start";
1785
+ default: {
1786
+ const _exhaustive = position;
1787
+ return _exhaustive;
1788
+ }
1789
+ }
1790
+ }
1791
+ function validationEmptyText(state) {
1792
+ switch (state) {
1793
+ case VALIDATION_STATE.VALIDATING:
1794
+ return "Validating\u2026";
1795
+ case VALIDATION_STATE.VALID:
1796
+ return "No issues found.";
1797
+ case VALIDATION_STATE.INVALID:
1798
+ return "No details available.";
1799
+ case VALIDATION_STATE.ERROR:
1800
+ return "Validation could not run.";
1801
+ default: {
1802
+ const _exhaustive = state;
1803
+ return _exhaustive;
1804
+ }
1805
+ }
1806
+ }
1807
+ function ValidationPanel({
1808
+ state,
1809
+ issues,
1810
+ onIssueClick,
1811
+ placement,
1812
+ id
1813
+ }) {
1814
+ return /* @__PURE__ */ jsxs17(
1815
+ "div",
1816
+ {
1817
+ id,
1818
+ className: `graph-validation-panel graph-validation-panel--${placement}`,
1819
+ role: "region",
1820
+ "aria-label": "Validation issues",
1821
+ children: [
1822
+ /* @__PURE__ */ jsx17("div", { className: "graph-validation-panel-header", children: validationLabel(state, issues.length) }),
1823
+ issues.length === 0 ? /* @__PURE__ */ jsx17("div", { className: "graph-validation-empty", children: validationEmptyText(state) }) : /* @__PURE__ */ jsx17("ul", { className: "graph-validation-issues", role: "list", children: issues.map((issue, index) => {
1824
+ const clickable = onIssueClick !== void 0;
1825
+ const meta = (issue.context || issue.file) && /* @__PURE__ */ jsxs17("div", { className: "graph-validation-issue-meta", children: [
1826
+ issue.context && /* @__PURE__ */ jsx17("span", { className: "graph-validation-issue-context", children: issue.context }),
1827
+ issue.file && /* @__PURE__ */ jsx17("span", { className: "graph-validation-issue-file", children: issue.file })
1828
+ ] });
1829
+ return /* @__PURE__ */ jsx17(
1830
+ "li",
1831
+ {
1832
+ className: `graph-validation-issue graph-validation-issue--${issue.severity}` + (clickable ? " graph-validation-issue--clickable" : ""),
1833
+ children: /* @__PURE__ */ jsxs17(
1834
+ "div",
1835
+ {
1836
+ className: "graph-validation-issue-body",
1837
+ role: clickable ? "button" : void 0,
1838
+ tabIndex: clickable ? 0 : void 0,
1839
+ onClick: clickable ? () => onIssueClick(index, issue) : void 0,
1840
+ onKeyDown: clickable ? (event) => {
1841
+ if (event.key === "Enter" || event.key === " ") {
1842
+ event.preventDefault();
1843
+ onIssueClick(index, issue);
1844
+ }
1845
+ } : void 0,
1846
+ children: [
1847
+ meta,
1848
+ /* @__PURE__ */ jsx17("div", { className: "graph-validation-issue-message", children: issue.message }),
1849
+ issue.suggestedFix && /* @__PURE__ */ jsx17("div", { className: "graph-validation-issue-fix", children: issue.suggestedFix })
1850
+ ]
1851
+ }
1852
+ )
1853
+ },
1854
+ index
1855
+ );
1856
+ }) })
1857
+ ]
1858
+ }
1859
+ );
1860
+ }
1861
+
1862
+ // src/graph/react/viewer/GraphToolbar.tsx
1863
+ import { Fragment as Fragment12, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1729
1864
  var REACTFLOW_PANEL_MARGIN = 15;
1730
- var ARROW_RIGHT_ICON = /* @__PURE__ */ jsxs17(
1865
+ var ARROW_RIGHT_ICON = /* @__PURE__ */ jsxs18(
1731
1866
  "svg",
1732
1867
  {
1733
1868
  viewBox: "0 0 24 24",
@@ -1739,12 +1874,12 @@ var ARROW_RIGHT_ICON = /* @__PURE__ */ jsxs17(
1739
1874
  strokeLinecap: "round",
1740
1875
  strokeLinejoin: "round",
1741
1876
  children: [
1742
- /* @__PURE__ */ jsx17("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
1743
- /* @__PURE__ */ jsx17("polyline", { points: "12 5 19 12 12 19" })
1877
+ /* @__PURE__ */ jsx18("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
1878
+ /* @__PURE__ */ jsx18("polyline", { points: "12 5 19 12 12 19" })
1744
1879
  ]
1745
1880
  }
1746
1881
  );
1747
- var ARROW_DOWN_ICON = /* @__PURE__ */ jsxs17(
1882
+ var ARROW_DOWN_ICON = /* @__PURE__ */ jsxs18(
1748
1883
  "svg",
1749
1884
  {
1750
1885
  viewBox: "0 0 24 24",
@@ -1756,12 +1891,12 @@ var ARROW_DOWN_ICON = /* @__PURE__ */ jsxs17(
1756
1891
  strokeLinecap: "round",
1757
1892
  strokeLinejoin: "round",
1758
1893
  children: [
1759
- /* @__PURE__ */ jsx17("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1760
- /* @__PURE__ */ jsx17("polyline", { points: "19 12 12 19 5 12" })
1894
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1895
+ /* @__PURE__ */ jsx18("polyline", { points: "19 12 12 19 5 12" })
1761
1896
  ]
1762
1897
  }
1763
1898
  );
1764
- var MINUS_ICON = /* @__PURE__ */ jsx17(
1899
+ var MINUS_ICON = /* @__PURE__ */ jsx18(
1765
1900
  "svg",
1766
1901
  {
1767
1902
  viewBox: "0 0 24 24",
@@ -1772,10 +1907,10 @@ var MINUS_ICON = /* @__PURE__ */ jsx17(
1772
1907
  strokeWidth: "2",
1773
1908
  strokeLinecap: "round",
1774
1909
  strokeLinejoin: "round",
1775
- children: /* @__PURE__ */ jsx17("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1910
+ children: /* @__PURE__ */ jsx18("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1776
1911
  }
1777
1912
  );
1778
- var PLUS_ICON = /* @__PURE__ */ jsxs17(
1913
+ var PLUS_ICON = /* @__PURE__ */ jsxs18(
1779
1914
  "svg",
1780
1915
  {
1781
1916
  viewBox: "0 0 24 24",
@@ -1787,12 +1922,12 @@ var PLUS_ICON = /* @__PURE__ */ jsxs17(
1787
1922
  strokeLinecap: "round",
1788
1923
  strokeLinejoin: "round",
1789
1924
  children: [
1790
- /* @__PURE__ */ jsx17("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1791
- /* @__PURE__ */ jsx17("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1925
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1926
+ /* @__PURE__ */ jsx18("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1792
1927
  ]
1793
1928
  }
1794
1929
  );
1795
- var FIT_VIEW_ICON = /* @__PURE__ */ jsxs17(
1930
+ var FIT_VIEW_ICON = /* @__PURE__ */ jsxs18(
1796
1931
  "svg",
1797
1932
  {
1798
1933
  viewBox: "0 0 24 24",
@@ -1804,14 +1939,14 @@ var FIT_VIEW_ICON = /* @__PURE__ */ jsxs17(
1804
1939
  strokeLinecap: "round",
1805
1940
  strokeLinejoin: "round",
1806
1941
  children: [
1807
- /* @__PURE__ */ jsx17("polyline", { points: "15 3 21 3 21 9" }),
1808
- /* @__PURE__ */ jsx17("polyline", { points: "9 21 3 21 3 15" }),
1809
- /* @__PURE__ */ jsx17("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
1810
- /* @__PURE__ */ jsx17("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
1942
+ /* @__PURE__ */ jsx18("polyline", { points: "15 3 21 3 21 9" }),
1943
+ /* @__PURE__ */ jsx18("polyline", { points: "9 21 3 21 3 15" }),
1944
+ /* @__PURE__ */ jsx18("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
1945
+ /* @__PURE__ */ jsx18("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
1811
1946
  ]
1812
1947
  }
1813
1948
  );
1814
- var BOXES_ICON = /* @__PURE__ */ jsxs17(
1949
+ var BOXES_ICON = /* @__PURE__ */ jsxs18(
1815
1950
  "svg",
1816
1951
  {
1817
1952
  viewBox: "0 0 24 24",
@@ -1823,22 +1958,22 @@ var BOXES_ICON = /* @__PURE__ */ jsxs17(
1823
1958
  strokeLinecap: "round",
1824
1959
  strokeLinejoin: "round",
1825
1960
  children: [
1826
- /* @__PURE__ */ jsx17("path", { d: "M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" }),
1827
- /* @__PURE__ */ jsx17("path", { d: "m7 16.5-4.74-2.85" }),
1828
- /* @__PURE__ */ jsx17("path", { d: "m7 16.5 5-3" }),
1829
- /* @__PURE__ */ jsx17("path", { d: "M7 16.5v5.17" }),
1830
- /* @__PURE__ */ jsx17("path", { d: "M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" }),
1831
- /* @__PURE__ */ jsx17("path", { d: "m17 16.5-5-3" }),
1832
- /* @__PURE__ */ jsx17("path", { d: "m17 16.5 4.74-2.85" }),
1833
- /* @__PURE__ */ jsx17("path", { d: "M17 16.5v5.17" }),
1834
- /* @__PURE__ */ jsx17("path", { d: "M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" }),
1835
- /* @__PURE__ */ jsx17("path", { d: "M12 8 7.26 5.15" }),
1836
- /* @__PURE__ */ jsx17("path", { d: "m12 8 4.74-2.85" }),
1837
- /* @__PURE__ */ jsx17("path", { d: "M12 13.5V8" })
1961
+ /* @__PURE__ */ jsx18("path", { d: "M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" }),
1962
+ /* @__PURE__ */ jsx18("path", { d: "m7 16.5-4.74-2.85" }),
1963
+ /* @__PURE__ */ jsx18("path", { d: "m7 16.5 5-3" }),
1964
+ /* @__PURE__ */ jsx18("path", { d: "M7 16.5v5.17" }),
1965
+ /* @__PURE__ */ jsx18("path", { d: "M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" }),
1966
+ /* @__PURE__ */ jsx18("path", { d: "m17 16.5-5-3" }),
1967
+ /* @__PURE__ */ jsx18("path", { d: "m17 16.5 4.74-2.85" }),
1968
+ /* @__PURE__ */ jsx18("path", { d: "M17 16.5v5.17" }),
1969
+ /* @__PURE__ */ jsx18("path", { d: "M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" }),
1970
+ /* @__PURE__ */ jsx18("path", { d: "M12 8 7.26 5.15" }),
1971
+ /* @__PURE__ */ jsx18("path", { d: "m12 8 4.74-2.85" }),
1972
+ /* @__PURE__ */ jsx18("path", { d: "M12 13.5V8" })
1838
1973
  ]
1839
1974
  }
1840
1975
  );
1841
- var FOLD_ALL_ICON = /* @__PURE__ */ jsxs17(
1976
+ var FOLD_ALL_ICON = /* @__PURE__ */ jsxs18(
1842
1977
  "svg",
1843
1978
  {
1844
1979
  viewBox: "0 0 24 24",
@@ -1850,15 +1985,15 @@ var FOLD_ALL_ICON = /* @__PURE__ */ jsxs17(
1850
1985
  strokeLinecap: "round",
1851
1986
  strokeLinejoin: "round",
1852
1987
  children: [
1853
- /* @__PURE__ */ jsx17("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }),
1854
- /* @__PURE__ */ jsx17("polyline", { points: "2 2 6 6 2 6" }),
1855
- /* @__PURE__ */ jsx17("polyline", { points: "22 2 18 6 22 6" }),
1856
- /* @__PURE__ */ jsx17("polyline", { points: "2 22 6 18 2 18" }),
1857
- /* @__PURE__ */ jsx17("polyline", { points: "22 22 18 18 22 18" })
1988
+ /* @__PURE__ */ jsx18("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }),
1989
+ /* @__PURE__ */ jsx18("polyline", { points: "2 2 6 6 2 6" }),
1990
+ /* @__PURE__ */ jsx18("polyline", { points: "22 2 18 6 22 6" }),
1991
+ /* @__PURE__ */ jsx18("polyline", { points: "2 22 6 18 2 18" }),
1992
+ /* @__PURE__ */ jsx18("polyline", { points: "22 22 18 18 22 18" })
1858
1993
  ]
1859
1994
  }
1860
1995
  );
1861
- var EXPAND_ALL_ICON = /* @__PURE__ */ jsxs17(
1996
+ var EXPAND_ALL_ICON = /* @__PURE__ */ jsxs18(
1862
1997
  "svg",
1863
1998
  {
1864
1999
  viewBox: "0 0 24 24",
@@ -1870,15 +2005,15 @@ var EXPAND_ALL_ICON = /* @__PURE__ */ jsxs17(
1870
2005
  strokeLinecap: "round",
1871
2006
  strokeLinejoin: "round",
1872
2007
  children: [
1873
- /* @__PURE__ */ jsx17("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" }),
1874
- /* @__PURE__ */ jsx17("polyline", { points: "3 3 7 7 3 7" }),
1875
- /* @__PURE__ */ jsx17("polyline", { points: "21 3 17 7 21 7" }),
1876
- /* @__PURE__ */ jsx17("polyline", { points: "3 21 7 17 3 17" }),
1877
- /* @__PURE__ */ jsx17("polyline", { points: "21 21 17 17 21 17" })
2008
+ /* @__PURE__ */ jsx18("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" }),
2009
+ /* @__PURE__ */ jsx18("polyline", { points: "3 3 7 7 3 7" }),
2010
+ /* @__PURE__ */ jsx18("polyline", { points: "21 3 17 7 21 7" }),
2011
+ /* @__PURE__ */ jsx18("polyline", { points: "3 21 7 17 3 17" }),
2012
+ /* @__PURE__ */ jsx18("polyline", { points: "21 21 17 17 21 17" })
1878
2013
  ]
1879
2014
  }
1880
2015
  );
1881
- var SUN_ICON = /* @__PURE__ */ jsxs17(
2016
+ var SUN_ICON = /* @__PURE__ */ jsxs18(
1882
2017
  "svg",
1883
2018
  {
1884
2019
  viewBox: "0 0 24 24",
@@ -1890,19 +2025,19 @@ var SUN_ICON = /* @__PURE__ */ jsxs17(
1890
2025
  strokeLinecap: "round",
1891
2026
  strokeLinejoin: "round",
1892
2027
  children: [
1893
- /* @__PURE__ */ jsx17("circle", { cx: "12", cy: "12", r: "4" }),
1894
- /* @__PURE__ */ jsx17("line", { x1: "12", y1: "2", x2: "12", y2: "4" }),
1895
- /* @__PURE__ */ jsx17("line", { x1: "12", y1: "20", x2: "12", y2: "22" }),
1896
- /* @__PURE__ */ jsx17("line", { x1: "4.93", y1: "4.93", x2: "6.34", y2: "6.34" }),
1897
- /* @__PURE__ */ jsx17("line", { x1: "17.66", y1: "17.66", x2: "19.07", y2: "19.07" }),
1898
- /* @__PURE__ */ jsx17("line", { x1: "2", y1: "12", x2: "4", y2: "12" }),
1899
- /* @__PURE__ */ jsx17("line", { x1: "20", y1: "12", x2: "22", y2: "12" }),
1900
- /* @__PURE__ */ jsx17("line", { x1: "4.93", y1: "19.07", x2: "6.34", y2: "17.66" }),
1901
- /* @__PURE__ */ jsx17("line", { x1: "17.66", y1: "6.34", x2: "19.07", y2: "4.93" })
2028
+ /* @__PURE__ */ jsx18("circle", { cx: "12", cy: "12", r: "4" }),
2029
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "2", x2: "12", y2: "4" }),
2030
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "20", x2: "12", y2: "22" }),
2031
+ /* @__PURE__ */ jsx18("line", { x1: "4.93", y1: "4.93", x2: "6.34", y2: "6.34" }),
2032
+ /* @__PURE__ */ jsx18("line", { x1: "17.66", y1: "17.66", x2: "19.07", y2: "19.07" }),
2033
+ /* @__PURE__ */ jsx18("line", { x1: "2", y1: "12", x2: "4", y2: "12" }),
2034
+ /* @__PURE__ */ jsx18("line", { x1: "20", y1: "12", x2: "22", y2: "12" }),
2035
+ /* @__PURE__ */ jsx18("line", { x1: "4.93", y1: "19.07", x2: "6.34", y2: "17.66" }),
2036
+ /* @__PURE__ */ jsx18("line", { x1: "17.66", y1: "6.34", x2: "19.07", y2: "4.93" })
1902
2037
  ]
1903
2038
  }
1904
2039
  );
1905
- var MOON_ICON = /* @__PURE__ */ jsx17(
2040
+ var MOON_ICON = /* @__PURE__ */ jsx18(
1906
2041
  "svg",
1907
2042
  {
1908
2043
  viewBox: "0 0 24 24",
@@ -1913,10 +2048,10 @@ var MOON_ICON = /* @__PURE__ */ jsx17(
1913
2048
  strokeWidth: "2",
1914
2049
  strokeLinecap: "round",
1915
2050
  strokeLinejoin: "round",
1916
- children: /* @__PURE__ */ jsx17("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
2051
+ children: /* @__PURE__ */ jsx18("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
1917
2052
  }
1918
2053
  );
1919
- var MONITOR_ICON = /* @__PURE__ */ jsxs17(
2054
+ var MONITOR_ICON = /* @__PURE__ */ jsxs18(
1920
2055
  "svg",
1921
2056
  {
1922
2057
  viewBox: "0 0 24 24",
@@ -1928,12 +2063,93 @@ var MONITOR_ICON = /* @__PURE__ */ jsxs17(
1928
2063
  strokeLinecap: "round",
1929
2064
  strokeLinejoin: "round",
1930
2065
  children: [
1931
- /* @__PURE__ */ jsx17("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
1932
- /* @__PURE__ */ jsx17("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
1933
- /* @__PURE__ */ jsx17("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
2066
+ /* @__PURE__ */ jsx18("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
2067
+ /* @__PURE__ */ jsx18("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
2068
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
1934
2069
  ]
1935
2070
  }
1936
2071
  );
2072
+ var SPINNER_ICON = /* @__PURE__ */ jsx18(
2073
+ "svg",
2074
+ {
2075
+ className: "graph-toolbar-validation-spin",
2076
+ viewBox: "0 0 24 24",
2077
+ width: "14",
2078
+ height: "14",
2079
+ fill: "none",
2080
+ stroke: "currentColor",
2081
+ strokeWidth: "2",
2082
+ strokeLinecap: "round",
2083
+ strokeLinejoin: "round",
2084
+ children: /* @__PURE__ */ jsx18("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
2085
+ }
2086
+ );
2087
+ var CHECK_ICON2 = /* @__PURE__ */ jsx18(
2088
+ "svg",
2089
+ {
2090
+ viewBox: "0 0 24 24",
2091
+ width: "14",
2092
+ height: "14",
2093
+ fill: "none",
2094
+ stroke: "currentColor",
2095
+ strokeWidth: "2",
2096
+ strokeLinecap: "round",
2097
+ strokeLinejoin: "round",
2098
+ children: /* @__PURE__ */ jsx18("polyline", { points: "20 6 9 17 4 12" })
2099
+ }
2100
+ );
2101
+ var X_CIRCLE_ICON = /* @__PURE__ */ jsxs18(
2102
+ "svg",
2103
+ {
2104
+ viewBox: "0 0 24 24",
2105
+ width: "14",
2106
+ height: "14",
2107
+ fill: "none",
2108
+ stroke: "currentColor",
2109
+ strokeWidth: "2",
2110
+ strokeLinecap: "round",
2111
+ strokeLinejoin: "round",
2112
+ children: [
2113
+ /* @__PURE__ */ jsx18("circle", { cx: "12", cy: "12", r: "10" }),
2114
+ /* @__PURE__ */ jsx18("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
2115
+ /* @__PURE__ */ jsx18("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
2116
+ ]
2117
+ }
2118
+ );
2119
+ var ALERT_TRIANGLE_ICON = /* @__PURE__ */ jsxs18(
2120
+ "svg",
2121
+ {
2122
+ viewBox: "0 0 24 24",
2123
+ width: "14",
2124
+ height: "14",
2125
+ fill: "none",
2126
+ stroke: "currentColor",
2127
+ strokeWidth: "2",
2128
+ strokeLinecap: "round",
2129
+ strokeLinejoin: "round",
2130
+ children: [
2131
+ /* @__PURE__ */ jsx18("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }),
2132
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
2133
+ /* @__PURE__ */ jsx18("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
2134
+ ]
2135
+ }
2136
+ );
2137
+ function validationIcon(state) {
2138
+ switch (state) {
2139
+ case VALIDATION_STATE.VALIDATING:
2140
+ return SPINNER_ICON;
2141
+ case VALIDATION_STATE.VALID:
2142
+ return CHECK_ICON2;
2143
+ case VALIDATION_STATE.INVALID:
2144
+ return X_CIRCLE_ICON;
2145
+ case VALIDATION_STATE.ERROR:
2146
+ return ALERT_TRIANGLE_ICON;
2147
+ default: {
2148
+ const _exhaustive = state;
2149
+ return _exhaustive;
2150
+ }
2151
+ }
2152
+ }
1937
2153
  var THEME_MODE_CYCLE = [
1938
2154
  GRAPH_THEME_MODE.SYSTEM,
1939
2155
  GRAPH_THEME_MODE.LIGHT,
@@ -1972,10 +2188,42 @@ function GraphToolbar({
1972
2188
  themeMode,
1973
2189
  onThemeModeChange,
1974
2190
  rightOffset = 0,
1975
- position
2191
+ position,
2192
+ validationState,
2193
+ validationIssues = [],
2194
+ onValidationIssueClick,
2195
+ validationOpen = false,
2196
+ onValidationOpenChange
1976
2197
  }) {
1977
2198
  const themeToggleEnabled = themeMode !== void 0 && onThemeModeChange !== void 0;
1978
2199
  const orientation = toolbarOrientation(position);
2200
+ const setValidationOpen = onValidationOpenChange != null ? onValidationOpenChange : (() => {
2201
+ });
2202
+ const setValidationOpenRef = React8.useRef(setValidationOpen);
2203
+ setValidationOpenRef.current = setValidationOpen;
2204
+ const validationRef = React8.useRef(null);
2205
+ const validationPanelId = React8.useId();
2206
+ React8.useEffect(() => {
2207
+ if (validationState === void 0) setValidationOpenRef.current(false);
2208
+ }, [validationState]);
2209
+ React8.useEffect(() => {
2210
+ if (!validationOpen) return;
2211
+ const onPointerDown = (event) => {
2212
+ const wrapper = validationRef.current;
2213
+ if (wrapper && event.target instanceof Node && !wrapper.contains(event.target)) {
2214
+ setValidationOpenRef.current(false);
2215
+ }
2216
+ };
2217
+ const onKeyDown = (event) => {
2218
+ if (event.key === "Escape") setValidationOpenRef.current(false);
2219
+ };
2220
+ document.addEventListener("mousedown", onPointerDown);
2221
+ document.addEventListener("keydown", onKeyDown);
2222
+ return () => {
2223
+ document.removeEventListener("mousedown", onPointerDown);
2224
+ document.removeEventListener("keydown", onKeyDown);
2225
+ };
2226
+ }, [validationOpen]);
1979
2227
  const dodgesDetailPanel = toolbarSide(position) === "right" && rightOffset > 0;
1980
2228
  const panelStyle = dodgesDetailPanel ? { marginRight: REACTFLOW_PANEL_MARGIN + rightOffset } : void 0;
1981
2229
  const isVertical = direction === GRAPH_DIRECTION.TB || direction === GRAPH_DIRECTION.BT;
@@ -1984,8 +2232,40 @@ function GraphToolbar({
1984
2232
  const foldAllSection = onFoldAll || onExpandAll;
1985
2233
  const foldAllTitle = foldAllDisabled ? "Fold all controllers (nothing to fold)" : "Fold all controllers";
1986
2234
  const expandAllTitle = expandAllDisabled ? "Expand all controllers (nothing to expand)" : "Expand all controllers";
1987
- return /* @__PURE__ */ jsx17(Panel, { position, className: "graph-toolbar-panel", style: panelStyle, children: /* @__PURE__ */ jsxs17("div", { className: `graph-toolbar graph-toolbar--${orientation}`, children: [
1988
- onFoldAll && /* @__PURE__ */ jsx17(
2235
+ return /* @__PURE__ */ jsx18(Panel, { position, className: "graph-toolbar-panel", style: panelStyle, children: /* @__PURE__ */ jsxs18("div", { className: `graph-toolbar graph-toolbar--${orientation}`, children: [
2236
+ validationState !== void 0 && /* @__PURE__ */ jsxs18(Fragment12, { children: [
2237
+ /* @__PURE__ */ jsxs18("div", { className: "graph-toolbar-validation", ref: validationRef, children: [
2238
+ /* @__PURE__ */ jsxs18(
2239
+ "button",
2240
+ {
2241
+ type: "button",
2242
+ className: `graph-toolbar-btn graph-toolbar-validation-btn graph-toolbar-validation-btn--${validationState}`,
2243
+ onClick: () => setValidationOpen(!validationOpen),
2244
+ title: validationLabel(validationState, validationIssues.length),
2245
+ "aria-label": validationLabel(validationState, validationIssues.length),
2246
+ "aria-expanded": validationOpen,
2247
+ "aria-haspopup": "true",
2248
+ "aria-controls": validationPanelId,
2249
+ children: [
2250
+ validationIcon(validationState),
2251
+ validationIssues.length > 0 && /* @__PURE__ */ jsx18("span", { className: "graph-toolbar-validation-badge", children: validationIssues.length > 99 ? "99+" : validationIssues.length })
2252
+ ]
2253
+ }
2254
+ ),
2255
+ validationOpen && /* @__PURE__ */ jsx18(
2256
+ ValidationPanel,
2257
+ {
2258
+ id: validationPanelId,
2259
+ state: validationState,
2260
+ issues: validationIssues,
2261
+ onIssueClick: onValidationIssueClick,
2262
+ placement: validationPanelPlacement(position)
2263
+ }
2264
+ )
2265
+ ] }),
2266
+ /* @__PURE__ */ jsx18("div", { className: "graph-toolbar-separator" })
2267
+ ] }),
2268
+ onFoldAll && /* @__PURE__ */ jsx18(
1989
2269
  "button",
1990
2270
  {
1991
2271
  type: "button",
@@ -1997,7 +2277,7 @@ function GraphToolbar({
1997
2277
  children: FOLD_ALL_ICON
1998
2278
  }
1999
2279
  ),
2000
- onExpandAll && /* @__PURE__ */ jsx17(
2280
+ onExpandAll && /* @__PURE__ */ jsx18(
2001
2281
  "button",
2002
2282
  {
2003
2283
  type: "button",
@@ -2009,8 +2289,8 @@ function GraphToolbar({
2009
2289
  children: EXPAND_ALL_ICON
2010
2290
  }
2011
2291
  ),
2012
- foldAllSection && /* @__PURE__ */ jsx17("div", { className: "graph-toolbar-separator" }),
2013
- /* @__PURE__ */ jsx17(
2292
+ foldAllSection && /* @__PURE__ */ jsx18("div", { className: "graph-toolbar-separator" }),
2293
+ /* @__PURE__ */ jsx18(
2014
2294
  "button",
2015
2295
  {
2016
2296
  type: "button",
@@ -2021,7 +2301,7 @@ function GraphToolbar({
2021
2301
  children: BOXES_ICON
2022
2302
  }
2023
2303
  ),
2024
- /* @__PURE__ */ jsx17(
2304
+ /* @__PURE__ */ jsx18(
2025
2305
  "button",
2026
2306
  {
2027
2307
  type: "button",
@@ -2032,8 +2312,8 @@ function GraphToolbar({
2032
2312
  children: isVertical ? ARROW_RIGHT_ICON : ARROW_DOWN_ICON
2033
2313
  }
2034
2314
  ),
2035
- (onZoomOut || onZoomIn || onFitView) && /* @__PURE__ */ jsx17("div", { className: "graph-toolbar-separator" }),
2036
- onZoomOut && /* @__PURE__ */ jsx17(
2315
+ (onZoomOut || onZoomIn || onFitView) && /* @__PURE__ */ jsx18("div", { className: "graph-toolbar-separator" }),
2316
+ onZoomOut && /* @__PURE__ */ jsx18(
2037
2317
  "button",
2038
2318
  {
2039
2319
  type: "button",
@@ -2044,7 +2324,7 @@ function GraphToolbar({
2044
2324
  children: MINUS_ICON
2045
2325
  }
2046
2326
  ),
2047
- onZoomIn && /* @__PURE__ */ jsx17(
2327
+ onZoomIn && /* @__PURE__ */ jsx18(
2048
2328
  "button",
2049
2329
  {
2050
2330
  type: "button",
@@ -2055,7 +2335,7 @@ function GraphToolbar({
2055
2335
  children: PLUS_ICON
2056
2336
  }
2057
2337
  ),
2058
- onFitView && /* @__PURE__ */ jsx17(
2338
+ onFitView && /* @__PURE__ */ jsx18(
2059
2339
  "button",
2060
2340
  {
2061
2341
  type: "button",
@@ -2066,9 +2346,9 @@ function GraphToolbar({
2066
2346
  children: FIT_VIEW_ICON
2067
2347
  }
2068
2348
  ),
2069
- themeToggleEnabled && /* @__PURE__ */ jsxs17(Fragment12, { children: [
2070
- /* @__PURE__ */ jsx17("div", { className: "graph-toolbar-separator" }),
2071
- /* @__PURE__ */ jsx17(
2349
+ themeToggleEnabled && /* @__PURE__ */ jsxs18(Fragment12, { children: [
2350
+ /* @__PURE__ */ jsx18("div", { className: "graph-toolbar-separator" }),
2351
+ /* @__PURE__ */ jsx18(
2072
2352
  "button",
2073
2353
  {
2074
2354
  type: "button",
@@ -2083,8 +2363,40 @@ function GraphToolbar({
2083
2363
  ] }) });
2084
2364
  }
2085
2365
 
2366
+ // src/graph/react/nodes/NodeValidationBadge.tsx
2367
+ import { jsx as jsx19 } from "react/jsx-runtime";
2368
+ function NodeValidationBadge({
2369
+ validation,
2370
+ onClick
2371
+ }) {
2372
+ const label = `${validation.count} validation ${validation.count === 1 ? "issue" : "issues"}`;
2373
+ const tooltip = validation.lines.join("\n");
2374
+ const className = `node-validation-badge node-validation-badge--${validation.severity}`;
2375
+ const text = validation.count > 99 ? "99+" : String(validation.count);
2376
+ if (!onClick) {
2377
+ return /* @__PURE__ */ jsx19("span", { className, title: tooltip, "aria-label": label, children: text });
2378
+ }
2379
+ return /* @__PURE__ */ jsx19(
2380
+ "button",
2381
+ {
2382
+ type: "button",
2383
+ className,
2384
+ title: tooltip,
2385
+ "aria-label": `${label} \u2014 open validation panel`,
2386
+ onClick: (e) => {
2387
+ e.stopPropagation();
2388
+ onClick();
2389
+ },
2390
+ children: text
2391
+ }
2392
+ );
2393
+ }
2394
+ function validationRingClass(validation) {
2395
+ return validation ? ` node-validation-ring--${validation.severity}` : "";
2396
+ }
2397
+
2086
2398
  // src/graph/react/nodes/controller/ControllerGroupNode.tsx
2087
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2399
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2088
2400
  var CONTROLLER_CONFIG = {
2089
2401
  PipeSequence: { badge: "Sequence", icon: "\u2192" },
2090
2402
  PipeParallel: { badge: "Parallel", icon: "//" },
@@ -2106,12 +2418,12 @@ function ControllerGroupNode({ data }) {
2106
2418
  const modifier = getControllerModifier(data.pipeType);
2107
2419
  const isCollapsible = (data.pipeType === "PipeParallel" || data.pipeType === "PipeBatch") && ((_a = data.childCount) != null ? _a : 0) > MAX_VISIBLE_CONTROLLER_CHILDREN;
2108
2420
  const hiddenCount = isCollapsible ? ((_b = data.childCount) != null ? _b : 0) - MAX_VISIBLE_CONTROLLER_CHILDREN : 0;
2109
- return /* @__PURE__ */ jsxs18("div", { className: `controller-group-node ${modifier}`, children: [
2110
- /* @__PURE__ */ jsxs18("div", { className: "controller-group-header", children: [
2111
- /* @__PURE__ */ jsx18("span", { className: "controller-group-icon", children: config.icon }),
2112
- /* @__PURE__ */ jsx18("span", { className: "controller-group-badge", children: config.badge }),
2113
- data.label && /* @__PURE__ */ jsx18("span", { className: "controller-group-label", children: data.label }),
2114
- data.onToggleFold && /* @__PURE__ */ jsx18(
2421
+ return /* @__PURE__ */ jsxs19("div", { className: `controller-group-node ${modifier}${validationRingClass(data.validation)}`, children: [
2422
+ /* @__PURE__ */ jsxs19("div", { className: "controller-group-header", children: [
2423
+ /* @__PURE__ */ jsx20("span", { className: "controller-group-icon", children: config.icon }),
2424
+ /* @__PURE__ */ jsx20("span", { className: "controller-group-badge", children: config.badge }),
2425
+ data.label && /* @__PURE__ */ jsx20("span", { className: "controller-group-label", children: data.label }),
2426
+ data.onToggleFold && /* @__PURE__ */ jsx20(
2115
2427
  "button",
2116
2428
  {
2117
2429
  type: "button",
@@ -2125,9 +2437,10 @@ function ControllerGroupNode({ data }) {
2125
2437
  },
2126
2438
  children: "\u2921"
2127
2439
  }
2128
- )
2440
+ ),
2441
+ data.validation && /* @__PURE__ */ jsx20(NodeValidationBadge, { validation: data.validation, onClick: data.onValidationBadgeClick })
2129
2442
  ] }),
2130
- isCollapsible && /* @__PURE__ */ jsx18(
2443
+ isCollapsible && /* @__PURE__ */ jsx20(
2131
2444
  "button",
2132
2445
  {
2133
2446
  className: "controller-group-collapse",
@@ -2148,7 +2461,7 @@ import { Handle, Position } from "@xyflow/react";
2148
2461
 
2149
2462
  // src/graph/react/nodes/pipe/PipeCardBase.tsx
2150
2463
  import { useState as useState2 } from "react";
2151
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2464
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
2152
2465
  var PIPE_TYPE_BADGES2 = {
2153
2466
  PipeLLM: "LLM",
2154
2467
  PipeExtract: "Extract",
@@ -2185,11 +2498,15 @@ function getBadge(pipeType) {
2185
2498
  return PIPE_TYPE_BADGES2[pipeType];
2186
2499
  }
2187
2500
  function PipeCardBase({ data, children }) {
2501
+ var _a, _b;
2188
2502
  const badge = getBadge(data.pipeType);
2189
2503
  const statusConfig = STATUS_CONFIG[data.status];
2190
2504
  const isRunning = data.status === "running";
2191
2505
  const isController = isControllerType(data.pipeType);
2192
2506
  const isSignature = data.pipeType === "PipeSignature";
2507
+ const isStatic = data.graphMode === "static";
2508
+ const outcome = isStatic ? (_a = data.tags) == null ? void 0 : _a.outcome : void 0;
2509
+ const batchMultiplicity = isStatic ? (_b = data.tags) == null ? void 0 : _b.batch_multiplicity : void 0;
2193
2510
  const [inputsExpanded, setInputsExpanded] = useState2(false);
2194
2511
  const hasMany = data.inputs.length > MAX_VISIBLE_INPUTS;
2195
2512
  const visibleInputs = hasMany && !inputsExpanded ? data.inputs.slice(0, MAX_VISIBLE_INPUTS) : data.inputs;
@@ -2197,88 +2514,104 @@ function PipeCardBase({ data, children }) {
2197
2514
  const dirClass = data.direction === "TB" ? "pipe-card--tb" : "pipe-card--lr";
2198
2515
  const controllerClass = isController ? " pipe-card--controller" : "";
2199
2516
  const signatureClass = isSignature ? " pipe-card--signature" : "";
2517
+ const staticClass = isStatic ? " pipe-card--static" : "";
2200
2518
  const badgeClass = isController ? "pipe-card-badge pipe-card-badge--controller" : isSignature ? "pipe-card-badge pipe-card-badge--signature" : "pipe-card-badge";
2201
- return /* @__PURE__ */ jsxs19("div", { className: `pipe-card ${dirClass}${controllerClass}${signatureClass}`, children: [
2202
- /* @__PURE__ */ jsxs19("div", { className: "pipe-card-header", children: [
2203
- /* @__PURE__ */ jsx19("span", { className: badgeClass, children: badge }),
2204
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-code", title: data.pipeCode, children: data.pipeCode }),
2205
- /* @__PURE__ */ jsx19(
2206
- "span",
2207
- {
2208
- className: "pipe-card-status",
2209
- style: { color: statusConfig.color },
2210
- title: statusConfig.label,
2211
- children: /* @__PURE__ */ jsx19(
2519
+ const ringClass = validationRingClass(data.validation);
2520
+ return /* @__PURE__ */ jsxs20(
2521
+ "div",
2522
+ {
2523
+ className: `pipe-card ${dirClass}${controllerClass}${signatureClass}${staticClass}${ringClass}`,
2524
+ children: [
2525
+ /* @__PURE__ */ jsxs20("div", { className: "pipe-card-header", children: [
2526
+ /* @__PURE__ */ jsx21("span", { className: badgeClass, children: badge }),
2527
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-code", title: data.pipeCode, children: data.pipeCode }),
2528
+ !isStatic && /* @__PURE__ */ jsx21(
2212
2529
  "span",
2213
2530
  {
2214
- className: `pipe-card-status-dot ${isRunning ? "pipe-card-status-dot--pulse" : ""}`,
2215
- style: { background: statusConfig.color }
2531
+ className: "pipe-card-status",
2532
+ style: { color: statusConfig.color },
2533
+ title: statusConfig.label,
2534
+ children: /* @__PURE__ */ jsx21(
2535
+ "span",
2536
+ {
2537
+ className: `pipe-card-status-dot ${isRunning ? "pipe-card-status-dot--pulse" : ""}`,
2538
+ style: { background: statusConfig.color }
2539
+ }
2540
+ )
2216
2541
  }
2217
- )
2218
- }
2219
- ),
2220
- data.onExpand && /* @__PURE__ */ jsx19(
2221
- "button",
2222
- {
2223
- type: "button",
2224
- className: "pipe-card-expand",
2225
- title: "Expand controller (alt/option: only this one)",
2226
- "aria-label": "Expand controller",
2227
- onClick: (e) => {
2228
- var _a;
2229
- e.stopPropagation();
2230
- (_a = data.onExpand) == null ? void 0 : _a.call(data, { soloMode: e.altKey });
2231
- },
2232
- children: "\u2922"
2233
- }
2234
- )
2235
- ] }),
2236
- data.description && /* @__PURE__ */ jsx19("span", { className: "pipe-card-description", title: data.description, children: data.description }),
2237
- data.inputs.length > 0 && /* @__PURE__ */ jsxs19("div", { className: "pipe-card-io", children: [
2238
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-io-label", children: "INPUTS" }),
2239
- /* @__PURE__ */ jsxs19("div", { className: "pipe-card-io-pills", children: [
2240
- visibleInputs.map((input) => /* @__PURE__ */ jsxs19(
2241
- "span",
2242
- {
2243
- className: "pipe-card-io-pill",
2244
- title: `${input.name}: ${input.concept}`,
2245
- children: [
2246
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-io-pill-name", children: input.name }),
2247
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-io-pill-concept", children: input.concept })
2248
- ]
2249
- },
2250
- input.name
2251
- )),
2252
- hasMany && /* @__PURE__ */ jsx19(
2253
- "button",
2254
- {
2255
- className: "pipe-card-io-more",
2256
- onClick: (e) => {
2257
- e.stopPropagation();
2258
- setInputsExpanded((v) => !v);
2542
+ ),
2543
+ data.onExpand && /* @__PURE__ */ jsx21(
2544
+ "button",
2545
+ {
2546
+ type: "button",
2547
+ className: "pipe-card-expand",
2548
+ title: "Expand controller (alt/option: only this one)",
2549
+ "aria-label": "Expand controller",
2550
+ onClick: (e) => {
2551
+ var _a2;
2552
+ e.stopPropagation();
2553
+ (_a2 = data.onExpand) == null ? void 0 : _a2.call(data, { soloMode: e.altKey });
2554
+ },
2555
+ children: "\u2922"
2556
+ }
2557
+ ),
2558
+ data.validation && /* @__PURE__ */ jsx21(NodeValidationBadge, { validation: data.validation, onClick: data.onValidationBadgeClick })
2559
+ ] }),
2560
+ (outcome || batchMultiplicity) && /* @__PURE__ */ jsxs20("div", { className: "pipe-card-annotations", children: [
2561
+ outcome && /* @__PURE__ */ jsxs20("span", { className: "pipe-card-annotation pipe-card-annotation--outcome", children: [
2562
+ "outcome: ",
2563
+ outcome
2564
+ ] }),
2565
+ batchMultiplicity && /* @__PURE__ */ jsx21("span", { className: "pipe-card-annotation pipe-card-annotation--batch", children: batchMultiplicity })
2566
+ ] }),
2567
+ data.description && /* @__PURE__ */ jsx21("span", { className: "pipe-card-description", title: data.description, children: data.description }),
2568
+ data.inputs.length > 0 && /* @__PURE__ */ jsxs20("div", { className: "pipe-card-io", children: [
2569
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-io-label", children: "INPUTS" }),
2570
+ /* @__PURE__ */ jsxs20("div", { className: "pipe-card-io-pills", children: [
2571
+ visibleInputs.map((input) => /* @__PURE__ */ jsxs20(
2572
+ "span",
2573
+ {
2574
+ className: "pipe-card-io-pill",
2575
+ title: `${input.name}: ${input.concept}`,
2576
+ children: [
2577
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-io-pill-name", children: input.name }),
2578
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-io-pill-concept", children: input.concept })
2579
+ ]
2580
+ },
2581
+ input.name
2582
+ )),
2583
+ hasMany && /* @__PURE__ */ jsx21(
2584
+ "button",
2585
+ {
2586
+ className: "pipe-card-io-more",
2587
+ onClick: (e) => {
2588
+ e.stopPropagation();
2589
+ setInputsExpanded((v) => !v);
2590
+ },
2591
+ children: inputsExpanded ? "show less" : `+${hiddenCount} more`
2592
+ }
2593
+ )
2594
+ ] })
2595
+ ] }),
2596
+ data.outputs.length > 0 && /* @__PURE__ */ jsxs20("div", { className: "pipe-card-io", children: [
2597
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-io-label", children: "OUTPUT" }),
2598
+ /* @__PURE__ */ jsx21("div", { className: "pipe-card-io-pills", children: data.outputs.map((output) => /* @__PURE__ */ jsxs20(
2599
+ "span",
2600
+ {
2601
+ className: "pipe-card-io-pill",
2602
+ title: `${output.name}: ${output.concept}`,
2603
+ children: [
2604
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-io-pill-name", children: output.name }),
2605
+ /* @__PURE__ */ jsx21("span", { className: "pipe-card-io-pill-concept", children: output.concept })
2606
+ ]
2259
2607
  },
2260
- children: inputsExpanded ? "show less" : `+${hiddenCount} more`
2261
- }
2262
- )
2263
- ] })
2264
- ] }),
2265
- data.outputs.length > 0 && /* @__PURE__ */ jsxs19("div", { className: "pipe-card-io", children: [
2266
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-io-label", children: "OUTPUT" }),
2267
- /* @__PURE__ */ jsx19("div", { className: "pipe-card-io-pills", children: data.outputs.map((output) => /* @__PURE__ */ jsxs19(
2268
- "span",
2269
- {
2270
- className: "pipe-card-io-pill",
2271
- title: `${output.name}: ${output.concept}`,
2272
- children: [
2273
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-io-pill-name", children: output.name }),
2274
- /* @__PURE__ */ jsx19("span", { className: "pipe-card-io-pill-concept", children: output.concept })
2275
- ]
2276
- },
2277
- output.name
2278
- )) })
2279
- ] }),
2280
- children
2281
- ] });
2608
+ output.name
2609
+ )) })
2610
+ ] }),
2611
+ children
2612
+ ]
2613
+ }
2614
+ );
2282
2615
  }
2283
2616
 
2284
2617
  // src/graph/react/nodes/pipe/pipeCardRegistry.ts
@@ -2301,11 +2634,11 @@ function getPipeCardComponent(pipeType) {
2301
2634
  }
2302
2635
 
2303
2636
  // src/graph/react/nodes/pipe/PipeCardNode.tsx
2304
- import { Fragment as Fragment13, jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
2637
+ import { Fragment as Fragment13, jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
2305
2638
  function PipeCardNode({ data }) {
2306
2639
  var _a;
2307
2640
  const Card = (_a = getPipeCardComponent(data.pipeType)) != null ? _a : PipeCardBase;
2308
- return /* @__PURE__ */ jsx20(Card, { data });
2641
+ return /* @__PURE__ */ jsx22(Card, { data });
2309
2642
  }
2310
2643
  function PipeCardRFNode({
2311
2644
  data,
@@ -2314,15 +2647,15 @@ function PipeCardRFNode({
2314
2647
  }) {
2315
2648
  const payload = data.pipeCardData;
2316
2649
  if (!payload) return null;
2317
- return /* @__PURE__ */ jsxs20(Fragment13, { children: [
2318
- /* @__PURE__ */ jsx20(Handle, { type: "target", position: targetPosition }),
2319
- /* @__PURE__ */ jsx20(PipeCardNode, { data: payload }),
2320
- /* @__PURE__ */ jsx20(Handle, { type: "source", position: sourcePosition })
2650
+ return /* @__PURE__ */ jsxs21(Fragment13, { children: [
2651
+ /* @__PURE__ */ jsx22(Handle, { type: "target", position: targetPosition }),
2652
+ /* @__PURE__ */ jsx22(PipeCardNode, { data: payload }),
2653
+ /* @__PURE__ */ jsx22(Handle, { type: "source", position: sourcePosition })
2321
2654
  ] });
2322
2655
  }
2323
2656
 
2324
2657
  // src/graph/react/viewer/GraphViewer.tsx
2325
- import { Fragment as Fragment14, jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
2658
+ import { Fragment as Fragment14, jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
2326
2659
  var nodeTypes = __spreadProps(__spreadValues({}, controllerNodeTypes), {
2327
2660
  pipeCard: PipeCardRFNode
2328
2661
  });
@@ -2335,19 +2668,21 @@ function StuffNodeDetail({
2335
2668
  onOpenExternally
2336
2669
  }) {
2337
2670
  const conceptInfo = stuffData.concept && graphspec ? resolveConceptRef(graphspec, stuffData.concept) : void 0;
2338
- return /* @__PURE__ */ jsx21(Fragment14, { children: conceptInfo ? /* @__PURE__ */ jsx21(
2671
+ const isDryRun = graphSpecMode(graphspec) === GRAPH_SPEC_MODE.DRY;
2672
+ return /* @__PURE__ */ jsx23(Fragment14, { children: conceptInfo ? /* @__PURE__ */ jsx23(
2339
2673
  ConceptDetailPanel,
2340
2674
  {
2341
2675
  concept: conceptInfo,
2342
2676
  ioData: stuffData,
2677
+ isDryRun,
2343
2678
  instanceKey: nodeId,
2344
2679
  resolveStorageUrl,
2345
2680
  canEmbedPdf,
2346
2681
  onOpenExternally
2347
2682
  }
2348
- ) : (
2683
+ ) : isDryRun ? /* @__PURE__ */ jsx23("div", { className: "detail-not-available", children: "Dry run data hidden" }) : (
2349
2684
  /* Fallback: just show the StuffViewer if no concept info */
2350
- /* @__PURE__ */ jsx21(
2685
+ /* @__PURE__ */ jsx23(
2351
2686
  StuffViewer,
2352
2687
  {
2353
2688
  stuff: stuffData,
@@ -2373,6 +2708,9 @@ function resolveToolbarPosition(positionProp, configPosition) {
2373
2708
  var _a;
2374
2709
  return (_a = positionProp != null ? positionProp : configPosition) != null ? _a : DEFAULT_GRAPH_CONFIG.toolbarPosition;
2375
2710
  }
2711
+ function stripFlash(className) {
2712
+ return (className == null ? void 0 : className.replace(/\s*\bnode-validation-flash\b/, "")) || void 0;
2713
+ }
2376
2714
  function cloneCachedNodes(nodes) {
2377
2715
  return nodes.map((n) => __spreadProps(__spreadValues({}, n), {
2378
2716
  position: __spreadValues({}, n.position),
@@ -2383,11 +2721,14 @@ function cloneCachedNodes(nodes) {
2383
2721
  function applyStatusOverrides(nodes, statusMap) {
2384
2722
  if (!statusMap || Object.keys(statusMap).length === 0) return nodes;
2385
2723
  return nodes.map((node) => {
2386
- var _a;
2724
+ var _a, _b;
2725
+ if (node.data.graphMode === "static" || ((_a = node.data.pipeCardData) == null ? void 0 : _a.graphMode) === "static") {
2726
+ return node;
2727
+ }
2387
2728
  const pipeCode = node.data.pipeCode;
2388
2729
  if (!pipeCode || !Object.hasOwn(statusMap, pipeCode)) return node;
2389
2730
  const newStatus = statusMap[pipeCode];
2390
- if (((_a = node.data.pipeCardData) == null ? void 0 : _a.status) === newStatus) return node;
2731
+ if (((_b = node.data.pipeCardData) == null ? void 0 : _b.status) === newStatus) return node;
2391
2732
  return __spreadProps(__spreadValues({}, node), {
2392
2733
  data: __spreadProps(__spreadValues({}, node.data), {
2393
2734
  nodeData: node.data.nodeData ? __spreadProps(__spreadValues({}, node.data.nodeData), { status: newStatus }) : node.data.nodeData,
@@ -2419,22 +2760,25 @@ function GraphViewer(props) {
2419
2760
  renderDetailExtra,
2420
2761
  resolveStorageUrl,
2421
2762
  canEmbedPdf,
2422
- onOpenExternally
2763
+ onOpenExternally,
2764
+ validationState,
2765
+ validationIssues,
2766
+ onValidationIssueClick
2423
2767
  } = props;
2424
- const graphspec = React9.useMemo(
2768
+ const graphspec = React10.useMemo(
2425
2769
  () => graphspecProp === null ? null : validateGraphSpec(graphspecProp),
2426
2770
  [graphspecProp]
2427
2771
  );
2428
- const [direction, setDirection] = React9.useState(
2772
+ const [direction, setDirection] = React10.useState(
2429
2773
  () => {
2430
2774
  var _a2, _b2;
2431
2775
  return (_b2 = (_a2 = initialDirection != null ? initialDirection : config.direction) != null ? _a2 : DEFAULT_GRAPH_CONFIG.direction) != null ? _b2 : GRAPH_DIRECTION.TB;
2432
2776
  }
2433
2777
  );
2434
2778
  const externalMode = resolveExternalThemeMode(themeProp, config.theme);
2435
- const [mode, setMode] = React9.useState(externalMode);
2436
- const prevExternalModeRef = React9.useRef(externalMode);
2437
- React9.useEffect(() => {
2779
+ const [mode, setMode] = React10.useState(externalMode);
2780
+ const prevExternalModeRef = React10.useRef(externalMode);
2781
+ React10.useEffect(() => {
2438
2782
  if (externalMode !== prevExternalModeRef.current) {
2439
2783
  prevExternalModeRef.current = externalMode;
2440
2784
  setMode(externalMode);
@@ -2442,13 +2786,13 @@ function GraphViewer(props) {
2442
2786
  }, [externalMode]);
2443
2787
  const systemTheme = useSystemTheme(systemThemeProp);
2444
2788
  const resolvedTheme = resolveActiveTheme(mode, systemTheme);
2445
- const onThemeChangeRef = React9.useRef(onThemeChange);
2789
+ const onThemeChangeRef = React10.useRef(onThemeChange);
2446
2790
  onThemeChangeRef.current = onThemeChange;
2447
- const prevReportedRef = React9.useRef({
2791
+ const prevReportedRef = React10.useRef({
2448
2792
  mode,
2449
2793
  resolvedTheme
2450
2794
  });
2451
- React9.useEffect(() => {
2795
+ React10.useEffect(() => {
2452
2796
  var _a2;
2453
2797
  const prev = prevReportedRef.current;
2454
2798
  if (prev.mode !== mode || prev.resolvedTheme !== resolvedTheme) {
@@ -2457,26 +2801,26 @@ function GraphViewer(props) {
2457
2801
  }
2458
2802
  }, [mode, resolvedTheme]);
2459
2803
  const effectiveFoldMode = (_b = (_a = initialFoldMode != null ? initialFoldMode : config.foldMode) != null ? _a : DEFAULT_GRAPH_CONFIG.foldMode) != null ? _b : FOLD_MODE.EXPANDED;
2460
- const [showControllers, setShowControllers] = React9.useState(() => {
2804
+ const [showControllers, setShowControllers] = React10.useState(() => {
2461
2805
  var _a2, _b2;
2462
2806
  if (effectiveFoldMode === FOLD_MODE.FOLDED) return true;
2463
2807
  return (_b2 = (_a2 = initialShowControllers != null ? initialShowControllers : config.showControllers) != null ? _a2 : DEFAULT_GRAPH_CONFIG.showControllers) != null ? _b2 : false;
2464
2808
  });
2465
- const foldModeRef = React9.useRef(effectiveFoldMode);
2809
+ const foldModeRef = React10.useRef(effectiveFoldMode);
2466
2810
  foldModeRef.current = effectiveFoldMode;
2467
- const containerRef = React9.useRef(null);
2468
- const [detailSelection, setDetailSelection] = React9.useState(null);
2469
- const [conceptOverride, setConceptOverride] = React9.useState(null);
2811
+ const containerRef = React10.useRef(null);
2812
+ const [detailSelection, setDetailSelection] = React10.useState(null);
2813
+ const [conceptOverride, setConceptOverride] = React10.useState(null);
2470
2814
  const {
2471
2815
  width: panelWidth,
2472
2816
  isDragging: isPanelDragging,
2473
2817
  handleMouseDown: onResizeMouseDown
2474
2818
  } = useResizable({ defaultWidth: 380, minWidth: 280, maxWidth: 800, containerRef });
2475
- React9.useEffect(() => {
2819
+ React10.useEffect(() => {
2476
2820
  setDetailSelection(null);
2477
2821
  setConceptOverride(null);
2478
2822
  }, [graphspec]);
2479
- React9.useEffect(() => {
2823
+ React10.useEffect(() => {
2480
2824
  const el = containerRef.current;
2481
2825
  if (!el) return;
2482
2826
  const themePalette = getPaletteForTheme(resolvedTheme);
@@ -2493,12 +2837,12 @@ function GraphViewer(props) {
2493
2837
  }, [config.paletteColors, resolvedTheme]);
2494
2838
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
2495
2839
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
2496
- const reactFlowRef = React9.useRef(null);
2497
- const initialDataRef = React9.useRef(null);
2498
- const rawGraphDataRef = React9.useRef(null);
2499
- const layoutCacheRef = React9.useRef(null);
2500
- const [expandedControllers, setExpandedControllers] = React9.useState(/* @__PURE__ */ new Set());
2501
- const toggleCollapse = React9.useCallback((controllerId) => {
2840
+ const reactFlowRef = React10.useRef(null);
2841
+ const initialDataRef = React10.useRef(null);
2842
+ const rawGraphDataRef = React10.useRef(null);
2843
+ const layoutCacheRef = React10.useRef(null);
2844
+ const [expandedControllers, setExpandedControllers] = React10.useState(/* @__PURE__ */ new Set());
2845
+ const toggleCollapse = React10.useCallback((controllerId) => {
2502
2846
  setExpandedControllers((prev) => {
2503
2847
  const next = new Set(prev);
2504
2848
  if (next.has(controllerId)) next.delete(controllerId);
@@ -2506,8 +2850,8 @@ function GraphViewer(props) {
2506
2850
  return next;
2507
2851
  });
2508
2852
  }, []);
2509
- const [foldedControllers, setFoldedControllers] = React9.useState(/* @__PURE__ */ new Set());
2510
- const toggleFold = React9.useCallback((controllerId, options) => {
2853
+ const [foldedControllers, setFoldedControllers] = React10.useState(/* @__PURE__ */ new Set());
2854
+ const toggleFold = React10.useCallback((controllerId, options) => {
2511
2855
  setFoldedControllers((prev) => {
2512
2856
  const next = new Set(prev);
2513
2857
  const shouldFold = !next.has(controllerId);
@@ -2521,34 +2865,112 @@ function GraphViewer(props) {
2521
2865
  });
2522
2866
  }, []);
2523
2867
  const edgeType = config.edgeType || EDGE_TYPE.DEFAULT;
2524
- const layoutConfig = React9.useMemo(
2868
+ const layoutConfig = React10.useMemo(
2525
2869
  () => ({ nodesep: config.nodesep, ranksep: config.ranksep }),
2526
2870
  [config.nodesep, config.ranksep]
2527
2871
  );
2528
- const showControllersRef = React9.useRef(showControllers);
2872
+ const showControllersRef = React10.useRef(showControllers);
2529
2873
  showControllersRef.current = showControllers;
2530
- const directionRef = React9.useRef(direction);
2874
+ const directionRef = React10.useRef(direction);
2531
2875
  directionRef.current = direction;
2532
- const layoutConfigRef = React9.useRef(layoutConfig);
2876
+ const layoutConfigRef = React10.useRef(layoutConfig);
2533
2877
  layoutConfigRef.current = layoutConfig;
2534
- const initialZoomRef = React9.useRef(config.initialZoom);
2878
+ const initialZoomRef = React10.useRef(config.initialZoom);
2535
2879
  initialZoomRef.current = config.initialZoom;
2536
- const panToTopRef = React9.useRef(config.panToTop);
2880
+ const panToTopRef = React10.useRef(config.panToTop);
2537
2881
  panToTopRef.current = config.panToTop;
2538
- const expandedRef = React9.useRef(expandedControllers);
2882
+ const expandedRef = React10.useRef(expandedControllers);
2539
2883
  expandedRef.current = expandedControllers;
2540
- const toggleCollapseRef = React9.useRef(toggleCollapse);
2884
+ const toggleCollapseRef = React10.useRef(toggleCollapse);
2541
2885
  toggleCollapseRef.current = toggleCollapse;
2542
- const foldedRef = React9.useRef(foldedControllers);
2886
+ const foldedRef = React10.useRef(foldedControllers);
2543
2887
  foldedRef.current = foldedControllers;
2544
- const toggleFoldRef = React9.useRef(toggleFold);
2888
+ const toggleFoldRef = React10.useRef(toggleFold);
2545
2889
  toggleFoldRef.current = toggleFold;
2546
- const isFirstFoldEffect = React9.useRef(true);
2547
- const prevFoldSizeRef = React9.useRef(0);
2548
- const skipNextFoldEffectRef = React9.useRef(false);
2549
- const statusMapRef = React9.useRef(statusMap);
2890
+ const isFirstFoldEffect = React10.useRef(true);
2891
+ const prevFoldSizeRef = React10.useRef(0);
2892
+ const skipNextFoldEffectRef = React10.useRef(false);
2893
+ const statusMapRef = React10.useRef(statusMap);
2550
2894
  statusMapRef.current = statusMap;
2551
- React9.useEffect(() => {
2895
+ const validationIssuesRef = React10.useRef(validationIssues);
2896
+ validationIssuesRef.current = validationIssues;
2897
+ const [validationOpen, setValidationOpen] = React10.useState(false);
2898
+ const openValidationPanel = React10.useCallback(() => setValidationOpen(true), []);
2899
+ const validationWidgetAvailable = validationState !== void 0 && !hideToolbar;
2900
+ const validationWidgetAvailableRef = React10.useRef(validationWidgetAvailable);
2901
+ validationWidgetAvailableRef.current = validationWidgetAvailable;
2902
+ const decorateNodes = React10.useCallback(
2903
+ (nodes2) => {
2904
+ var _a2, _b2;
2905
+ const raw = rawGraphDataRef.current;
2906
+ const decorations = buildValidationDecorations(
2907
+ validationIssuesRef.current,
2908
+ (_a2 = raw == null ? void 0 : raw.graphspec) != null ? _a2 : null,
2909
+ (_b2 = raw == null ? void 0 : raw.childToCtrl) != null ? _b2 : {},
2910
+ foldedRef.current
2911
+ );
2912
+ return applyValidationDecorations(
2913
+ applyStatusOverrides(toAppNodes(hydrateLabels(nodes2)), statusMapRef.current),
2914
+ decorations,
2915
+ validationWidgetAvailableRef.current ? openValidationPanel : void 0
2916
+ );
2917
+ },
2918
+ [openValidationPanel]
2919
+ );
2920
+ const nodesRef = React10.useRef([]);
2921
+ nodesRef.current = nodes;
2922
+ const onValidationIssueClickRef = React10.useRef(onValidationIssueClick);
2923
+ onValidationIssueClickRef.current = onValidationIssueClick;
2924
+ const flashTimeoutRef = React10.useRef(null);
2925
+ React10.useEffect(
2926
+ () => () => {
2927
+ if (flashTimeoutRef.current) clearTimeout(flashTimeoutRef.current);
2928
+ },
2929
+ []
2930
+ );
2931
+ const handleValidationIssueClick = React10.useCallback(
2932
+ (index, issue) => {
2933
+ var _a2, _b2;
2934
+ (_a2 = onValidationIssueClickRef.current) == null ? void 0 : _a2.call(onValidationIssueClickRef, index, issue);
2935
+ const raw = rawGraphDataRef.current;
2936
+ if (!(raw == null ? void 0 : raw.graphspec) || !raw.analysis) return;
2937
+ const targetId = resolveIssueTargetNodeId(
2938
+ issue,
2939
+ raw.graphspec,
2940
+ raw.childToCtrl,
2941
+ foldedRef.current,
2942
+ new Set(nodesRef.current.map((n) => n.id))
2943
+ );
2944
+ if (!targetId) return;
2945
+ void ((_b2 = reactFlowRef.current) == null ? void 0 : _b2.fitView({
2946
+ nodes: [{ id: targetId }],
2947
+ duration: 500,
2948
+ padding: 0.4
2949
+ }));
2950
+ if (flashTimeoutRef.current) clearTimeout(flashTimeoutRef.current);
2951
+ setNodes(
2952
+ (nds) => nds.map((n) => {
2953
+ const base = stripFlash(n.className);
2954
+ if (n.id === targetId) {
2955
+ return __spreadProps(__spreadValues({}, n), {
2956
+ className: base ? `${base} node-validation-flash` : "node-validation-flash"
2957
+ });
2958
+ }
2959
+ return base === n.className ? n : __spreadProps(__spreadValues({}, n), { className: base });
2960
+ })
2961
+ );
2962
+ flashTimeoutRef.current = setTimeout(() => {
2963
+ setNodes(
2964
+ (nds) => nds.map((n) => {
2965
+ const base = stripFlash(n.className);
2966
+ return base === n.className ? n : __spreadProps(__spreadValues({}, n), { className: base });
2967
+ })
2968
+ );
2969
+ }, 1800);
2970
+ },
2971
+ [setNodes]
2972
+ );
2973
+ React10.useEffect(() => {
2552
2974
  if (!initialDataRef.current) return;
2553
2975
  let cancelled = false;
2554
2976
  void (async () => {
@@ -2567,7 +2989,8 @@ function GraphViewer(props) {
2567
2989
  layoutCacheRef.current = {
2568
2990
  nodes: relayouted.nodes,
2569
2991
  edges: relayouted.edges,
2570
- controllerPositions: relayouted.controllerPositions
2992
+ controllerPositions: relayouted.controllerPositions,
2993
+ graphspec: data._graphspec
2571
2994
  };
2572
2995
  const withControllers = applyControllers(
2573
2996
  cloneCachedNodes(relayouted.nodes),
@@ -2580,12 +3003,7 @@ function GraphViewer(props) {
2580
3003
  relayouted.controllerPositions,
2581
3004
  toggleFoldRef.current
2582
3005
  );
2583
- setNodes(
2584
- applyStatusOverrides(
2585
- toAppNodes(hydrateLabels(withControllers.nodes)),
2586
- statusMapRef.current
2587
- )
2588
- );
3006
+ setNodes(decorateNodes(withControllers.nodes));
2589
3007
  setEdges(toAppEdges(withControllers.edges));
2590
3008
  setTimeout(() => {
2591
3009
  if (!cancelled && reactFlowRef.current) {
@@ -2600,8 +3018,9 @@ function GraphViewer(props) {
2600
3018
  cancelled = true;
2601
3019
  };
2602
3020
  }, [direction, layoutConfig]);
2603
- React9.useEffect(() => {
3021
+ React10.useEffect(() => {
2604
3022
  if (!layoutCacheRef.current || !initialDataRef.current) return;
3023
+ if (layoutCacheRef.current.graphspec !== initialDataRef.current._graphspec) return;
2605
3024
  const cachedNodes = cloneCachedNodes(layoutCacheRef.current.nodes);
2606
3025
  const cachedEdges = layoutCacheRef.current.edges;
2607
3026
  const withControllers = applyControllers(
@@ -2615,12 +3034,10 @@ function GraphViewer(props) {
2615
3034
  layoutCacheRef.current.controllerPositions,
2616
3035
  toggleFold
2617
3036
  );
2618
- setNodes(
2619
- applyStatusOverrides(toAppNodes(hydrateLabels(withControllers.nodes)), statusMapRef.current)
2620
- );
3037
+ setNodes(decorateNodes(withControllers.nodes));
2621
3038
  setEdges(toAppEdges(withControllers.edges));
2622
- }, [showControllers, expandedControllers, toggleCollapse, toggleFold]);
2623
- React9.useEffect(() => {
3039
+ }, [showControllers, expandedControllers, toggleCollapse, toggleFold, decorateNodes]);
3040
+ React10.useEffect(() => {
2624
3041
  if (!graphspec) {
2625
3042
  initialDataRef.current = null;
2626
3043
  rawGraphDataRef.current = null;
@@ -2637,7 +3054,8 @@ function GraphViewer(props) {
2637
3054
  nodes: graphData.nodes,
2638
3055
  edges: graphData.edges,
2639
3056
  analysis,
2640
- graphspec
3057
+ graphspec,
3058
+ childToCtrl: analysis ? buildChildToControllerMap(graphspec, analysis) : {}
2641
3059
  };
2642
3060
  const seedSet = analysis ? seedFoldedControllers(foldModeRef.current, analysis.controllerNodeIds) : /* @__PURE__ */ new Set();
2643
3061
  setFoldedControllers(seedSet);
@@ -2684,7 +3102,8 @@ function GraphViewer(props) {
2684
3102
  layoutCacheRef.current = {
2685
3103
  nodes: layouted.nodes,
2686
3104
  edges: layouted.edges,
2687
- controllerPositions: layouted.controllerPositions
3105
+ controllerPositions: layouted.controllerPositions,
3106
+ graphspec
2688
3107
  };
2689
3108
  const withControllers = applyControllers(
2690
3109
  cloneCachedNodes(layouted.nodes),
@@ -2697,12 +3116,7 @@ function GraphViewer(props) {
2697
3116
  layouted.controllerPositions,
2698
3117
  toggleFoldRef.current
2699
3118
  );
2700
- setNodes(
2701
- applyStatusOverrides(
2702
- toAppNodes(hydrateLabels(withControllers.nodes)),
2703
- statusMapRef.current
2704
- )
2705
- );
3119
+ setNodes(decorateNodes(withControllers.nodes));
2706
3120
  setEdges(toAppEdges(withControllers.edges));
2707
3121
  setTimeout(() => {
2708
3122
  if (!cancelled && reactFlowRef.current) {
@@ -2724,7 +3138,7 @@ function GraphViewer(props) {
2724
3138
  cancelled = true;
2725
3139
  };
2726
3140
  }, [graphspec, edgeType]);
2727
- React9.useEffect(() => {
3141
+ React10.useEffect(() => {
2728
3142
  if (isFirstFoldEffect.current) {
2729
3143
  isFirstFoldEffect.current = false;
2730
3144
  prevFoldSizeRef.current = foldedControllers.size;
@@ -2771,7 +3185,8 @@ function GraphViewer(props) {
2771
3185
  layoutCacheRef.current = {
2772
3186
  nodes: layouted.nodes,
2773
3187
  edges: layouted.edges,
2774
- controllerPositions: layouted.controllerPositions
3188
+ controllerPositions: layouted.controllerPositions,
3189
+ graphspec: currentGraphspec
2775
3190
  };
2776
3191
  const withControllers = applyControllers(
2777
3192
  cloneCachedNodes(layouted.nodes),
@@ -2784,12 +3199,7 @@ function GraphViewer(props) {
2784
3199
  layouted.controllerPositions,
2785
3200
  toggleFoldRef.current
2786
3201
  );
2787
- setNodes(
2788
- applyStatusOverrides(
2789
- toAppNodes(hydrateLabels(withControllers.nodes)),
2790
- statusMapRef.current
2791
- )
2792
- );
3202
+ setNodes(decorateNodes(withControllers.nodes));
2793
3203
  setEdges(toAppEdges(withControllers.edges));
2794
3204
  setTimeout(() => {
2795
3205
  if (!cancelled && reactFlowRef.current) {
@@ -2804,8 +3214,9 @@ function GraphViewer(props) {
2804
3214
  cancelled = true;
2805
3215
  };
2806
3216
  }, [foldedControllers, toggleFold]);
2807
- React9.useEffect(() => {
3217
+ React10.useEffect(() => {
2808
3218
  if (!layoutCacheRef.current || !initialDataRef.current) return;
3219
+ if (layoutCacheRef.current.graphspec !== initialDataRef.current._graphspec) return;
2809
3220
  const cachedNodes = cloneCachedNodes(layoutCacheRef.current.nodes);
2810
3221
  const cachedEdges = layoutCacheRef.current.edges;
2811
3222
  const withControllers = applyControllers(
@@ -2819,10 +3230,27 @@ function GraphViewer(props) {
2819
3230
  layoutCacheRef.current.controllerPositions,
2820
3231
  toggleFoldRef.current
2821
3232
  );
2822
- setNodes(applyStatusOverrides(toAppNodes(hydrateLabels(withControllers.nodes)), statusMap));
3233
+ setNodes(decorateNodes(withControllers.nodes));
2823
3234
  setEdges(toAppEdges(withControllers.edges));
2824
- }, [statusMap]);
2825
- const onNodeClick = React9.useCallback(
3235
+ }, [statusMap, decorateNodes]);
3236
+ React10.useEffect(() => {
3237
+ if (!layoutCacheRef.current || !initialDataRef.current) return;
3238
+ if (layoutCacheRef.current.graphspec !== initialDataRef.current._graphspec) return;
3239
+ const cachedNodes = cloneCachedNodes(layoutCacheRef.current.nodes);
3240
+ const withControllers = applyControllers(
3241
+ cachedNodes,
3242
+ layoutCacheRef.current.edges,
3243
+ initialDataRef.current._graphspec,
3244
+ initialDataRef.current._analysis,
3245
+ showControllersRef.current,
3246
+ expandedRef.current,
3247
+ toggleCollapseRef.current,
3248
+ layoutCacheRef.current.controllerPositions,
3249
+ toggleFoldRef.current
3250
+ );
3251
+ setNodes(decorateNodes(withControllers.nodes));
3252
+ }, [validationIssues, validationWidgetAvailable, decorateNodes]);
3253
+ const onNodeClick = React10.useCallback(
2826
3254
  (event, node) => {
2827
3255
  var _a2;
2828
3256
  const nodeData = node.data;
@@ -2864,7 +3292,7 @@ function GraphViewer(props) {
2864
3292
  conceptOverride
2865
3293
  ]
2866
3294
  );
2867
- const onInit = React9.useCallback(
3295
+ const onInit = React10.useCallback(
2868
3296
  (reactFlowInstance) => {
2869
3297
  reactFlowRef.current = reactFlowInstance;
2870
3298
  if (onReactFlowInit) {
@@ -2873,12 +3301,12 @@ function GraphViewer(props) {
2873
3301
  },
2874
3302
  [onReactFlowInit]
2875
3303
  );
2876
- const handlePaneClick = React9.useCallback(() => {
3304
+ const handlePaneClick = React10.useCallback(() => {
2877
3305
  setDetailSelection(null);
2878
3306
  setConceptOverride(null);
2879
3307
  onPaneClick == null ? void 0 : onPaneClick();
2880
3308
  }, [onPaneClick]);
2881
- const handleConceptClick = React9.useCallback(
3309
+ const handleConceptClick = React10.useCallback(
2882
3310
  (conceptCode) => {
2883
3311
  if (!graphspec) return;
2884
3312
  const info = resolveConceptRef(graphspec, conceptCode);
@@ -2894,7 +3322,7 @@ function GraphViewer(props) {
2894
3322
  );
2895
3323
  const rawAnalysis = (_c = rawGraphDataRef.current) == null ? void 0 : _c.analysis;
2896
3324
  const allControllerIds = rawAnalysis == null ? void 0 : rawAnalysis.controllerNodeIds;
2897
- const foldAllProps = React9.useMemo(() => {
3325
+ const foldAllProps = React10.useMemo(() => {
2898
3326
  if (!showControllers || !allControllerIds || allControllerIds.size === 0) {
2899
3327
  return {
2900
3328
  onFoldAll: void 0,
@@ -2910,13 +3338,13 @@ function GraphViewer(props) {
2910
3338
  expandAllDisabled: foldedControllers.size === 0
2911
3339
  };
2912
3340
  }, [showControllers, allControllerIds, foldedControllers]);
2913
- return /* @__PURE__ */ jsxs21(
3341
+ return /* @__PURE__ */ jsxs22(
2914
3342
  "div",
2915
3343
  {
2916
3344
  ref: containerRef,
2917
3345
  className: `react-flow-container react-flow-container--theme-${resolvedTheme} react-flow-container--mode-${mode}`,
2918
3346
  children: [
2919
- /* @__PURE__ */ jsxs21(
3347
+ /* @__PURE__ */ jsxs22(
2920
3348
  ReactFlow,
2921
3349
  {
2922
3350
  nodes,
@@ -2935,7 +3363,7 @@ function GraphViewer(props) {
2935
3363
  proOptions: { hideAttribution: true },
2936
3364
  panActivationKeyCode: null,
2937
3365
  children: [
2938
- /* @__PURE__ */ jsx21(
3366
+ /* @__PURE__ */ jsx23(
2939
3367
  Background,
2940
3368
  {
2941
3369
  variant: BackgroundVariant.Dots,
@@ -2944,7 +3372,7 @@ function GraphViewer(props) {
2944
3372
  color: "var(--color-bg-dots)"
2945
3373
  }
2946
3374
  ),
2947
- !hideToolbar && /* @__PURE__ */ jsx21(
3375
+ !hideToolbar && /* @__PURE__ */ jsx23(
2948
3376
  GraphToolbar,
2949
3377
  {
2950
3378
  direction,
@@ -2970,13 +3398,18 @@ function GraphViewer(props) {
2970
3398
  themeMode: showThemeToggle ? mode : void 0,
2971
3399
  onThemeModeChange: showThemeToggle ? setMode : void 0,
2972
3400
  rightOffset: detailOpen ? panelWidth : 0,
2973
- position: effectiveToolbarPosition
3401
+ position: effectiveToolbarPosition,
3402
+ validationState,
3403
+ validationIssues,
3404
+ onValidationIssueClick: onValidationIssueClick ? handleValidationIssueClick : void 0,
3405
+ validationOpen,
3406
+ onValidationOpenChange: setValidationOpen
2974
3407
  }
2975
3408
  )
2976
3409
  ]
2977
3410
  }
2978
3411
  ),
2979
- /* @__PURE__ */ jsxs21(
3412
+ /* @__PURE__ */ jsxs22(
2980
3413
  DetailPanel,
2981
3414
  {
2982
3415
  isOpen: detailOpen,
@@ -2985,14 +3418,14 @@ function GraphViewer(props) {
2985
3418
  isDragging: isPanelDragging,
2986
3419
  onResizeHandleMouseDown: onResizeMouseDown,
2987
3420
  children: [
2988
- conceptOverride ? /* @__PURE__ */ jsx21(ConceptDetailPanel, { concept: conceptOverride }) : selectedSpecNode && graphspec ? /* @__PURE__ */ jsx21(
3421
+ conceptOverride ? /* @__PURE__ */ jsx23(ConceptDetailPanel, { concept: conceptOverride }) : selectedSpecNode && graphspec ? /* @__PURE__ */ jsx23(
2989
3422
  PipeDetailPanel,
2990
3423
  {
2991
3424
  node: selectedSpecNode,
2992
3425
  spec: graphspec,
2993
3426
  onConceptClick: handleConceptClick
2994
3427
  }
2995
- ) : (detailSelection == null ? void 0 : detailSelection.stuffData) ? /* @__PURE__ */ jsx21(
3428
+ ) : (detailSelection == null ? void 0 : detailSelection.stuffData) ? /* @__PURE__ */ jsx23(
2996
3429
  StuffNodeDetail,
2997
3430
  {
2998
3431
  nodeId: detailSelection.nodeId,
@@ -3020,6 +3453,7 @@ export {
3020
3453
  PipeCardNode,
3021
3454
  PipeDetailPanel,
3022
3455
  StuffViewer,
3456
+ ValidationPanel,
3023
3457
  applyStatusOverrides,
3024
3458
  controllerNodeTypes,
3025
3459
  detectSystemTheme,
@@ -3037,6 +3471,8 @@ export {
3037
3471
  toAppEdges,
3038
3472
  toAppNodes,
3039
3473
  useResizable,
3040
- useSystemTheme
3474
+ useSystemTheme,
3475
+ validationLabel,
3476
+ validationPanelPlacement
3041
3477
  };
3042
3478
  //# sourceMappingURL=index.js.map