footprint-explainable-ui 0.5.0 → 0.7.2

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.
@@ -26,24 +26,26 @@ __export(flowchart_exports, {
26
26
  SubflowTree: () => SubflowTree,
27
27
  TimeTravelDebugger: () => TimeTravelDebugger,
28
28
  TracedFlowchartView: () => TracedFlowchartView,
29
+ applyOverlay: () => applyOverlay,
30
+ specToLayout: () => specToLayout,
29
31
  specToReactFlow: () => specToReactFlow,
30
32
  useSubflowNavigation: () => useSubflowNavigation
31
33
  });
32
34
  module.exports = __toCommonJS(flowchart_exports);
33
35
 
34
36
  // src/components/FlowchartView/FlowchartView.tsx
35
- var import_react4 = require("react");
36
- var import_react5 = require("@xyflow/react");
37
+ var import_react5 = require("react");
38
+ var import_react6 = require("@xyflow/react");
37
39
 
38
40
  // src/components/StageNode/StageNode.tsx
39
- var import_react2 = require("react");
40
- var import_react3 = require("@xyflow/react");
41
+ var import_react3 = require("react");
42
+ var import_react4 = require("@xyflow/react");
41
43
 
42
44
  // src/theme/ThemeProvider.tsx
43
45
  var import_react = require("react");
44
46
 
45
47
  // src/theme/tokens.ts
46
- var defaultTokens = {
48
+ var rawDefaults = {
47
49
  colors: {
48
50
  primary: "#6366f1",
49
51
  success: "#22c55e",
@@ -63,6 +65,26 @@ var defaultTokens = {
63
65
  mono: "'JetBrains Mono', 'Fira Code', monospace"
64
66
  }
65
67
  };
68
+ var defaultTokens = {
69
+ colors: {
70
+ primary: `var(--fp-color-primary, ${rawDefaults.colors.primary})`,
71
+ success: `var(--fp-color-success, ${rawDefaults.colors.success})`,
72
+ error: `var(--fp-color-error, ${rawDefaults.colors.error})`,
73
+ warning: `var(--fp-color-warning, ${rawDefaults.colors.warning})`,
74
+ bgPrimary: `var(--fp-bg-primary, ${rawDefaults.colors.bgPrimary})`,
75
+ bgSecondary: `var(--fp-bg-secondary, ${rawDefaults.colors.bgSecondary})`,
76
+ bgTertiary: `var(--fp-bg-tertiary, ${rawDefaults.colors.bgTertiary})`,
77
+ textPrimary: `var(--fp-text-primary, ${rawDefaults.colors.textPrimary})`,
78
+ textSecondary: `var(--fp-text-secondary, ${rawDefaults.colors.textSecondary})`,
79
+ textMuted: `var(--fp-text-muted, ${rawDefaults.colors.textMuted})`,
80
+ border: `var(--fp-border, ${rawDefaults.colors.border})`
81
+ },
82
+ radius: `var(--fp-radius, ${rawDefaults.radius})`,
83
+ fontFamily: {
84
+ sans: `var(--fp-font-sans, ${rawDefaults.fontFamily.sans})`,
85
+ mono: `var(--fp-font-mono, ${rawDefaults.fontFamily.mono})`
86
+ }
87
+ };
66
88
 
67
89
  // src/theme/ThemeProvider.tsx
68
90
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -99,19 +121,181 @@ var padding = {
99
121
  detailed: 16
100
122
  };
101
123
 
124
+ // src/theme/useDarkModeTokens.ts
125
+ var import_react2 = require("react");
126
+
102
127
  // src/components/StageNode/StageNode.tsx
103
128
  var import_jsx_runtime2 = require("react/jsx-runtime");
104
- var StageNode = (0, import_react2.memo)(function StageNode2({
129
+ var KEYFRAMES_ID = "fp-stage-node-keyframes";
130
+ var KEYFRAMES_CSS = `
131
+ @media (prefers-reduced-motion: no-preference) {
132
+ @keyframes fp-pulse {
133
+ 0%, 100% { opacity: 0.4; transform: scale(1); }
134
+ 50% { opacity: 0.15; transform: scale(1.06); }
135
+ }
136
+ @keyframes fp-blink {
137
+ 0%, 100% { opacity: 1; }
138
+ 50% { opacity: 0.3; }
139
+ }
140
+ }
141
+ @media (prefers-reduced-motion: reduce) {
142
+ @keyframes fp-pulse { 0%, 100% { opacity: 0.3; } }
143
+ @keyframes fp-blink { 0%, 100% { opacity: 1; } }
144
+ }
145
+ `;
146
+ var ICON_SIZE = 16;
147
+ function StageIcon({ type, color }) {
148
+ const s = ICON_SIZE;
149
+ const props = { width: s, height: s, viewBox: `0 0 ${s} ${s}`, fill: "none", style: { flexShrink: 0 } };
150
+ switch (type) {
151
+ // LLM / AI call — brain/sparkle
152
+ case "llm":
153
+ case "ai":
154
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "8", r: "6", stroke: color, strokeWidth: "1.5" }),
156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M5.5 8C5.5 6.5 6.5 5 8 5S10.5 6.5 10.5 8", stroke: color, strokeWidth: "1.2", strokeLinecap: "round" }),
157
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "9.5", r: "1", fill: color }),
158
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "3.5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
159
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12.5", y1: "4", x2: "11.2", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
160
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3.5", y1: "4", x2: "4.8", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
161
+ ] });
162
+ // Tool / function call — gear
163
+ case "tool":
164
+ case "function":
165
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "8", r: "3", stroke: color, strokeWidth: "1.5" }),
167
+ [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
168
+ const rad = angle * Math.PI / 180;
169
+ const x1 = 8 + Math.cos(rad) * 4.5;
170
+ const y1 = 8 + Math.sin(rad) * 4.5;
171
+ const x2 = 8 + Math.cos(rad) * 6;
172
+ const y2 = 8 + Math.sin(rad) * 6;
173
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1, y1, x2, y2, stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }, angle);
174
+ })
175
+ ] });
176
+ // RAG / retrieval — magnifying glass + doc
177
+ case "rag":
178
+ case "search":
179
+ case "retrieval":
180
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "7", cy: "7", r: "4", stroke: color, strokeWidth: "1.5" }),
182
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "10", x2: "13.5", y2: "13.5", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "5.5", y1: "6", x2: "8.5", y2: "6", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
184
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "5.5", y1: "8", x2: "7.5", y2: "8", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
185
+ ] });
186
+ // Parse / process — diamond with arrows
187
+ case "parse":
188
+ case "process":
189
+ case "transform":
190
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5", stroke: color, strokeWidth: "1.5", transform: "rotate(45 8 8)" }) });
191
+ // Start / seed — play triangle
192
+ case "start":
193
+ case "seed":
194
+ case "init":
195
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M5 3.5L12.5 8L5 12.5V3.5Z", fill: color, opacity: "0.8" }) });
196
+ // End / finalize — stop square
197
+ case "end":
198
+ case "finalize":
199
+ case "output":
200
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5", fill: color, opacity: "0.8" }) });
201
+ // Agent — person silhouette
202
+ case "agent":
203
+ case "orchestrator":
204
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "5", r: "2.5", stroke: color, strokeWidth: "1.5" }),
206
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3.5 14C3.5 11 5.5 9 8 9S12.5 11 12.5 14", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" })
207
+ ] });
208
+ // Swarm — multi-agent
209
+ case "swarm":
210
+ case "multi-agent":
211
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
212
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "5", r: "2", stroke: color, strokeWidth: "1.2" }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11", cy: "5", r: "2", stroke: color, strokeWidth: "1.2" }),
214
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "11", r: "2", stroke: color, strokeWidth: "1.2" }),
215
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "5", y1: "7", x2: "8", y2: "9", stroke: color, strokeWidth: "1", opacity: "0.5" }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "11", y1: "7", x2: "8", y2: "9", stroke: color, strokeWidth: "1", opacity: "0.5" })
217
+ ] });
218
+ // Guard / guardrail — shield
219
+ case "guard":
220
+ case "guardrail":
221
+ case "validate":
222
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
223
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2L3 5V9C3 11.5 5 13.5 8 14.5C11 13.5 13 11.5 13 9V5L8 2Z", stroke: color, strokeWidth: "1.5", strokeLinejoin: "round" }),
224
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M6 8L7.5 9.5L10 6.5", stroke: color, strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
225
+ ] });
226
+ // Stream — wave
227
+ case "stream":
228
+ case "streaming":
229
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
230
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 8C4 5 6 11 8 8S12 5 14 8", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", fill: "none" }),
231
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 11C4 8 6 14 8 11S12 8 14 11", stroke: color, strokeWidth: "1", strokeLinecap: "round", fill: "none", opacity: "0.5" })
232
+ ] });
233
+ // Memory / state — database cylinder
234
+ case "memory":
235
+ case "state":
236
+ case "db":
237
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
238
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ellipse", { cx: "8", cy: "4.5", rx: "5", ry: "2", stroke: color, strokeWidth: "1.3" }),
239
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "4.5", x2: "3", y2: "11.5", stroke: color, strokeWidth: "1.3" }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "13", y1: "4.5", x2: "13", y2: "11.5", stroke: color, strokeWidth: "1.3" }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ellipse", { cx: "8", cy: "11.5", rx: "5", ry: "2", stroke: color, strokeWidth: "1.3" })
242
+ ] });
243
+ // Loop — circular arrow
244
+ case "loop":
245
+ case "retry":
246
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
247
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 8A4 4 0 1 1 8 4", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", fill: "none" }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 1.5L10.5 4L8 6.5", stroke: color, strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" })
249
+ ] });
250
+ // Lazy / service — cloud (deferred resolution, loaded on demand)
251
+ case "lazy":
252
+ case "service":
253
+ case "cloud":
254
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
255
+ "path",
256
+ {
257
+ d: "M4.5 12C2.8 12 1.5 10.7 1.5 9C1.5 7.5 2.5 6.3 3.8 6C4 4 5.8 2.5 8 2.5C9.8 2.5 11.3 3.5 11.9 5C13.9 5.2 15.5 6.8 15.5 8.8C15.5 10.8 13.9 12.5 11.8 12.5H4.5",
258
+ stroke: color,
259
+ strokeWidth: "1.3",
260
+ strokeLinecap: "round",
261
+ fill: "none"
262
+ }
263
+ ) });
264
+ // Decision — diamond (already handled by isDecider shape)
265
+ case "decision":
266
+ case "router":
267
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
268
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2L14 8L8 14L2 8Z", stroke: color, strokeWidth: "1.5", fill: "none" }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "8", r: "1.5", fill: color })
270
+ ] });
271
+ default:
272
+ return null;
273
+ }
274
+ }
275
+ var StageNode = (0, import_react3.memo)(function StageNode2({
105
276
  data
106
277
  }) {
107
- const { label, active, done, error, linked, stepNumbers, dimmed, isSubflow, description } = data;
278
+ const { label, active, done, error, linked, icon, stepNumbers, dimmed, isSubflow, isLazy, isDecider, isFork, description } = data;
279
+ const effectiveIcon = icon || (isLazy ? "lazy" : void 0);
280
+ const isLazyUnresolved = isLazy && !done && !active;
281
+ const injectedRef = (0, import_react3.useRef)(false);
282
+ (0, import_react3.useEffect)(() => {
283
+ if (injectedRef.current) return;
284
+ if (typeof document !== "undefined" && !document.getElementById(KEYFRAMES_ID)) {
285
+ const styleEl = document.createElement("style");
286
+ styleEl.id = KEYFRAMES_ID;
287
+ styleEl.textContent = KEYFRAMES_CSS;
288
+ document.head.appendChild(styleEl);
289
+ }
290
+ injectedRef.current = true;
291
+ }, []);
108
292
  const isOnPath = active || done;
109
293
  const bg = active ? theme.primary : done ? theme.success : error ? theme.error : theme.bgSecondary;
110
294
  const borderColor = active ? theme.primary : done ? theme.success : error ? theme.error : theme.border;
111
295
  const shadow = active ? `0 0 16px color-mix(in srgb, ${theme.primary} 40%, transparent)` : done ? `0 0 8px color-mix(in srgb, ${theme.success} 20%, transparent)` : error ? `0 0 12px color-mix(in srgb, ${theme.error} 30%, transparent)` : `0 2px 8px rgba(0,0,0,0.15)`;
112
296
  const textColor = active || done || error ? "#fff" : theme.textPrimary;
113
297
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
114
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Handle, { type: "target", position: import_react3.Position.Top, style: { opacity: 0 } }),
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Top, style: { opacity: 0 } }),
115
299
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
116
300
  "div",
117
301
  {
@@ -166,7 +350,8 @@ var StageNode = (0, import_react2.memo)(function StageNode2({
166
350
  style: {
167
351
  position: "absolute",
168
352
  inset: -6,
169
- borderRadius: `calc(${theme.radius} + 4px)`,
353
+ borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
354
+ transform: isDecider ? "rotate(45deg)" : void 0,
170
355
  border: `2px solid ${theme.primary}`,
171
356
  opacity: 0.4,
172
357
  animation: "fp-pulse 2s ease-in-out infinite"
@@ -179,127 +364,195 @@ var StageNode = (0, import_react2.memo)(function StageNode2({
179
364
  style: {
180
365
  position: "absolute",
181
366
  inset: -6,
182
- borderRadius: `calc(${theme.radius} + 4px)`,
367
+ borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
368
+ transform: isDecider ? "rotate(45deg)" : void 0,
183
369
  border: `2px solid ${theme.primary}`,
184
370
  opacity: 0.3,
185
371
  animation: "fp-pulse 1.5s ease-out infinite"
186
372
  }
187
373
  }
188
374
  ),
189
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
375
+ isDecider ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
190
376
  "div",
191
377
  {
192
378
  style: {
193
379
  background: bg,
194
- border: `2px solid ${borderColor}`,
195
- borderRadius: theme.radius,
196
- padding: description ? "8px 16px" : "10px 20px",
197
- display: "flex",
198
- flexDirection: "column",
199
- alignItems: "center",
200
- gap: description ? 2 : 0,
380
+ border: `2px ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
381
+ borderRadius: 4,
382
+ transform: "rotate(45deg)",
383
+ padding: 20,
201
384
  boxShadow: shadow,
202
385
  transition: "all 0.3s ease",
203
- fontFamily: theme.fontSans,
204
- minWidth: 100,
386
+ display: "flex",
387
+ alignItems: "center",
205
388
  justifyContent: "center"
206
389
  },
207
- children: [
208
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
209
- done && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: textColor }, children: "\u2713" }),
210
- active && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
211
- "span",
212
- {
213
- style: {
214
- width: 8,
215
- height: 8,
216
- borderRadius: "50%",
217
- background: "#fff",
218
- animation: "fp-blink 1s ease-in-out infinite",
219
- flexShrink: 0
390
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
391
+ "div",
392
+ {
393
+ style: {
394
+ transform: "rotate(-45deg)",
395
+ display: "flex",
396
+ flexDirection: "column",
397
+ alignItems: "center",
398
+ gap: 2,
399
+ fontFamily: theme.fontSans
400
+ },
401
+ children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [
403
+ icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StageIcon, { type: icon, color: textColor }),
404
+ !icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: textColor }, children: "\u25C7" }),
405
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
406
+ "span",
407
+ {
408
+ style: {
409
+ fontSize: 12,
410
+ fontWeight: 600,
411
+ color: textColor,
412
+ whiteSpace: "nowrap"
413
+ },
414
+ children: label
415
+ }
416
+ )
417
+ ] }),
418
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
419
+ "span",
420
+ {
421
+ style: {
422
+ fontSize: 9,
423
+ fontWeight: 400,
424
+ color: textColor,
425
+ opacity: 0.7,
426
+ whiteSpace: "nowrap",
427
+ overflow: "hidden",
428
+ textOverflow: "ellipsis",
429
+ maxWidth: 130
430
+ },
431
+ children: description
220
432
  }
221
- }
222
- ),
223
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: textColor }, children: "\u2717" }),
224
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
+ )
434
+ ]
435
+ }
436
+ )
437
+ }
438
+ ) : (
439
+ /* Standard rectangular node */
440
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
441
+ "div",
442
+ {
443
+ style: {
444
+ background: bg,
445
+ border: `2px ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
446
+ borderRadius: theme.radius,
447
+ padding: description ? "8px 16px" : "10px 20px",
448
+ display: "flex",
449
+ flexDirection: "column",
450
+ alignItems: "center",
451
+ gap: description ? 2 : 0,
452
+ boxShadow: shadow,
453
+ transition: "all 0.3s ease",
454
+ fontFamily: theme.fontSans,
455
+ minWidth: 100,
456
+ justifyContent: "center"
457
+ },
458
+ children: [
459
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
460
+ effectiveIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StageIcon, { type: effectiveIcon, color: textColor }),
461
+ done && !effectiveIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: textColor }, children: "\u2713" }),
462
+ active && !effectiveIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
463
+ "span",
464
+ {
465
+ style: {
466
+ width: 8,
467
+ height: 8,
468
+ borderRadius: "50%",
469
+ background: "#fff",
470
+ animation: "fp-blink 1s ease-in-out infinite",
471
+ flexShrink: 0
472
+ }
473
+ }
474
+ ),
475
+ error && !effectiveIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: textColor }, children: "\u2717" }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
477
+ "span",
478
+ {
479
+ style: {
480
+ fontSize: 13,
481
+ fontWeight: 500,
482
+ color: textColor,
483
+ whiteSpace: "nowrap"
484
+ },
485
+ children: label
486
+ }
487
+ ),
488
+ isSubflow && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
489
+ "span",
490
+ {
491
+ style: {
492
+ display: "inline-flex",
493
+ alignItems: "center",
494
+ justifyContent: "center",
495
+ width: 16,
496
+ height: 16,
497
+ borderRadius: 3,
498
+ border: `1.5px solid ${textColor}`,
499
+ position: "relative",
500
+ opacity: 0.7,
501
+ flexShrink: 0
502
+ },
503
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
504
+ "span",
505
+ {
506
+ style: {
507
+ width: 8,
508
+ height: 8,
509
+ borderRadius: 2,
510
+ border: `1px solid ${textColor}`
511
+ }
512
+ }
513
+ )
514
+ }
515
+ )
516
+ ] }),
517
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
225
518
  "span",
226
519
  {
227
520
  style: {
228
- fontSize: 13,
229
- fontWeight: 500,
521
+ fontSize: 10,
522
+ fontWeight: 400,
230
523
  color: textColor,
231
- whiteSpace: "nowrap"
232
- },
233
- children: label
234
- }
235
- ),
236
- isSubflow && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
237
- "span",
238
- {
239
- style: {
240
- display: "inline-flex",
241
- alignItems: "center",
242
- justifyContent: "center",
243
- width: 16,
244
- height: 16,
245
- borderRadius: 3,
246
- border: `1.5px solid ${textColor}`,
247
- position: "relative",
248
524
  opacity: 0.7,
249
- flexShrink: 0
525
+ whiteSpace: "nowrap",
526
+ overflow: "hidden",
527
+ textOverflow: "ellipsis",
528
+ maxWidth: 160
250
529
  },
251
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
252
- "span",
253
- {
254
- style: {
255
- width: 8,
256
- height: 8,
257
- borderRadius: 2,
258
- border: `1px solid ${textColor}`
259
- }
260
- }
261
- )
530
+ children: description
262
531
  }
263
532
  )
264
- ] }),
265
- description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
266
- "span",
267
- {
268
- style: {
269
- fontSize: 10,
270
- fontWeight: 400,
271
- color: textColor,
272
- opacity: 0.7,
273
- whiteSpace: "nowrap",
274
- overflow: "hidden",
275
- textOverflow: "ellipsis",
276
- maxWidth: 160
277
- },
278
- children: description
279
- }
280
- )
281
- ]
282
- }
533
+ ]
534
+ }
535
+ )
283
536
  )
284
537
  ]
285
538
  }
286
539
  ),
287
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Handle, { type: "source", position: import_react3.Position.Bottom, style: { opacity: 0 } }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Handle, { type: "source", position: import_react4.Position.Bottom, style: { opacity: 0 } }),
288
541
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
289
- import_react3.Handle,
542
+ import_react4.Handle,
290
543
  {
291
544
  id: "loop-source",
292
545
  type: "source",
293
- position: import_react3.Position.Right,
546
+ position: import_react4.Position.Right,
294
547
  style: { background: "transparent", border: "none", width: 6, height: 6 }
295
548
  }
296
549
  ),
297
550
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
298
- import_react3.Handle,
551
+ import_react4.Handle,
299
552
  {
300
553
  id: "loop-target",
301
554
  type: "target",
302
- position: import_react3.Position.Right,
555
+ position: import_react4.Position.Right,
303
556
  style: { background: "transparent", border: "none", width: 6, height: 6 }
304
557
  }
305
558
  )
@@ -319,7 +572,7 @@ function FlowchartView({
319
572
  className,
320
573
  style
321
574
  }) {
322
- const enhancedNodes = (0, import_react4.useMemo)(() => {
575
+ const enhancedNodes = (0, import_react5.useMemo)(() => {
323
576
  if (!snapshots || snapshots.length === 0) {
324
577
  return rawNodes.map((n) => ({
325
578
  ...n,
@@ -349,7 +602,7 @@ function FlowchartView({
349
602
  }
350
603
  }));
351
604
  }, [rawNodes, snapshots, selectedIndex]);
352
- const enhancedEdges = (0, import_react4.useMemo)(() => {
605
+ const enhancedEdges = (0, import_react5.useMemo)(() => {
353
606
  if (!snapshots || snapshots.length === 0) {
354
607
  return rawEdges.map((e) => ({
355
608
  ...e,
@@ -374,9 +627,9 @@ function FlowchartView({
374
627
  };
375
628
  });
376
629
  }, [rawEdges, snapshots, selectedIndex]);
377
- const [nodes, , onNodesChange] = (0, import_react5.useNodesState)(enhancedNodes);
378
- const [edges, , onEdgesChange] = (0, import_react5.useEdgesState)(enhancedEdges);
379
- const handleNodeClick = (0, import_react4.useCallback)(
630
+ const [nodes, , onNodesChange] = (0, import_react6.useNodesState)(enhancedNodes);
631
+ const [edges, , onEdgesChange] = (0, import_react6.useEdgesState)(enhancedEdges);
632
+ const handleNodeClick = (0, import_react5.useCallback)(
380
633
  (_, node) => {
381
634
  if (!onNodeClick || !snapshots) return;
382
635
  const idx = snapshots.findIndex((s) => s.stageName === node.id);
@@ -395,7 +648,7 @@ function FlowchartView({
395
648
  },
396
649
  "data-fp": "flowchart-view",
397
650
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
398
- import_react5.ReactFlow,
651
+ import_react6.ReactFlow,
399
652
  {
400
653
  nodes,
401
654
  edges,
@@ -412,7 +665,7 @@ function FlowchartView({
412
665
  nodesDraggable: false,
413
666
  nodesConnectable: false,
414
667
  elementsSelectable: !!onNodeClick,
415
- children: !unstyled && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Background, { variant: import_react5.BackgroundVariant.Dots, gap: 16, size: 1 })
668
+ children: !unstyled && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Background, { variant: import_react6.BackgroundVariant.Dots, gap: 16, size: 1 })
416
669
  }
417
670
  )
418
671
  }
@@ -420,19 +673,19 @@ function FlowchartView({
420
673
  }
421
674
 
422
675
  // src/components/FlowchartView/TracedFlowchartView.tsx
423
- var import_react9 = require("react");
424
- var import_react10 = require("@xyflow/react");
676
+ var import_react7 = require("react");
677
+ var import_react8 = require("@xyflow/react");
425
678
 
426
679
  // src/components/FlowchartView/specToReactFlow.ts
427
680
  var DEFAULT_COLORS = {
428
- edgeDefault: defaultTokens.colors.textMuted,
429
- edgeExecuted: defaultTokens.colors.success,
430
- edgeActive: defaultTokens.colors.primary,
431
- edgeLoop: defaultTokens.colors.warning,
432
- labelDefault: defaultTokens.colors.textSecondary,
433
- labelExecuted: defaultTokens.colors.success,
434
- labelLoop: defaultTokens.colors.warning,
435
- pathGlow: `${defaultTokens.colors.success}4D`
681
+ edgeDefault: rawDefaults.colors.textMuted,
682
+ edgeExecuted: rawDefaults.colors.success,
683
+ edgeActive: rawDefaults.colors.primary,
684
+ edgeLoop: rawDefaults.colors.warning,
685
+ labelDefault: rawDefaults.colors.textSecondary,
686
+ labelExecuted: rawDefaults.colors.success,
687
+ labelLoop: rawDefaults.colors.warning,
688
+ pathGlow: `${rawDefaults.colors.success}4D`
436
689
  // ~30% opacity hex
437
690
  };
438
691
  var Y_STEP = 100;
@@ -440,122 +693,33 @@ var X_SPREAD = 200;
440
693
  function nid(n) {
441
694
  return n.name || n.id || `spec-${Math.random()}`;
442
695
  }
443
- function addEdge(state, source, target, label, isLoop) {
444
- state.edgeCounter++;
445
- const o = state.overlay;
446
- const c = state.colors;
447
- const executed = o && o.executedStages.has(source) && o.executedStages.has(target);
448
- const isLeadingEdge = o && source === o.activeStage && !o.doneStages.has(target);
449
- if (isLoop) {
450
- let loopExecuted = false;
451
- if (o?.executionOrder) {
452
- const lastSourceIdx = o.executionOrder.lastIndexOf(source);
453
- if (lastSourceIdx >= 0) {
454
- loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(target);
455
- }
456
- }
457
- state.edges.push({
458
- id: `se${state.edgeCounter}`,
459
- source,
460
- target,
461
- sourceHandle: "loop-source",
462
- targetHandle: "loop-target",
463
- label: label ?? "loop",
464
- type: "smoothstep",
465
- pathOptions: { offset: 40, borderRadius: 16 },
466
- style: {
467
- stroke: c.edgeLoop,
468
- strokeWidth: loopExecuted ? 3 : 2,
469
- strokeDasharray: "6 3",
470
- opacity: o && !loopExecuted ? 0.35 : 1
471
- },
472
- labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
473
- animated: loopExecuted,
474
- zIndex: 2
475
- });
476
- return;
477
- }
478
- if (executed) {
479
- state.edges.push({
480
- id: `se${state.edgeCounter}-glow`,
481
- source,
482
- target,
483
- style: {
484
- stroke: c.pathGlow,
485
- strokeWidth: 8,
486
- opacity: 0.4
487
- },
488
- zIndex: 0,
489
- selectable: false,
490
- focusable: false
491
- });
492
- state.edges.push({
493
- id: `se${state.edgeCounter}`,
494
- source,
495
- target,
496
- label,
497
- style: {
498
- stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
499
- strokeWidth: 3.5
500
- },
501
- labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
502
- animated: !!isLeadingEdge,
503
- zIndex: 1
504
- });
505
- } else {
506
- state.edges.push({
507
- id: `se${state.edgeCounter}`,
508
- source,
509
- target,
510
- label,
511
- style: {
512
- stroke: c.edgeDefault,
513
- strokeWidth: 1.5,
514
- opacity: o ? 0.3 : 1
515
- },
516
- labelStyle: { fontSize: 10, fill: c.labelDefault }
517
- });
696
+ function registerNode(state, node) {
697
+ if (node.id && node.name) {
698
+ state.idToName.set(node.id, node.name);
518
699
  }
519
700
  }
520
- function walk(node, state, x, y) {
701
+ function walkLayout(node, state, x, y) {
702
+ if (!node) return { lastIds: [], bottomY: y };
703
+ registerNode(state, node);
521
704
  const id = nid(node);
522
705
  if (state.seen.has(id)) {
523
706
  return { lastIds: [id], bottomY: y };
524
707
  }
525
708
  state.seen.add(id);
526
- const isDecider = node.type === "decider" || node.hasDecider;
709
+ const isDecider = node.type === "decider" || !!node.hasDecider;
527
710
  const isFork = node.type === "fork";
528
- const o = state.overlay;
529
- const isDone = o ? o.doneStages.has(id) : false;
530
- const isActive = o ? o.activeStage === id : false;
531
- const wasExecuted = o ? o.executedStages.has(id) : false;
532
- const dimmed = o && !wasExecuted;
533
- let stepNumbers;
534
- if (o?.executionOrder) {
535
- const nums = [];
536
- for (let i = 0; i < o.executionOrder.length; i++) {
537
- if (o.executionOrder[i] === id) nums.push(i + 1);
538
- }
539
- if (nums.length > 0) stepNumbers = nums;
540
- }
541
711
  state.nodes.push({
542
712
  id,
543
- position: { x, y },
544
- data: {
545
- label: node.name,
546
- active: isActive,
547
- done: isDone,
548
- error: false,
549
- isDecider,
550
- isFork,
551
- description: node.description,
552
- subflowId: node.subflowId,
553
- dimmed,
554
- stepNumbers,
555
- isSubflow: !!node.isSubflowRoot
556
- },
557
- type: "stage",
558
- style: dimmed ? { opacity: 0.35 } : void 0
713
+ x,
714
+ y,
715
+ label: node.name,
716
+ isDecider,
717
+ isFork,
718
+ description: node.description,
719
+ icon: node.icon,
720
+ subflowId: node.subflowId,
721
+ isSubflow: !!node.isSubflowRoot,
722
+ isLazy: node.isLazy
559
723
  });
560
724
  let lastIds = [id];
561
725
  let bottomY = y;
@@ -566,120 +730,257 @@ function walk(node, state, x, y) {
566
730
  const childResults = [];
567
731
  for (let i = 0; i < node.children.length; i++) {
568
732
  const child = node.children[i];
733
+ if (!child) continue;
569
734
  const childX = startX + i * X_SPREAD;
570
735
  const edgeLabel = node.branchIds?.[i];
571
- addEdge(state, id, nid(child), edgeLabel);
572
- const result = walk(child, state, childX, childY);
736
+ state.edgeCounter++;
737
+ state.edges.push({ id: `se${state.edgeCounter}`, source: id, target: nid(child), label: edgeLabel, isLoop: false });
738
+ const result = walkLayout(child, state, childX, childY);
573
739
  childResults.push(result);
574
740
  }
575
741
  lastIds = childResults.flatMap((r) => r.lastIds);
576
742
  bottomY = Math.max(...childResults.map((r) => r.bottomY));
577
743
  }
578
744
  if (node.loopTarget) {
579
- addEdge(state, id, node.loopTarget, "loop", true);
745
+ const resolvedTarget = state.idToName.get(node.loopTarget) ?? node.loopTarget;
746
+ state.edgeCounter++;
747
+ state.edges.push({ id: `se${state.edgeCounter}`, source: id, target: resolvedTarget, label: "loop", isLoop: true });
580
748
  }
581
749
  if (node.next) {
750
+ const rawNextId = nid(node.next);
751
+ const resolvedNextId = state.idToName.get(rawNextId) ?? rawNextId;
752
+ const isLoopRef = node.loopTarget && state.seen.has(resolvedNextId);
753
+ if (isLoopRef) {
754
+ return { lastIds, bottomY };
755
+ }
582
756
  const nextY = bottomY + Y_STEP;
583
- const nextId = nid(node.next);
584
757
  for (const lid of lastIds) {
585
- if (node.loopTarget && lid === id && node.loopTarget === nextId) continue;
586
- addEdge(state, lid, nextId);
758
+ state.edgeCounter++;
759
+ state.edges.push({ id: `se${state.edgeCounter}`, source: lid, target: resolvedNextId, isLoop: false });
587
760
  }
588
- const result = walk(node.next, state, x, nextY);
589
- return result;
761
+ return walkLayout(node.next, state, x, nextY);
590
762
  }
591
763
  return { lastIds, bottomY };
592
764
  }
593
- function specToReactFlow(spec, overlay, colors) {
765
+ function specToLayout(spec) {
594
766
  const state = {
595
767
  nodes: [],
596
768
  edges: [],
597
769
  edgeCounter: 0,
598
770
  seen: /* @__PURE__ */ new Set(),
599
- overlay: overlay ?? null,
600
- colors: { ...DEFAULT_COLORS, ...colors }
771
+ idToName: /* @__PURE__ */ new Map()
601
772
  };
602
- walk(spec, state, 300, 0);
603
- return { nodes: state.nodes, edges: state.edges };
773
+ walkLayout(spec, state, 300, 0);
774
+ return { nodes: state.nodes, edges: state.edges, idToName: state.idToName };
604
775
  }
605
-
606
- // src/components/FlowchartView/useSubflowNavigation.ts
607
- var import_react6 = require("react");
608
- function useSubflowNavigation(rootSpec, overlay, colors) {
609
- const [stack, setStack] = (0, import_react6.useState)([]);
610
- const currentSpec = stack.length > 0 ? stack[stack.length - 1].spec : rootSpec;
611
- const { nodes, edges } = (0, import_react6.useMemo)(() => {
612
- if (!currentSpec) return { nodes: [], edges: [] };
613
- return specToReactFlow(currentSpec, overlay, colors);
614
- }, [currentSpec, overlay, colors]);
615
- const subflowMap = (0, import_react6.useMemo)(() => {
616
- const map = /* @__PURE__ */ new Map();
617
- if (!currentSpec) return map;
618
- function collectSubflows(node) {
619
- if (node.isSubflowRoot && node.subflowStructure) {
620
- const id = node.name || node.id || "";
621
- map.set(id, node);
776
+ function applyOverlay(layout, overlay, colors) {
777
+ const c = { ...DEFAULT_COLORS, ...colors };
778
+ const o = overlay ?? null;
779
+ const nodes = layout.nodes.map((ln) => {
780
+ const isDone = o ? o.doneStages.has(ln.id) : false;
781
+ const isActive = o ? o.activeStage === ln.id : false;
782
+ const wasExecuted = o ? o.executedStages.has(ln.id) : false;
783
+ const dimmed = o && !wasExecuted;
784
+ let stepNumbers;
785
+ if (o?.executionOrder) {
786
+ const nums = [];
787
+ for (let i = 0; i < o.executionOrder.length; i++) {
788
+ if (o.executionOrder[i] === ln.id) nums.push(i + 1);
622
789
  }
623
- if (node.children) node.children.forEach(collectSubflows);
624
- if (node.next) collectSubflows(node.next);
790
+ if (nums.length > 0) stepNumbers = nums;
625
791
  }
626
- collectSubflows(currentSpec);
627
- return map;
628
- }, [currentSpec]);
629
- const breadcrumbs = (0, import_react6.useMemo)(() => {
630
- const root = {
631
- label: rootSpec?.name || "Flowchart",
632
- spec: rootSpec,
633
- description: rootSpec?.description
792
+ return {
793
+ id: ln.id,
794
+ position: { x: ln.x, y: ln.y },
795
+ data: {
796
+ label: ln.label,
797
+ active: isActive,
798
+ done: isDone,
799
+ error: false,
800
+ isDecider: ln.isDecider,
801
+ isFork: ln.isFork,
802
+ description: ln.description,
803
+ icon: ln.icon,
804
+ subflowId: ln.subflowId,
805
+ dimmed,
806
+ stepNumbers,
807
+ isSubflow: ln.isSubflow,
808
+ isLazy: ln.isLazy
809
+ },
810
+ type: "stage",
811
+ style: dimmed ? { opacity: 0.35 } : void 0
634
812
  };
635
- return [root, ...stack];
636
- }, [rootSpec, stack]);
637
- const handleNodeClick = (0, import_react6.useCallback)(
638
- (nodeId) => {
639
- const subflowNode = subflowMap.get(nodeId);
640
- if (!subflowNode?.subflowStructure) return false;
641
- setStack((prev) => [
642
- ...prev,
643
- {
644
- label: subflowNode.subflowName || subflowNode.name,
645
- spec: subflowNode.subflowStructure,
646
- description: subflowNode.description
813
+ });
814
+ const edges = [];
815
+ for (const le of layout.edges) {
816
+ const executed = o && o.executedStages.has(le.source) && o.executedStages.has(le.target);
817
+ const isLeadingEdge = o && le.source === o.activeStage && !o.doneStages.has(le.target);
818
+ if (le.isLoop) {
819
+ let loopExecuted = false;
820
+ if (o?.executionOrder) {
821
+ const lastSourceIdx = o.executionOrder.lastIndexOf(le.source);
822
+ if (lastSourceIdx >= 0) {
823
+ loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(le.target);
647
824
  }
648
- ]);
649
- return true;
650
- },
651
- [subflowMap]
652
- );
653
- const navigateTo = (0, import_react6.useCallback)(
654
- (level) => {
655
- if (level === 0) {
656
- setStack([]);
657
- } else {
658
- setStack((prev) => prev.slice(0, level));
659
825
  }
826
+ edges.push({
827
+ id: le.id,
828
+ source: le.source,
829
+ target: le.target,
830
+ sourceHandle: "loop-source",
831
+ targetHandle: "loop-target",
832
+ label: le.label ?? "loop",
833
+ type: "smoothstep",
834
+ pathOptions: { offset: 40, borderRadius: 16 },
835
+ style: {
836
+ stroke: c.edgeLoop,
837
+ strokeWidth: loopExecuted ? 3 : 2,
838
+ strokeDasharray: "6 3",
839
+ opacity: o && !loopExecuted ? 0.35 : 1
840
+ },
841
+ labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
842
+ animated: loopExecuted,
843
+ zIndex: 2
844
+ });
845
+ } else if (executed) {
846
+ edges.push({
847
+ id: `${le.id}-glow`,
848
+ source: le.source,
849
+ target: le.target,
850
+ style: { stroke: c.pathGlow, strokeWidth: 8, opacity: 0.4 },
851
+ zIndex: 0,
852
+ selectable: false,
853
+ focusable: false
854
+ });
855
+ edges.push({
856
+ id: le.id,
857
+ source: le.source,
858
+ target: le.target,
859
+ label: le.label,
860
+ style: {
861
+ stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
862
+ strokeWidth: 3.5
863
+ },
864
+ labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
865
+ animated: !!isLeadingEdge,
866
+ zIndex: 1
867
+ });
868
+ } else {
869
+ edges.push({
870
+ id: le.id,
871
+ source: le.source,
872
+ target: le.target,
873
+ label: le.label,
874
+ style: {
875
+ stroke: c.edgeDefault,
876
+ strokeWidth: 1.5,
877
+ opacity: o ? 0.3 : 1
878
+ },
879
+ labelStyle: { fontSize: 10, fill: c.labelDefault }
880
+ });
881
+ }
882
+ }
883
+ return { nodes, edges };
884
+ }
885
+ function specToReactFlow(spec, overlay, colors) {
886
+ const layout = specToLayout(spec);
887
+ return applyOverlay(layout, overlay, colors);
888
+ }
889
+
890
+ // src/components/FlowchartView/TracedFlowchartView.tsx
891
+ var import_jsx_runtime4 = require("react/jsx-runtime");
892
+ var defaultNodeTypes = { stage: StageNode };
893
+ function FitViewOnResize() {
894
+ const { fitView } = (0, import_react8.useReactFlow)();
895
+ (0, import_react7.useEffect)(() => {
896
+ const handler = () => {
897
+ requestAnimationFrame(() => fitView({ padding: 0.3 }));
898
+ };
899
+ window.addEventListener("resize", handler);
900
+ return () => window.removeEventListener("resize", handler);
901
+ }, [fitView]);
902
+ return null;
903
+ }
904
+ function TracedFlowchartView({
905
+ spec,
906
+ snapshots,
907
+ snapshotIndex = 0,
908
+ onNodeClick,
909
+ nodeTypes: customNodeTypes,
910
+ unstyled = false,
911
+ className,
912
+ style
913
+ }) {
914
+ const nodeTypes2 = customNodeTypes ?? defaultNodeTypes;
915
+ const overlay = (0, import_react7.useMemo)(() => {
916
+ if (!snapshots || snapshots.length === 0) return void 0;
917
+ const executionOrder = snapshots.slice(0, snapshotIndex + 1).map((s) => s.stageLabel);
918
+ const doneStages = new Set(
919
+ snapshots.slice(0, snapshotIndex).map((s) => s.stageLabel)
920
+ );
921
+ const activeStage = snapshots[snapshotIndex]?.stageLabel ?? null;
922
+ const executedStages = /* @__PURE__ */ new Set([...doneStages]);
923
+ if (activeStage) executedStages.add(activeStage);
924
+ return { doneStages, activeStage, executedStages, executionOrder };
925
+ }, [snapshots, snapshotIndex]);
926
+ const layout = (0, import_react7.useMemo)(() => {
927
+ if (!spec) return null;
928
+ return specToLayout(spec);
929
+ }, [spec]);
930
+ const { nodes, edges } = (0, import_react7.useMemo)(() => {
931
+ if (!layout) return { nodes: [], edges: [] };
932
+ return applyOverlay(layout, overlay);
933
+ }, [layout, overlay]);
934
+ const handleNodeClick = (0, import_react7.useCallback)(
935
+ (_, node) => {
936
+ if (!onNodeClick) return;
937
+ onNodeClick(node.id);
660
938
  },
661
- []
939
+ [onNodeClick]
940
+ );
941
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
942
+ "div",
943
+ {
944
+ className,
945
+ style: { width: "100%", height: "100%", ...style },
946
+ "data-fp": "traced-flowchart",
947
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
948
+ import_react8.ReactFlow,
949
+ {
950
+ nodes,
951
+ edges,
952
+ onNodeClick: handleNodeClick,
953
+ nodeTypes: nodeTypes2,
954
+ fitView: true,
955
+ fitViewOptions: { padding: 0.3 },
956
+ proOptions: { hideAttribution: true },
957
+ panOnDrag: false,
958
+ zoomOnScroll: false,
959
+ zoomOnPinch: false,
960
+ zoomOnDoubleClick: false,
961
+ preventScrolling: false,
962
+ nodesDraggable: false,
963
+ nodesConnectable: false,
964
+ elementsSelectable: !!onNodeClick,
965
+ children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FitViewOnResize, {}),
967
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Background, { variant: import_react8.BackgroundVariant.Dots, gap: 16, size: 1 })
968
+ ]
969
+ }
970
+ )
971
+ }
662
972
  );
663
- return {
664
- breadcrumbs,
665
- nodes,
666
- edges,
667
- handleNodeClick,
668
- navigateTo,
669
- isInSubflow: stack.length > 0,
670
- currentSubflowNodeName: stack.length > 0 ? stack[stack.length - 1].label : null
671
- };
672
973
  }
673
974
 
674
975
  // src/components/FlowchartView/SubflowBreadcrumb.tsx
675
- var import_react7 = require("react");
676
- var import_jsx_runtime4 = require("react/jsx-runtime");
677
- var SubflowBreadcrumb = (0, import_react7.memo)(function SubflowBreadcrumb2({
976
+ var import_react9 = require("react");
977
+ var import_jsx_runtime5 = require("react/jsx-runtime");
978
+ var SubflowBreadcrumb = (0, import_react9.memo)(function SubflowBreadcrumb2({
678
979
  breadcrumbs,
679
980
  onNavigate
680
981
  }) {
681
982
  if (breadcrumbs.length <= 1) return null;
682
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
983
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
683
984
  "div",
684
985
  {
685
986
  style: {
@@ -696,10 +997,10 @@ var SubflowBreadcrumb = (0, import_react7.memo)(function SubflowBreadcrumb2({
696
997
  },
697
998
  children: breadcrumbs.map((crumb, i) => {
698
999
  const isLast = i === breadcrumbs.length - 1;
699
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
700
- i > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
701
- isLast ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
702
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1000
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1001
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
1002
+ isLast ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1003
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
703
1004
  "span",
704
1005
  {
705
1006
  style: {
@@ -709,7 +1010,7 @@ var SubflowBreadcrumb = (0, import_react7.memo)(function SubflowBreadcrumb2({
709
1010
  children: crumb.label
710
1011
  }
711
1012
  ),
712
- crumb.description && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1013
+ crumb.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
713
1014
  "span",
714
1015
  {
715
1016
  style: {
@@ -723,7 +1024,7 @@ var SubflowBreadcrumb = (0, import_react7.memo)(function SubflowBreadcrumb2({
723
1024
  ]
724
1025
  }
725
1026
  )
726
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1027
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
727
1028
  "button",
728
1029
  {
729
1030
  onClick: () => onNavigate(i),
@@ -754,13 +1055,83 @@ var SubflowBreadcrumb = (0, import_react7.memo)(function SubflowBreadcrumb2({
754
1055
  );
755
1056
  });
756
1057
 
1058
+ // src/components/FlowchartView/useSubflowNavigation.ts
1059
+ var import_react10 = require("react");
1060
+ function useSubflowNavigation(rootSpec, overlay, colors) {
1061
+ const [stack, setStack] = (0, import_react10.useState)([]);
1062
+ const currentSpec = stack.length > 0 ? stack[stack.length - 1].spec : rootSpec;
1063
+ const { nodes, edges } = (0, import_react10.useMemo)(() => {
1064
+ if (!currentSpec) return { nodes: [], edges: [] };
1065
+ return specToReactFlow(currentSpec, overlay, colors);
1066
+ }, [currentSpec, overlay, colors]);
1067
+ const subflowMap = (0, import_react10.useMemo)(() => {
1068
+ const map = /* @__PURE__ */ new Map();
1069
+ if (!currentSpec) return map;
1070
+ function collectSubflows(node) {
1071
+ if (node.isSubflowRoot && node.subflowStructure) {
1072
+ const id = node.name || node.id || "";
1073
+ map.set(id, node);
1074
+ }
1075
+ if (node.children) node.children.forEach(collectSubflows);
1076
+ if (node.next) collectSubflows(node.next);
1077
+ }
1078
+ collectSubflows(currentSpec);
1079
+ return map;
1080
+ }, [currentSpec]);
1081
+ const breadcrumbs = (0, import_react10.useMemo)(() => {
1082
+ const root = {
1083
+ label: rootSpec?.name || "Flowchart",
1084
+ spec: rootSpec,
1085
+ description: rootSpec?.description
1086
+ };
1087
+ return [root, ...stack];
1088
+ }, [rootSpec, stack]);
1089
+ const handleNodeClick = (0, import_react10.useCallback)(
1090
+ (nodeId) => {
1091
+ const subflowNode = subflowMap.get(nodeId);
1092
+ if (!subflowNode?.subflowStructure) return false;
1093
+ setStack((prev) => [
1094
+ ...prev,
1095
+ {
1096
+ label: subflowNode.subflowName || subflowNode.name,
1097
+ spec: subflowNode.subflowStructure,
1098
+ description: subflowNode.description
1099
+ }
1100
+ ]);
1101
+ return true;
1102
+ },
1103
+ [subflowMap]
1104
+ );
1105
+ const navigateTo = (0, import_react10.useCallback)(
1106
+ (level) => {
1107
+ if (level === 0) {
1108
+ setStack([]);
1109
+ } else {
1110
+ setStack((prev) => prev.slice(0, level));
1111
+ }
1112
+ },
1113
+ []
1114
+ );
1115
+ return {
1116
+ breadcrumbs,
1117
+ nodes,
1118
+ edges,
1119
+ handleNodeClick,
1120
+ navigateTo,
1121
+ isInSubflow: stack.length > 0,
1122
+ currentSubflowNodeName: stack.length > 0 ? stack[stack.length - 1].label : null
1123
+ };
1124
+ }
1125
+
757
1126
  // src/components/FlowchartView/SubflowTree.tsx
758
- var import_react8 = require("react");
759
- var import_jsx_runtime5 = require("react/jsx-runtime");
1127
+ var import_react11 = require("react");
1128
+ var import_jsx_runtime6 = require("react/jsx-runtime");
760
1129
  function specToTree(node) {
1130
+ if (!node) return [];
761
1131
  const entries = [];
762
1132
  const seen = /* @__PURE__ */ new Set();
763
- function walk2(n) {
1133
+ function walk(n) {
1134
+ if (!n) return;
764
1135
  const id = n.name || n.id || "";
765
1136
  if (seen.has(id)) return;
766
1137
  seen.add(id);
@@ -776,35 +1147,35 @@ function specToTree(node) {
776
1147
  entries.push(entry);
777
1148
  if (n.children) {
778
1149
  for (const child of n.children) {
779
- walk2(child);
1150
+ if (child) walk(child);
780
1151
  }
781
1152
  }
782
1153
  if (n.next) {
783
- walk2(n.next);
1154
+ walk(n.next);
784
1155
  }
785
1156
  }
786
- walk2(node);
1157
+ walk(node);
787
1158
  return entries;
788
1159
  }
789
- var TreeNode = (0, import_react8.memo)(function TreeNode2({
1160
+ var TreeNode = (0, import_react11.memo)(function TreeNode2({
790
1161
  entry,
791
1162
  depth,
792
1163
  activeStage,
793
1164
  doneStages,
794
1165
  onNodeSelect
795
1166
  }) {
796
- const [expanded, setExpanded] = (0, import_react8.useState)(true);
1167
+ const [expanded, setExpanded] = (0, import_react11.useState)(true);
797
1168
  const hasChildren = entry.children && entry.children.length > 0;
798
1169
  const isActive = activeStage === entry.name;
799
1170
  const isDone = doneStages?.has(entry.name);
800
- const handleClick = (0, import_react8.useCallback)(() => {
1171
+ const handleClick = (0, import_react11.useCallback)(() => {
801
1172
  if (hasChildren) {
802
1173
  setExpanded((prev) => !prev);
803
1174
  }
804
1175
  onNodeSelect?.(entry.name, !!entry.isSubflow);
805
1176
  }, [hasChildren, onNodeSelect, entry.name, entry.isSubflow]);
806
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
807
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1177
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1178
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
808
1179
  "button",
809
1180
  {
810
1181
  onClick: handleClick,
@@ -835,7 +1206,7 @@ var TreeNode = (0, import_react8.memo)(function TreeNode2({
835
1206
  }
836
1207
  },
837
1208
  children: [
838
- hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1209
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
839
1210
  "span",
840
1211
  {
841
1212
  style: {
@@ -850,8 +1221,8 @@ var TreeNode = (0, import_react8.memo)(function TreeNode2({
850
1221
  },
851
1222
  children: "\u25B6"
852
1223
  }
853
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { width: 12, flexShrink: 0 } }),
854
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1224
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { width: 12, flexShrink: 0 } }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
855
1226
  "span",
856
1227
  {
857
1228
  style: {
@@ -863,8 +1234,8 @@ var TreeNode = (0, import_react8.memo)(function TreeNode2({
863
1234
  }
864
1235
  }
865
1236
  ),
866
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
867
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1237
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
868
1239
  "span",
869
1240
  {
870
1241
  style: {
@@ -876,11 +1247,11 @@ var TreeNode = (0, import_react8.memo)(function TreeNode2({
876
1247
  },
877
1248
  children: [
878
1249
  entry.name,
879
- entry.isSubflow && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
1250
+ entry.isSubflow && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
880
1251
  ]
881
1252
  }
882
1253
  ),
883
- entry.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1254
+ entry.description && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
884
1255
  "span",
885
1256
  {
886
1257
  style: {
@@ -897,7 +1268,7 @@ var TreeNode = (0, import_react8.memo)(function TreeNode2({
897
1268
  ]
898
1269
  }
899
1270
  ),
900
- hasChildren && expanded && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: entry.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1271
+ hasChildren && expanded && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: entry.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
901
1272
  TreeNode2,
902
1273
  {
903
1274
  entry: child,
@@ -906,12 +1277,12 @@ var TreeNode = (0, import_react8.memo)(function TreeNode2({
906
1277
  doneStages,
907
1278
  onNodeSelect
908
1279
  },
909
- `${child.name}-${i}`
1280
+ child.subflowId ?? `${child.name}-${i}`
910
1281
  )) })
911
1282
  ] });
912
1283
  });
913
- var SectionLabel = (0, import_react8.memo)(function SectionLabel2({ children }) {
914
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1284
+ var SectionLabel = (0, import_react11.memo)(function SectionLabel2({ children }) {
1285
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
915
1286
  "div",
916
1287
  {
917
1288
  style: {
@@ -926,7 +1297,7 @@ var SectionLabel = (0, import_react8.memo)(function SectionLabel2({ children })
926
1297
  }
927
1298
  );
928
1299
  });
929
- var SubflowTree = (0, import_react8.memo)(function SubflowTree2({
1300
+ var SubflowTree = (0, import_react11.memo)(function SubflowTree2({
930
1301
  spec,
931
1302
  activeStage,
932
1303
  doneStages,
@@ -935,21 +1306,10 @@ var SubflowTree = (0, import_react8.memo)(function SubflowTree2({
935
1306
  className,
936
1307
  style
937
1308
  }) {
938
- const tree = (0, import_react8.useMemo)(() => specToTree(spec), [spec]);
939
- const mainStages = (0, import_react8.useMemo)(() => tree.filter((e) => !e.isSubflow), [tree]);
940
- const subflowStages = (0, import_react8.useMemo)(() => tree.filter((e) => e.isSubflow), [tree]);
941
- const renderEntries = (entries) => entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
942
- TreeNode,
943
- {
944
- entry,
945
- depth: 0,
946
- activeStage,
947
- doneStages,
948
- onNodeSelect
949
- },
950
- `${entry.name}-${i}`
951
- ));
952
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1309
+ const tree = (0, import_react11.useMemo)(() => specToTree(spec), [spec]);
1310
+ const subflowStages = (0, import_react11.useMemo)(() => tree.filter((e) => e.isSubflow), [tree]);
1311
+ if (subflowStages.length === 0) return null;
1312
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
953
1313
  "div",
954
1314
  {
955
1315
  className,
@@ -967,185 +1327,28 @@ var SubflowTree = (0, import_react8.memo)(function SubflowTree2({
967
1327
  ...style
968
1328
  },
969
1329
  children: [
970
- !unstyled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SectionLabel, { children: "Flowchart" }),
971
- renderEntries(mainStages),
972
- subflowStages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
973
- !unstyled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height: 1, background: theme.border, margin: "8px 12px" } }),
974
- !unstyled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SectionLabel, { children: "Subflows" }),
975
- renderEntries(subflowStages)
976
- ] })
977
- ]
978
- }
979
- );
980
- });
981
-
982
- // src/components/FlowchartView/TracedFlowchartView.tsx
983
- var import_jsx_runtime6 = require("react/jsx-runtime");
984
- var nodeTypes2 = { stage: StageNode };
985
- function TracedFlowchartView({
986
- spec,
987
- snapshots,
988
- snapshotIndex = 0,
989
- onNodeClick,
990
- onSubflowChange,
991
- showTree = false,
992
- treeWidth = 200,
993
- unstyled = false,
994
- className,
995
- style
996
- }) {
997
- const [treeVisible, setTreeVisible] = (0, import_react9.useState)(showTree);
998
- const subflowNav = useSubflowNavigation(spec);
999
- const currentSpec = subflowNav.breadcrumbs.length > 0 ? subflowNav.breadcrumbs[subflowNav.breadcrumbs.length - 1].spec : null;
1000
- const overlay = (0, import_react9.useMemo)(() => {
1001
- if (!snapshots || snapshots.length === 0) return void 0;
1002
- const executionOrder = snapshots.slice(0, snapshotIndex + 1).map((s) => s.stageLabel);
1003
- const doneStages = new Set(
1004
- snapshots.slice(0, snapshotIndex).map((s) => s.stageLabel)
1005
- );
1006
- const activeStage = snapshots[snapshotIndex]?.stageLabel ?? null;
1007
- const executedStages = /* @__PURE__ */ new Set([...doneStages]);
1008
- if (activeStage) executedStages.add(activeStage);
1009
- return { doneStages, activeStage, executedStages, executionOrder };
1010
- }, [snapshots, snapshotIndex]);
1011
- const { nodes, edges } = (0, import_react9.useMemo)(() => {
1012
- if (!currentSpec) return { nodes: [], edges: [] };
1013
- return specToReactFlow(currentSpec, overlay);
1014
- }, [currentSpec, overlay]);
1015
- const handleNodeClick = (0, import_react9.useCallback)(
1016
- (_, node) => {
1017
- if (subflowNav.handleNodeClick(node.id)) {
1018
- onSubflowChange?.(true, node.id);
1019
- return;
1020
- }
1021
- if (onNodeClick && snapshots) {
1022
- const idx = snapshots.findIndex((s) => s.stageLabel === node.id);
1023
- if (idx >= 0) onNodeClick(idx);
1024
- } else if (onNodeClick) {
1025
- onNodeClick(node.id);
1026
- }
1027
- },
1028
- [subflowNav, onNodeClick, onSubflowChange, snapshots]
1029
- );
1030
- const handleBreadcrumbNavigate = (0, import_react9.useCallback)(
1031
- (level) => {
1032
- subflowNav.navigateTo(level);
1033
- onSubflowChange?.(level > 0, null);
1034
- },
1035
- [subflowNav, onSubflowChange]
1036
- );
1037
- const handleTreeNodeSelect = (0, import_react9.useCallback)(
1038
- (name, isSubflow) => {
1039
- if (isSubflow) {
1040
- if (subflowNav.handleNodeClick(name)) {
1041
- onSubflowChange?.(true, name);
1042
- }
1043
- } else if (onNodeClick && snapshots) {
1044
- const idx = snapshots.findIndex((s) => s.stageLabel === name);
1045
- if (idx >= 0) onNodeClick(idx);
1046
- }
1047
- },
1048
- [subflowNav, onNodeClick, onSubflowChange, snapshots]
1049
- );
1050
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1051
- "div",
1052
- {
1053
- className,
1054
- style: { width: "100%", height: "100%", display: "flex", flexDirection: "row", ...style },
1055
- "data-fp": "traced-flowchart",
1056
- children: [
1057
- showTree && treeVisible && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1058
- SubflowTree,
1330
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Subflows" }),
1331
+ subflowStages.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1332
+ TreeNode,
1059
1333
  {
1060
- spec,
1061
- activeStage: overlay?.activeStage,
1062
- doneStages: overlay?.doneStages,
1063
- onNodeSelect: handleTreeNodeSelect,
1064
- unstyled,
1065
- style: { width: treeWidth, flexShrink: 0, height: "100%" }
1066
- }
1067
- ),
1068
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0, height: "100%" }, children: [
1069
- (subflowNav.isInSubflow || showTree && !treeVisible) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "stretch", flexShrink: 0 }, children: [
1070
- showTree && !treeVisible && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1071
- "button",
1072
- {
1073
- onClick: () => setTreeVisible(true),
1074
- "data-fp": "tree-toggle",
1075
- style: unstyled ? {} : {
1076
- background: "transparent",
1077
- border: "none",
1078
- cursor: "pointer",
1079
- padding: "6px 8px",
1080
- fontSize: 10,
1081
- flexShrink: 0
1082
- },
1083
- children: "\u25B6"
1084
- }
1085
- ),
1086
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1087
- SubflowBreadcrumb,
1088
- {
1089
- breadcrumbs: subflowNav.breadcrumbs,
1090
- onNavigate: handleBreadcrumbNavigate
1091
- }
1092
- ) })
1093
- ] }),
1094
- showTree && treeVisible && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "stretch", flexShrink: 0 }, children: [
1095
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1096
- "button",
1097
- {
1098
- onClick: () => setTreeVisible(false),
1099
- "data-fp": "tree-toggle",
1100
- style: unstyled ? {} : {
1101
- background: "transparent",
1102
- border: "none",
1103
- cursor: "pointer",
1104
- padding: "6px 8px",
1105
- fontSize: 10,
1106
- flexShrink: 0
1107
- },
1108
- children: "\u25C0"
1109
- }
1110
- ),
1111
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: subflowNav.isInSubflow && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1112
- SubflowBreadcrumb,
1113
- {
1114
- breadcrumbs: subflowNav.breadcrumbs,
1115
- onNavigate: handleBreadcrumbNavigate
1116
- }
1117
- ) })
1118
- ] }),
1119
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1120
- import_react10.ReactFlow,
1121
- {
1122
- nodes,
1123
- edges,
1124
- onNodeClick: handleNodeClick,
1125
- nodeTypes: nodeTypes2,
1126
- fitView: true,
1127
- panOnDrag: false,
1128
- zoomOnScroll: false,
1129
- zoomOnPinch: false,
1130
- zoomOnDoubleClick: false,
1131
- preventScrolling: false,
1132
- nodesDraggable: false,
1133
- nodesConnectable: false,
1134
- elementsSelectable: !!onNodeClick,
1135
- children: !unstyled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react10.Background, { variant: import_react10.BackgroundVariant.Dots, gap: 16, size: 1 })
1136
- }
1137
- ) })
1138
- ] })
1334
+ entry,
1335
+ depth: 0,
1336
+ activeStage,
1337
+ doneStages,
1338
+ onNodeSelect
1339
+ },
1340
+ entry.subflowId ?? `${entry.name}-${i}`
1341
+ ))
1139
1342
  ]
1140
1343
  }
1141
1344
  );
1142
- }
1345
+ });
1143
1346
 
1144
1347
  // src/components/TimeTravelDebugger/TimeTravelDebugger.tsx
1145
- var import_react14 = require("react");
1348
+ var import_react15 = require("react");
1146
1349
 
1147
1350
  // src/components/MemoryInspector/MemoryInspector.tsx
1148
- var import_react11 = require("react");
1351
+ var import_react12 = require("react");
1149
1352
  var import_jsx_runtime7 = require("react/jsx-runtime");
1150
1353
  function MemoryInspector({
1151
1354
  data,
@@ -1158,28 +1361,45 @@ function MemoryInspector({
1158
1361
  className,
1159
1362
  style
1160
1363
  }) {
1161
- const { memory, newKeys } = (0, import_react11.useMemo)(() => {
1364
+ const cacheRef = (0, import_react12.useRef)(null);
1365
+ const { memory, newKeys } = (0, import_react12.useMemo)(() => {
1162
1366
  if (data) {
1163
1367
  return { memory: data, newKeys: /* @__PURE__ */ new Set() };
1164
1368
  }
1165
1369
  if (!snapshots || snapshots.length === 0) {
1166
1370
  return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
1167
1371
  }
1168
- const merged = {};
1169
- for (let i = 0; i <= Math.min(selectedIndex, snapshots.length - 1); i++) {
1170
- Object.assign(merged, snapshots[i]?.memory);
1372
+ const safeIdx = Math.min(selectedIndex, snapshots.length - 1);
1373
+ let merged;
1374
+ const cache = cacheRef.current;
1375
+ if (cache && cache.snapshots === snapshots && cache.index <= safeIdx) {
1376
+ merged = { ...cache.accumulated };
1377
+ for (let i = cache.index + 1; i <= safeIdx; i++) {
1378
+ Object.assign(merged, snapshots[i]?.memory);
1379
+ }
1380
+ } else {
1381
+ merged = {};
1382
+ for (let i = 0; i <= safeIdx; i++) {
1383
+ Object.assign(merged, snapshots[i]?.memory);
1384
+ }
1171
1385
  }
1386
+ cacheRef.current = { snapshots, index: safeIdx, accumulated: merged };
1172
1387
  const nk = /* @__PURE__ */ new Set();
1173
- if (highlightNew && selectedIndex > 0) {
1174
- const prev = {};
1175
- for (let i = 0; i < selectedIndex; i++) {
1176
- Object.assign(prev, snapshots[i]?.memory);
1388
+ if (highlightNew && safeIdx > 0) {
1389
+ let prev;
1390
+ if (cache && cache.snapshots === snapshots && cache.index === safeIdx - 1) {
1391
+ prev = cache.accumulated;
1392
+ } else {
1393
+ prev = {};
1394
+ for (let i = 0; i < safeIdx; i++) {
1395
+ Object.assign(prev, snapshots[i]?.memory);
1396
+ }
1177
1397
  }
1178
- const current = snapshots[selectedIndex]?.memory ?? {};
1398
+ const current = snapshots[safeIdx]?.memory ?? {};
1179
1399
  for (const k of Object.keys(current)) {
1180
1400
  if (!(k in prev)) nk.add(k);
1181
1401
  }
1182
- } else if (highlightNew && selectedIndex === 0 && snapshots[0]) {
1402
+ } else if (highlightNew && safeIdx === 0 && snapshots[0]) {
1183
1403
  for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
1184
1404
  }
1185
1405
  return { memory: merged, newKeys: nk };
@@ -1188,9 +1408,9 @@ function MemoryInspector({
1188
1408
  const fs = fontSize[size];
1189
1409
  const pad = padding[size];
1190
1410
  if (unstyled) {
1191
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className, style, "data-fp": "memory-inspector", children: [
1411
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className, style, "data-fp": "memory-inspector", role: "region", "aria-label": "Memory state", children: [
1192
1412
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { "data-fp": "memory-label", children: "Memory State" }),
1193
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { "data-fp": "memory-json", children: JSON.stringify(memory, null, 2) })
1413
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { "data-fp": "memory-json", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { children: JSON.stringify(memory, null, 2) }) })
1194
1414
  ] });
1195
1415
  }
1196
1416
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
@@ -1203,6 +1423,8 @@ function MemoryInspector({
1203
1423
  ...style
1204
1424
  },
1205
1425
  "data-fp": "memory-inspector",
1426
+ role: "region",
1427
+ "aria-label": "Memory state",
1206
1428
  children: [
1207
1429
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1208
1430
  "span",
@@ -1302,7 +1524,7 @@ function formatValue(value) {
1302
1524
  }
1303
1525
 
1304
1526
  // src/components/NarrativeLog/NarrativeLog.tsx
1305
- var import_react12 = require("react");
1527
+ var import_react13 = require("react");
1306
1528
  var import_jsx_runtime8 = require("react/jsx-runtime");
1307
1529
  function NarrativeLog({
1308
1530
  snapshots,
@@ -1313,7 +1535,7 @@ function NarrativeLog({
1313
1535
  className,
1314
1536
  style
1315
1537
  }) {
1316
- const entries = (0, import_react12.useMemo)(() => {
1538
+ const entries = (0, import_react13.useMemo)(() => {
1317
1539
  if (narrative) {
1318
1540
  return [{ label: "Output", text: narrative, isCurrent: true }];
1319
1541
  }
@@ -1435,7 +1657,7 @@ function NarrativeLog({
1435
1657
  }
1436
1658
 
1437
1659
  // src/components/GanttTimeline/GanttTimeline.tsx
1438
- var import_react13 = require("react");
1660
+ var import_react14 = require("react");
1439
1661
  var import_jsx_runtime9 = require("react/jsx-runtime");
1440
1662
  function GanttTimeline({
1441
1663
  snapshots,
@@ -1447,10 +1669,10 @@ function GanttTimeline({
1447
1669
  style,
1448
1670
  maxVisibleRows = 5
1449
1671
  }) {
1450
- const [expanded, setExpanded] = (0, import_react13.useState)(false);
1451
- const activeRowRef = (0, import_react13.useRef)(null);
1452
- const scrollContainerRef = (0, import_react13.useRef)(null);
1453
- const totalWallTime = (0, import_react13.useMemo)(
1672
+ const [expanded, setExpanded] = (0, import_react14.useState)(false);
1673
+ const activeRowRef = (0, import_react14.useRef)(null);
1674
+ const scrollContainerRef = (0, import_react14.useRef)(null);
1675
+ const totalWallTime = (0, import_react14.useMemo)(
1454
1676
  () => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
1455
1677
  [snapshots]
1456
1678
  );
@@ -1461,7 +1683,7 @@ function GanttTimeline({
1461
1683
  const rowHeight = size === "compact" ? 18 : 22;
1462
1684
  const collapsible = maxVisibleRows > 0 && snapshots.length > maxVisibleRows;
1463
1685
  const showAll = expanded || !collapsible;
1464
- (0, import_react13.useEffect)(() => {
1686
+ (0, import_react14.useEffect)(() => {
1465
1687
  if (!showAll && activeRowRef.current && scrollContainerRef.current) {
1466
1688
  activeRowRef.current.scrollIntoView({
1467
1689
  block: "nearest",
@@ -1470,12 +1692,15 @@ function GanttTimeline({
1470
1692
  }
1471
1693
  }, [selectedIndex, showAll]);
1472
1694
  if (unstyled) {
1473
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className, style, "data-fp": "gantt-timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1695
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className, style, "data-fp": "gantt-timeline", role: "listbox", "aria-label": "Execution timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1474
1696
  "div",
1475
1697
  {
1476
1698
  "data-fp": "gantt-bar",
1477
1699
  "data-selected": idx === selectedIndex,
1478
1700
  "data-visible": idx <= selectedIndex,
1701
+ role: "option",
1702
+ "aria-selected": idx === selectedIndex,
1703
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
1479
1704
  onClick: () => onSelect?.(idx),
1480
1705
  children: [
1481
1706
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
@@ -1485,7 +1710,7 @@ function GanttTimeline({
1485
1710
  ] })
1486
1711
  ]
1487
1712
  },
1488
- snap.stageName
1713
+ `${snap.stageName}-${idx}`
1489
1714
  )) });
1490
1715
  }
1491
1716
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
@@ -1541,6 +1766,8 @@ function GanttTimeline({
1541
1766
  "div",
1542
1767
  {
1543
1768
  ref: scrollContainerRef,
1769
+ role: "listbox",
1770
+ "aria-label": "Execution timeline",
1544
1771
  style: {
1545
1772
  marginTop: 8,
1546
1773
  display: "flex",
@@ -1561,6 +1788,9 @@ function GanttTimeline({
1561
1788
  "div",
1562
1789
  {
1563
1790
  ref: isSelected ? activeRowRef : void 0,
1791
+ role: "option",
1792
+ "aria-selected": isSelected,
1793
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
1564
1794
  onClick: () => onSelect?.(idx),
1565
1795
  style: {
1566
1796
  display: "flex",
@@ -1576,6 +1806,7 @@ function GanttTimeline({
1576
1806
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1577
1807
  "span",
1578
1808
  {
1809
+ title: snap.stageLabel,
1579
1810
  style: {
1580
1811
  width: labelWidth,
1581
1812
  fontSize: fs.small,
@@ -1635,7 +1866,7 @@ function GanttTimeline({
1635
1866
  )
1636
1867
  ]
1637
1868
  },
1638
- snap.stageName
1869
+ `${snap.stageName}-${idx}`
1639
1870
  );
1640
1871
  })
1641
1872
  }
@@ -1685,7 +1916,7 @@ function TimeTravelDebugger({
1685
1916
  className,
1686
1917
  style
1687
1918
  }) {
1688
- const [selectedIndex, setSelectedIndex] = (0, import_react14.useState)(0);
1919
+ const [selectedIndex, setSelectedIndex] = (0, import_react15.useState)(0);
1689
1920
  const fs = fontSize[size];
1690
1921
  const pad = padding[size];
1691
1922
  if (snapshots.length === 0) {
@@ -1990,6 +2221,8 @@ function ScrubButton({
1990
2221
  SubflowTree,
1991
2222
  TimeTravelDebugger,
1992
2223
  TracedFlowchartView,
2224
+ applyOverlay,
2225
+ specToLayout,
1993
2226
  specToReactFlow,
1994
2227
  useSubflowNavigation
1995
2228
  });