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.
package/dist/flowchart.js CHANGED
@@ -9,14 +9,14 @@ import {
9
9
  } from "@xyflow/react";
10
10
 
11
11
  // src/components/StageNode/StageNode.tsx
12
- import { memo } from "react";
12
+ import { memo, useEffect as useEffect2, useRef } from "react";
13
13
  import { Handle, Position } from "@xyflow/react";
14
14
 
15
15
  // src/theme/ThemeProvider.tsx
16
16
  import { createContext, useContext } from "react";
17
17
 
18
18
  // src/theme/tokens.ts
19
- var defaultTokens = {
19
+ var rawDefaults = {
20
20
  colors: {
21
21
  primary: "#6366f1",
22
22
  success: "#22c55e",
@@ -36,6 +36,26 @@ var defaultTokens = {
36
36
  mono: "'JetBrains Mono', 'Fira Code', monospace"
37
37
  }
38
38
  };
39
+ var defaultTokens = {
40
+ colors: {
41
+ primary: `var(--fp-color-primary, ${rawDefaults.colors.primary})`,
42
+ success: `var(--fp-color-success, ${rawDefaults.colors.success})`,
43
+ error: `var(--fp-color-error, ${rawDefaults.colors.error})`,
44
+ warning: `var(--fp-color-warning, ${rawDefaults.colors.warning})`,
45
+ bgPrimary: `var(--fp-bg-primary, ${rawDefaults.colors.bgPrimary})`,
46
+ bgSecondary: `var(--fp-bg-secondary, ${rawDefaults.colors.bgSecondary})`,
47
+ bgTertiary: `var(--fp-bg-tertiary, ${rawDefaults.colors.bgTertiary})`,
48
+ textPrimary: `var(--fp-text-primary, ${rawDefaults.colors.textPrimary})`,
49
+ textSecondary: `var(--fp-text-secondary, ${rawDefaults.colors.textSecondary})`,
50
+ textMuted: `var(--fp-text-muted, ${rawDefaults.colors.textMuted})`,
51
+ border: `var(--fp-border, ${rawDefaults.colors.border})`
52
+ },
53
+ radius: `var(--fp-radius, ${rawDefaults.radius})`,
54
+ fontFamily: {
55
+ sans: `var(--fp-font-sans, ${rawDefaults.fontFamily.sans})`,
56
+ mono: `var(--fp-font-mono, ${rawDefaults.fontFamily.mono})`
57
+ }
58
+ };
39
59
 
40
60
  // src/theme/ThemeProvider.tsx
41
61
  import { jsx } from "react/jsx-runtime";
@@ -72,12 +92,174 @@ var padding = {
72
92
  detailed: 16
73
93
  };
74
94
 
95
+ // src/theme/useDarkModeTokens.ts
96
+ import { useState, useEffect } from "react";
97
+
75
98
  // src/components/StageNode/StageNode.tsx
76
99
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
100
+ var KEYFRAMES_ID = "fp-stage-node-keyframes";
101
+ var KEYFRAMES_CSS = `
102
+ @media (prefers-reduced-motion: no-preference) {
103
+ @keyframes fp-pulse {
104
+ 0%, 100% { opacity: 0.4; transform: scale(1); }
105
+ 50% { opacity: 0.15; transform: scale(1.06); }
106
+ }
107
+ @keyframes fp-blink {
108
+ 0%, 100% { opacity: 1; }
109
+ 50% { opacity: 0.3; }
110
+ }
111
+ }
112
+ @media (prefers-reduced-motion: reduce) {
113
+ @keyframes fp-pulse { 0%, 100% { opacity: 0.3; } }
114
+ @keyframes fp-blink { 0%, 100% { opacity: 1; } }
115
+ }
116
+ `;
117
+ var ICON_SIZE = 16;
118
+ function StageIcon({ type, color }) {
119
+ const s = ICON_SIZE;
120
+ const props = { width: s, height: s, viewBox: `0 0 ${s} ${s}`, fill: "none", style: { flexShrink: 0 } };
121
+ switch (type) {
122
+ // LLM / AI call — brain/sparkle
123
+ case "llm":
124
+ case "ai":
125
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
126
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "6", stroke: color, strokeWidth: "1.5" }),
127
+ /* @__PURE__ */ jsx2("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" }),
128
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "9.5", r: "1", fill: color }),
129
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "2", x2: "8", y2: "3.5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
130
+ /* @__PURE__ */ jsx2("line", { x1: "12.5", y1: "4", x2: "11.2", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
131
+ /* @__PURE__ */ jsx2("line", { x1: "3.5", y1: "4", x2: "4.8", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
132
+ ] });
133
+ // Tool / function call — gear
134
+ case "tool":
135
+ case "function":
136
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
137
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "3", stroke: color, strokeWidth: "1.5" }),
138
+ [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
139
+ const rad = angle * Math.PI / 180;
140
+ const x1 = 8 + Math.cos(rad) * 4.5;
141
+ const y1 = 8 + Math.sin(rad) * 4.5;
142
+ const x2 = 8 + Math.cos(rad) * 6;
143
+ const y2 = 8 + Math.sin(rad) * 6;
144
+ return /* @__PURE__ */ jsx2("line", { x1, y1, x2, y2, stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }, angle);
145
+ })
146
+ ] });
147
+ // RAG / retrieval — magnifying glass + doc
148
+ case "rag":
149
+ case "search":
150
+ case "retrieval":
151
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
152
+ /* @__PURE__ */ jsx2("circle", { cx: "7", cy: "7", r: "4", stroke: color, strokeWidth: "1.5" }),
153
+ /* @__PURE__ */ jsx2("line", { x1: "10", y1: "10", x2: "13.5", y2: "13.5", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }),
154
+ /* @__PURE__ */ jsx2("line", { x1: "5.5", y1: "6", x2: "8.5", y2: "6", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
155
+ /* @__PURE__ */ jsx2("line", { x1: "5.5", y1: "8", x2: "7.5", y2: "8", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
156
+ ] });
157
+ // Parse / process — diamond with arrows
158
+ case "parse":
159
+ case "process":
160
+ case "transform":
161
+ return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5", stroke: color, strokeWidth: "1.5", transform: "rotate(45 8 8)" }) });
162
+ // Start / seed — play triangle
163
+ case "start":
164
+ case "seed":
165
+ case "init":
166
+ return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M5 3.5L12.5 8L5 12.5V3.5Z", fill: color, opacity: "0.8" }) });
167
+ // End / finalize — stop square
168
+ case "end":
169
+ case "finalize":
170
+ case "output":
171
+ return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5", fill: color, opacity: "0.8" }) });
172
+ // Agent — person silhouette
173
+ case "agent":
174
+ case "orchestrator":
175
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
176
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "5", r: "2.5", stroke: color, strokeWidth: "1.5" }),
177
+ /* @__PURE__ */ jsx2("path", { d: "M3.5 14C3.5 11 5.5 9 8 9S12.5 11 12.5 14", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" })
178
+ ] });
179
+ // Swarm — multi-agent
180
+ case "swarm":
181
+ case "multi-agent":
182
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
183
+ /* @__PURE__ */ jsx2("circle", { cx: "5", cy: "5", r: "2", stroke: color, strokeWidth: "1.2" }),
184
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "5", r: "2", stroke: color, strokeWidth: "1.2" }),
185
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "11", r: "2", stroke: color, strokeWidth: "1.2" }),
186
+ /* @__PURE__ */ jsx2("line", { x1: "5", y1: "7", x2: "8", y2: "9", stroke: color, strokeWidth: "1", opacity: "0.5" }),
187
+ /* @__PURE__ */ jsx2("line", { x1: "11", y1: "7", x2: "8", y2: "9", stroke: color, strokeWidth: "1", opacity: "0.5" })
188
+ ] });
189
+ // Guard / guardrail — shield
190
+ case "guard":
191
+ case "guardrail":
192
+ case "validate":
193
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
194
+ /* @__PURE__ */ jsx2("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" }),
195
+ /* @__PURE__ */ jsx2("path", { d: "M6 8L7.5 9.5L10 6.5", stroke: color, strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
196
+ ] });
197
+ // Stream — wave
198
+ case "stream":
199
+ case "streaming":
200
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
201
+ /* @__PURE__ */ jsx2("path", { d: "M2 8C4 5 6 11 8 8S12 5 14 8", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", fill: "none" }),
202
+ /* @__PURE__ */ jsx2("path", { d: "M2 11C4 8 6 14 8 11S12 8 14 11", stroke: color, strokeWidth: "1", strokeLinecap: "round", fill: "none", opacity: "0.5" })
203
+ ] });
204
+ // Memory / state — database cylinder
205
+ case "memory":
206
+ case "state":
207
+ case "db":
208
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
209
+ /* @__PURE__ */ jsx2("ellipse", { cx: "8", cy: "4.5", rx: "5", ry: "2", stroke: color, strokeWidth: "1.3" }),
210
+ /* @__PURE__ */ jsx2("line", { x1: "3", y1: "4.5", x2: "3", y2: "11.5", stroke: color, strokeWidth: "1.3" }),
211
+ /* @__PURE__ */ jsx2("line", { x1: "13", y1: "4.5", x2: "13", y2: "11.5", stroke: color, strokeWidth: "1.3" }),
212
+ /* @__PURE__ */ jsx2("ellipse", { cx: "8", cy: "11.5", rx: "5", ry: "2", stroke: color, strokeWidth: "1.3" })
213
+ ] });
214
+ // Loop — circular arrow
215
+ case "loop":
216
+ case "retry":
217
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
218
+ /* @__PURE__ */ jsx2("path", { d: "M12 8A4 4 0 1 1 8 4", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", fill: "none" }),
219
+ /* @__PURE__ */ jsx2("path", { d: "M8 1.5L10.5 4L8 6.5", stroke: color, strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" })
220
+ ] });
221
+ // Lazy / service — cloud (deferred resolution, loaded on demand)
222
+ case "lazy":
223
+ case "service":
224
+ case "cloud":
225
+ return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2(
226
+ "path",
227
+ {
228
+ 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",
229
+ stroke: color,
230
+ strokeWidth: "1.3",
231
+ strokeLinecap: "round",
232
+ fill: "none"
233
+ }
234
+ ) });
235
+ // Decision — diamond (already handled by isDecider shape)
236
+ case "decision":
237
+ case "router":
238
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
239
+ /* @__PURE__ */ jsx2("path", { d: "M8 2L14 8L8 14L2 8Z", stroke: color, strokeWidth: "1.5", fill: "none" }),
240
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "1.5", fill: color })
241
+ ] });
242
+ default:
243
+ return null;
244
+ }
245
+ }
77
246
  var StageNode = memo(function StageNode2({
78
247
  data
79
248
  }) {
80
- const { label, active, done, error, linked, stepNumbers, dimmed, isSubflow, description } = data;
249
+ const { label, active, done, error, linked, icon, stepNumbers, dimmed, isSubflow, isLazy, isDecider, isFork, description } = data;
250
+ const effectiveIcon = icon || (isLazy ? "lazy" : void 0);
251
+ const isLazyUnresolved = isLazy && !done && !active;
252
+ const injectedRef = useRef(false);
253
+ useEffect2(() => {
254
+ if (injectedRef.current) return;
255
+ if (typeof document !== "undefined" && !document.getElementById(KEYFRAMES_ID)) {
256
+ const styleEl = document.createElement("style");
257
+ styleEl.id = KEYFRAMES_ID;
258
+ styleEl.textContent = KEYFRAMES_CSS;
259
+ document.head.appendChild(styleEl);
260
+ }
261
+ injectedRef.current = true;
262
+ }, []);
81
263
  const isOnPath = active || done;
82
264
  const bg = active ? theme.primary : done ? theme.success : error ? theme.error : theme.bgSecondary;
83
265
  const borderColor = active ? theme.primary : done ? theme.success : error ? theme.error : theme.border;
@@ -139,7 +321,8 @@ var StageNode = memo(function StageNode2({
139
321
  style: {
140
322
  position: "absolute",
141
323
  inset: -6,
142
- borderRadius: `calc(${theme.radius} + 4px)`,
324
+ borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
325
+ transform: isDecider ? "rotate(45deg)" : void 0,
143
326
  border: `2px solid ${theme.primary}`,
144
327
  opacity: 0.4,
145
328
  animation: "fp-pulse 2s ease-in-out infinite"
@@ -152,107 +335,175 @@ var StageNode = memo(function StageNode2({
152
335
  style: {
153
336
  position: "absolute",
154
337
  inset: -6,
155
- borderRadius: `calc(${theme.radius} + 4px)`,
338
+ borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
339
+ transform: isDecider ? "rotate(45deg)" : void 0,
156
340
  border: `2px solid ${theme.primary}`,
157
341
  opacity: 0.3,
158
342
  animation: "fp-pulse 1.5s ease-out infinite"
159
343
  }
160
344
  }
161
345
  ),
162
- /* @__PURE__ */ jsxs(
346
+ isDecider ? /* @__PURE__ */ jsx2(
163
347
  "div",
164
348
  {
165
349
  style: {
166
350
  background: bg,
167
- border: `2px solid ${borderColor}`,
168
- borderRadius: theme.radius,
169
- padding: description ? "8px 16px" : "10px 20px",
170
- display: "flex",
171
- flexDirection: "column",
172
- alignItems: "center",
173
- gap: description ? 2 : 0,
351
+ border: `2px ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
352
+ borderRadius: 4,
353
+ transform: "rotate(45deg)",
354
+ padding: 20,
174
355
  boxShadow: shadow,
175
356
  transition: "all 0.3s ease",
176
- fontFamily: theme.fontSans,
177
- minWidth: 100,
357
+ display: "flex",
358
+ alignItems: "center",
178
359
  justifyContent: "center"
179
360
  },
180
- children: [
181
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
182
- done && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2713" }),
183
- active && /* @__PURE__ */ jsx2(
184
- "span",
185
- {
186
- style: {
187
- width: 8,
188
- height: 8,
189
- borderRadius: "50%",
190
- background: "#fff",
191
- animation: "fp-blink 1s ease-in-out infinite",
192
- flexShrink: 0
361
+ children: /* @__PURE__ */ jsxs(
362
+ "div",
363
+ {
364
+ style: {
365
+ transform: "rotate(-45deg)",
366
+ display: "flex",
367
+ flexDirection: "column",
368
+ alignItems: "center",
369
+ gap: 2,
370
+ fontFamily: theme.fontSans
371
+ },
372
+ children: [
373
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [
374
+ icon && /* @__PURE__ */ jsx2(StageIcon, { type: icon, color: textColor }),
375
+ !icon && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u25C7" }),
376
+ /* @__PURE__ */ jsx2(
377
+ "span",
378
+ {
379
+ style: {
380
+ fontSize: 12,
381
+ fontWeight: 600,
382
+ color: textColor,
383
+ whiteSpace: "nowrap"
384
+ },
385
+ children: label
386
+ }
387
+ )
388
+ ] }),
389
+ description && /* @__PURE__ */ jsx2(
390
+ "span",
391
+ {
392
+ style: {
393
+ fontSize: 9,
394
+ fontWeight: 400,
395
+ color: textColor,
396
+ opacity: 0.7,
397
+ whiteSpace: "nowrap",
398
+ overflow: "hidden",
399
+ textOverflow: "ellipsis",
400
+ maxWidth: 130
401
+ },
402
+ children: description
193
403
  }
194
- }
195
- ),
196
- error && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2717" }),
197
- /* @__PURE__ */ jsx2(
404
+ )
405
+ ]
406
+ }
407
+ )
408
+ }
409
+ ) : (
410
+ /* Standard rectangular node */
411
+ /* @__PURE__ */ jsxs(
412
+ "div",
413
+ {
414
+ style: {
415
+ background: bg,
416
+ border: `2px ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
417
+ borderRadius: theme.radius,
418
+ padding: description ? "8px 16px" : "10px 20px",
419
+ display: "flex",
420
+ flexDirection: "column",
421
+ alignItems: "center",
422
+ gap: description ? 2 : 0,
423
+ boxShadow: shadow,
424
+ transition: "all 0.3s ease",
425
+ fontFamily: theme.fontSans,
426
+ minWidth: 100,
427
+ justifyContent: "center"
428
+ },
429
+ children: [
430
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
431
+ effectiveIcon && /* @__PURE__ */ jsx2(StageIcon, { type: effectiveIcon, color: textColor }),
432
+ done && !effectiveIcon && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2713" }),
433
+ active && !effectiveIcon && /* @__PURE__ */ jsx2(
434
+ "span",
435
+ {
436
+ style: {
437
+ width: 8,
438
+ height: 8,
439
+ borderRadius: "50%",
440
+ background: "#fff",
441
+ animation: "fp-blink 1s ease-in-out infinite",
442
+ flexShrink: 0
443
+ }
444
+ }
445
+ ),
446
+ error && !effectiveIcon && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2717" }),
447
+ /* @__PURE__ */ jsx2(
448
+ "span",
449
+ {
450
+ style: {
451
+ fontSize: 13,
452
+ fontWeight: 500,
453
+ color: textColor,
454
+ whiteSpace: "nowrap"
455
+ },
456
+ children: label
457
+ }
458
+ ),
459
+ isSubflow && /* @__PURE__ */ jsx2(
460
+ "span",
461
+ {
462
+ style: {
463
+ display: "inline-flex",
464
+ alignItems: "center",
465
+ justifyContent: "center",
466
+ width: 16,
467
+ height: 16,
468
+ borderRadius: 3,
469
+ border: `1.5px solid ${textColor}`,
470
+ position: "relative",
471
+ opacity: 0.7,
472
+ flexShrink: 0
473
+ },
474
+ children: /* @__PURE__ */ jsx2(
475
+ "span",
476
+ {
477
+ style: {
478
+ width: 8,
479
+ height: 8,
480
+ borderRadius: 2,
481
+ border: `1px solid ${textColor}`
482
+ }
483
+ }
484
+ )
485
+ }
486
+ )
487
+ ] }),
488
+ description && /* @__PURE__ */ jsx2(
198
489
  "span",
199
490
  {
200
491
  style: {
201
- fontSize: 13,
202
- fontWeight: 500,
492
+ fontSize: 10,
493
+ fontWeight: 400,
203
494
  color: textColor,
204
- whiteSpace: "nowrap"
205
- },
206
- children: label
207
- }
208
- ),
209
- isSubflow && /* @__PURE__ */ jsx2(
210
- "span",
211
- {
212
- style: {
213
- display: "inline-flex",
214
- alignItems: "center",
215
- justifyContent: "center",
216
- width: 16,
217
- height: 16,
218
- borderRadius: 3,
219
- border: `1.5px solid ${textColor}`,
220
- position: "relative",
221
495
  opacity: 0.7,
222
- flexShrink: 0
496
+ whiteSpace: "nowrap",
497
+ overflow: "hidden",
498
+ textOverflow: "ellipsis",
499
+ maxWidth: 160
223
500
  },
224
- children: /* @__PURE__ */ jsx2(
225
- "span",
226
- {
227
- style: {
228
- width: 8,
229
- height: 8,
230
- borderRadius: 2,
231
- border: `1px solid ${textColor}`
232
- }
233
- }
234
- )
501
+ children: description
235
502
  }
236
503
  )
237
- ] }),
238
- description && /* @__PURE__ */ jsx2(
239
- "span",
240
- {
241
- style: {
242
- fontSize: 10,
243
- fontWeight: 400,
244
- color: textColor,
245
- opacity: 0.7,
246
- whiteSpace: "nowrap",
247
- overflow: "hidden",
248
- textOverflow: "ellipsis",
249
- maxWidth: 160
250
- },
251
- children: description
252
- }
253
- )
254
- ]
255
- }
504
+ ]
505
+ }
506
+ )
256
507
  )
257
508
  ]
258
509
  }
@@ -393,23 +644,24 @@ function FlowchartView({
393
644
  }
394
645
 
395
646
  // src/components/FlowchartView/TracedFlowchartView.tsx
396
- import { useState as useState3, useMemo as useMemo4, useCallback as useCallback4 } from "react";
647
+ import { useMemo as useMemo2, useCallback as useCallback2, useEffect as useEffect3 } from "react";
397
648
  import {
398
649
  ReactFlow as ReactFlow2,
399
650
  Background as Background2,
400
- BackgroundVariant as BackgroundVariant2
651
+ BackgroundVariant as BackgroundVariant2,
652
+ useReactFlow
401
653
  } from "@xyflow/react";
402
654
 
403
655
  // src/components/FlowchartView/specToReactFlow.ts
404
656
  var DEFAULT_COLORS = {
405
- edgeDefault: defaultTokens.colors.textMuted,
406
- edgeExecuted: defaultTokens.colors.success,
407
- edgeActive: defaultTokens.colors.primary,
408
- edgeLoop: defaultTokens.colors.warning,
409
- labelDefault: defaultTokens.colors.textSecondary,
410
- labelExecuted: defaultTokens.colors.success,
411
- labelLoop: defaultTokens.colors.warning,
412
- pathGlow: `${defaultTokens.colors.success}4D`
657
+ edgeDefault: rawDefaults.colors.textMuted,
658
+ edgeExecuted: rawDefaults.colors.success,
659
+ edgeActive: rawDefaults.colors.primary,
660
+ edgeLoop: rawDefaults.colors.warning,
661
+ labelDefault: rawDefaults.colors.textSecondary,
662
+ labelExecuted: rawDefaults.colors.success,
663
+ labelLoop: rawDefaults.colors.warning,
664
+ pathGlow: `${rawDefaults.colors.success}4D`
413
665
  // ~30% opacity hex
414
666
  };
415
667
  var Y_STEP = 100;
@@ -417,122 +669,33 @@ var X_SPREAD = 200;
417
669
  function nid(n) {
418
670
  return n.name || n.id || `spec-${Math.random()}`;
419
671
  }
420
- function addEdge(state, source, target, label, isLoop) {
421
- state.edgeCounter++;
422
- const o = state.overlay;
423
- const c = state.colors;
424
- const executed = o && o.executedStages.has(source) && o.executedStages.has(target);
425
- const isLeadingEdge = o && source === o.activeStage && !o.doneStages.has(target);
426
- if (isLoop) {
427
- let loopExecuted = false;
428
- if (o?.executionOrder) {
429
- const lastSourceIdx = o.executionOrder.lastIndexOf(source);
430
- if (lastSourceIdx >= 0) {
431
- loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(target);
432
- }
433
- }
434
- state.edges.push({
435
- id: `se${state.edgeCounter}`,
436
- source,
437
- target,
438
- sourceHandle: "loop-source",
439
- targetHandle: "loop-target",
440
- label: label ?? "loop",
441
- type: "smoothstep",
442
- pathOptions: { offset: 40, borderRadius: 16 },
443
- style: {
444
- stroke: c.edgeLoop,
445
- strokeWidth: loopExecuted ? 3 : 2,
446
- strokeDasharray: "6 3",
447
- opacity: o && !loopExecuted ? 0.35 : 1
448
- },
449
- labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
450
- animated: loopExecuted,
451
- zIndex: 2
452
- });
453
- return;
454
- }
455
- if (executed) {
456
- state.edges.push({
457
- id: `se${state.edgeCounter}-glow`,
458
- source,
459
- target,
460
- style: {
461
- stroke: c.pathGlow,
462
- strokeWidth: 8,
463
- opacity: 0.4
464
- },
465
- zIndex: 0,
466
- selectable: false,
467
- focusable: false
468
- });
469
- state.edges.push({
470
- id: `se${state.edgeCounter}`,
471
- source,
472
- target,
473
- label,
474
- style: {
475
- stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
476
- strokeWidth: 3.5
477
- },
478
- labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
479
- animated: !!isLeadingEdge,
480
- zIndex: 1
481
- });
482
- } else {
483
- state.edges.push({
484
- id: `se${state.edgeCounter}`,
485
- source,
486
- target,
487
- label,
488
- style: {
489
- stroke: c.edgeDefault,
490
- strokeWidth: 1.5,
491
- opacity: o ? 0.3 : 1
492
- },
493
- labelStyle: { fontSize: 10, fill: c.labelDefault }
494
- });
672
+ function registerNode(state, node) {
673
+ if (node.id && node.name) {
674
+ state.idToName.set(node.id, node.name);
495
675
  }
496
676
  }
497
- function walk(node, state, x, y) {
677
+ function walkLayout(node, state, x, y) {
678
+ if (!node) return { lastIds: [], bottomY: y };
679
+ registerNode(state, node);
498
680
  const id = nid(node);
499
681
  if (state.seen.has(id)) {
500
682
  return { lastIds: [id], bottomY: y };
501
683
  }
502
684
  state.seen.add(id);
503
- const isDecider = node.type === "decider" || node.hasDecider;
685
+ const isDecider = node.type === "decider" || !!node.hasDecider;
504
686
  const isFork = node.type === "fork";
505
- const o = state.overlay;
506
- const isDone = o ? o.doneStages.has(id) : false;
507
- const isActive = o ? o.activeStage === id : false;
508
- const wasExecuted = o ? o.executedStages.has(id) : false;
509
- const dimmed = o && !wasExecuted;
510
- let stepNumbers;
511
- if (o?.executionOrder) {
512
- const nums = [];
513
- for (let i = 0; i < o.executionOrder.length; i++) {
514
- if (o.executionOrder[i] === id) nums.push(i + 1);
515
- }
516
- if (nums.length > 0) stepNumbers = nums;
517
- }
518
687
  state.nodes.push({
519
688
  id,
520
- position: { x, y },
521
- data: {
522
- label: node.name,
523
- active: isActive,
524
- done: isDone,
525
- error: false,
526
- isDecider,
527
- isFork,
528
- description: node.description,
529
- subflowId: node.subflowId,
530
- dimmed,
531
- stepNumbers,
532
- isSubflow: !!node.isSubflowRoot
533
- },
534
- type: "stage",
535
- style: dimmed ? { opacity: 0.35 } : void 0
689
+ x,
690
+ y,
691
+ label: node.name,
692
+ isDecider,
693
+ isFork,
694
+ description: node.description,
695
+ icon: node.icon,
696
+ subflowId: node.subflowId,
697
+ isSubflow: !!node.isSubflowRoot,
698
+ isLazy: node.isLazy
536
699
  });
537
700
  let lastIds = [id];
538
701
  let bottomY = y;
@@ -543,120 +706,257 @@ function walk(node, state, x, y) {
543
706
  const childResults = [];
544
707
  for (let i = 0; i < node.children.length; i++) {
545
708
  const child = node.children[i];
709
+ if (!child) continue;
546
710
  const childX = startX + i * X_SPREAD;
547
711
  const edgeLabel = node.branchIds?.[i];
548
- addEdge(state, id, nid(child), edgeLabel);
549
- const result = walk(child, state, childX, childY);
712
+ state.edgeCounter++;
713
+ state.edges.push({ id: `se${state.edgeCounter}`, source: id, target: nid(child), label: edgeLabel, isLoop: false });
714
+ const result = walkLayout(child, state, childX, childY);
550
715
  childResults.push(result);
551
716
  }
552
717
  lastIds = childResults.flatMap((r) => r.lastIds);
553
718
  bottomY = Math.max(...childResults.map((r) => r.bottomY));
554
719
  }
555
720
  if (node.loopTarget) {
556
- addEdge(state, id, node.loopTarget, "loop", true);
721
+ const resolvedTarget = state.idToName.get(node.loopTarget) ?? node.loopTarget;
722
+ state.edgeCounter++;
723
+ state.edges.push({ id: `se${state.edgeCounter}`, source: id, target: resolvedTarget, label: "loop", isLoop: true });
557
724
  }
558
725
  if (node.next) {
726
+ const rawNextId = nid(node.next);
727
+ const resolvedNextId = state.idToName.get(rawNextId) ?? rawNextId;
728
+ const isLoopRef = node.loopTarget && state.seen.has(resolvedNextId);
729
+ if (isLoopRef) {
730
+ return { lastIds, bottomY };
731
+ }
559
732
  const nextY = bottomY + Y_STEP;
560
- const nextId = nid(node.next);
561
733
  for (const lid of lastIds) {
562
- if (node.loopTarget && lid === id && node.loopTarget === nextId) continue;
563
- addEdge(state, lid, nextId);
734
+ state.edgeCounter++;
735
+ state.edges.push({ id: `se${state.edgeCounter}`, source: lid, target: resolvedNextId, isLoop: false });
564
736
  }
565
- const result = walk(node.next, state, x, nextY);
566
- return result;
737
+ return walkLayout(node.next, state, x, nextY);
567
738
  }
568
739
  return { lastIds, bottomY };
569
740
  }
570
- function specToReactFlow(spec, overlay, colors) {
741
+ function specToLayout(spec) {
571
742
  const state = {
572
743
  nodes: [],
573
744
  edges: [],
574
745
  edgeCounter: 0,
575
746
  seen: /* @__PURE__ */ new Set(),
576
- overlay: overlay ?? null,
577
- colors: { ...DEFAULT_COLORS, ...colors }
747
+ idToName: /* @__PURE__ */ new Map()
578
748
  };
579
- walk(spec, state, 300, 0);
580
- return { nodes: state.nodes, edges: state.edges };
749
+ walkLayout(spec, state, 300, 0);
750
+ return { nodes: state.nodes, edges: state.edges, idToName: state.idToName };
581
751
  }
582
-
583
- // src/components/FlowchartView/useSubflowNavigation.ts
584
- import { useState, useCallback as useCallback2, useMemo as useMemo2 } from "react";
585
- function useSubflowNavigation(rootSpec, overlay, colors) {
586
- const [stack, setStack] = useState([]);
587
- const currentSpec = stack.length > 0 ? stack[stack.length - 1].spec : rootSpec;
588
- const { nodes, edges } = useMemo2(() => {
589
- if (!currentSpec) return { nodes: [], edges: [] };
590
- return specToReactFlow(currentSpec, overlay, colors);
591
- }, [currentSpec, overlay, colors]);
592
- const subflowMap = useMemo2(() => {
593
- const map = /* @__PURE__ */ new Map();
594
- if (!currentSpec) return map;
595
- function collectSubflows(node) {
596
- if (node.isSubflowRoot && node.subflowStructure) {
597
- const id = node.name || node.id || "";
598
- map.set(id, node);
752
+ function applyOverlay(layout, overlay, colors) {
753
+ const c = { ...DEFAULT_COLORS, ...colors };
754
+ const o = overlay ?? null;
755
+ const nodes = layout.nodes.map((ln) => {
756
+ const isDone = o ? o.doneStages.has(ln.id) : false;
757
+ const isActive = o ? o.activeStage === ln.id : false;
758
+ const wasExecuted = o ? o.executedStages.has(ln.id) : false;
759
+ const dimmed = o && !wasExecuted;
760
+ let stepNumbers;
761
+ if (o?.executionOrder) {
762
+ const nums = [];
763
+ for (let i = 0; i < o.executionOrder.length; i++) {
764
+ if (o.executionOrder[i] === ln.id) nums.push(i + 1);
599
765
  }
600
- if (node.children) node.children.forEach(collectSubflows);
601
- if (node.next) collectSubflows(node.next);
766
+ if (nums.length > 0) stepNumbers = nums;
602
767
  }
603
- collectSubflows(currentSpec);
604
- return map;
605
- }, [currentSpec]);
606
- const breadcrumbs = useMemo2(() => {
607
- const root = {
608
- label: rootSpec?.name || "Flowchart",
609
- spec: rootSpec,
610
- description: rootSpec?.description
768
+ return {
769
+ id: ln.id,
770
+ position: { x: ln.x, y: ln.y },
771
+ data: {
772
+ label: ln.label,
773
+ active: isActive,
774
+ done: isDone,
775
+ error: false,
776
+ isDecider: ln.isDecider,
777
+ isFork: ln.isFork,
778
+ description: ln.description,
779
+ icon: ln.icon,
780
+ subflowId: ln.subflowId,
781
+ dimmed,
782
+ stepNumbers,
783
+ isSubflow: ln.isSubflow,
784
+ isLazy: ln.isLazy
785
+ },
786
+ type: "stage",
787
+ style: dimmed ? { opacity: 0.35 } : void 0
611
788
  };
612
- return [root, ...stack];
613
- }, [rootSpec, stack]);
614
- const handleNodeClick = useCallback2(
615
- (nodeId) => {
616
- const subflowNode = subflowMap.get(nodeId);
617
- if (!subflowNode?.subflowStructure) return false;
618
- setStack((prev) => [
619
- ...prev,
620
- {
621
- label: subflowNode.subflowName || subflowNode.name,
622
- spec: subflowNode.subflowStructure,
623
- description: subflowNode.description
789
+ });
790
+ const edges = [];
791
+ for (const le of layout.edges) {
792
+ const executed = o && o.executedStages.has(le.source) && o.executedStages.has(le.target);
793
+ const isLeadingEdge = o && le.source === o.activeStage && !o.doneStages.has(le.target);
794
+ if (le.isLoop) {
795
+ let loopExecuted = false;
796
+ if (o?.executionOrder) {
797
+ const lastSourceIdx = o.executionOrder.lastIndexOf(le.source);
798
+ if (lastSourceIdx >= 0) {
799
+ loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(le.target);
624
800
  }
625
- ]);
626
- return true;
627
- },
628
- [subflowMap]
629
- );
630
- const navigateTo = useCallback2(
631
- (level) => {
632
- if (level === 0) {
633
- setStack([]);
634
- } else {
635
- setStack((prev) => prev.slice(0, level));
636
801
  }
802
+ edges.push({
803
+ id: le.id,
804
+ source: le.source,
805
+ target: le.target,
806
+ sourceHandle: "loop-source",
807
+ targetHandle: "loop-target",
808
+ label: le.label ?? "loop",
809
+ type: "smoothstep",
810
+ pathOptions: { offset: 40, borderRadius: 16 },
811
+ style: {
812
+ stroke: c.edgeLoop,
813
+ strokeWidth: loopExecuted ? 3 : 2,
814
+ strokeDasharray: "6 3",
815
+ opacity: o && !loopExecuted ? 0.35 : 1
816
+ },
817
+ labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
818
+ animated: loopExecuted,
819
+ zIndex: 2
820
+ });
821
+ } else if (executed) {
822
+ edges.push({
823
+ id: `${le.id}-glow`,
824
+ source: le.source,
825
+ target: le.target,
826
+ style: { stroke: c.pathGlow, strokeWidth: 8, opacity: 0.4 },
827
+ zIndex: 0,
828
+ selectable: false,
829
+ focusable: false
830
+ });
831
+ edges.push({
832
+ id: le.id,
833
+ source: le.source,
834
+ target: le.target,
835
+ label: le.label,
836
+ style: {
837
+ stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
838
+ strokeWidth: 3.5
839
+ },
840
+ labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
841
+ animated: !!isLeadingEdge,
842
+ zIndex: 1
843
+ });
844
+ } else {
845
+ edges.push({
846
+ id: le.id,
847
+ source: le.source,
848
+ target: le.target,
849
+ label: le.label,
850
+ style: {
851
+ stroke: c.edgeDefault,
852
+ strokeWidth: 1.5,
853
+ opacity: o ? 0.3 : 1
854
+ },
855
+ labelStyle: { fontSize: 10, fill: c.labelDefault }
856
+ });
857
+ }
858
+ }
859
+ return { nodes, edges };
860
+ }
861
+ function specToReactFlow(spec, overlay, colors) {
862
+ const layout = specToLayout(spec);
863
+ return applyOverlay(layout, overlay, colors);
864
+ }
865
+
866
+ // src/components/FlowchartView/TracedFlowchartView.tsx
867
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
868
+ var defaultNodeTypes = { stage: StageNode };
869
+ function FitViewOnResize() {
870
+ const { fitView } = useReactFlow();
871
+ useEffect3(() => {
872
+ const handler = () => {
873
+ requestAnimationFrame(() => fitView({ padding: 0.3 }));
874
+ };
875
+ window.addEventListener("resize", handler);
876
+ return () => window.removeEventListener("resize", handler);
877
+ }, [fitView]);
878
+ return null;
879
+ }
880
+ function TracedFlowchartView({
881
+ spec,
882
+ snapshots,
883
+ snapshotIndex = 0,
884
+ onNodeClick,
885
+ nodeTypes: customNodeTypes,
886
+ unstyled = false,
887
+ className,
888
+ style
889
+ }) {
890
+ const nodeTypes2 = customNodeTypes ?? defaultNodeTypes;
891
+ const overlay = useMemo2(() => {
892
+ if (!snapshots || snapshots.length === 0) return void 0;
893
+ const executionOrder = snapshots.slice(0, snapshotIndex + 1).map((s) => s.stageLabel);
894
+ const doneStages = new Set(
895
+ snapshots.slice(0, snapshotIndex).map((s) => s.stageLabel)
896
+ );
897
+ const activeStage = snapshots[snapshotIndex]?.stageLabel ?? null;
898
+ const executedStages = /* @__PURE__ */ new Set([...doneStages]);
899
+ if (activeStage) executedStages.add(activeStage);
900
+ return { doneStages, activeStage, executedStages, executionOrder };
901
+ }, [snapshots, snapshotIndex]);
902
+ const layout = useMemo2(() => {
903
+ if (!spec) return null;
904
+ return specToLayout(spec);
905
+ }, [spec]);
906
+ const { nodes, edges } = useMemo2(() => {
907
+ if (!layout) return { nodes: [], edges: [] };
908
+ return applyOverlay(layout, overlay);
909
+ }, [layout, overlay]);
910
+ const handleNodeClick = useCallback2(
911
+ (_, node) => {
912
+ if (!onNodeClick) return;
913
+ onNodeClick(node.id);
637
914
  },
638
- []
915
+ [onNodeClick]
916
+ );
917
+ return /* @__PURE__ */ jsx4(
918
+ "div",
919
+ {
920
+ className,
921
+ style: { width: "100%", height: "100%", ...style },
922
+ "data-fp": "traced-flowchart",
923
+ children: /* @__PURE__ */ jsxs2(
924
+ ReactFlow2,
925
+ {
926
+ nodes,
927
+ edges,
928
+ onNodeClick: handleNodeClick,
929
+ nodeTypes: nodeTypes2,
930
+ fitView: true,
931
+ fitViewOptions: { padding: 0.3 },
932
+ proOptions: { hideAttribution: true },
933
+ panOnDrag: false,
934
+ zoomOnScroll: false,
935
+ zoomOnPinch: false,
936
+ zoomOnDoubleClick: false,
937
+ preventScrolling: false,
938
+ nodesDraggable: false,
939
+ nodesConnectable: false,
940
+ elementsSelectable: !!onNodeClick,
941
+ children: [
942
+ /* @__PURE__ */ jsx4(FitViewOnResize, {}),
943
+ !unstyled && /* @__PURE__ */ jsx4(Background2, { variant: BackgroundVariant2.Dots, gap: 16, size: 1 })
944
+ ]
945
+ }
946
+ )
947
+ }
639
948
  );
640
- return {
641
- breadcrumbs,
642
- nodes,
643
- edges,
644
- handleNodeClick,
645
- navigateTo,
646
- isInSubflow: stack.length > 0,
647
- currentSubflowNodeName: stack.length > 0 ? stack[stack.length - 1].label : null
648
- };
649
949
  }
650
950
 
651
951
  // src/components/FlowchartView/SubflowBreadcrumb.tsx
652
952
  import { memo as memo2 } from "react";
653
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
953
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
654
954
  var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
655
955
  breadcrumbs,
656
956
  onNavigate
657
957
  }) {
658
958
  if (breadcrumbs.length <= 1) return null;
659
- return /* @__PURE__ */ jsx4(
959
+ return /* @__PURE__ */ jsx5(
660
960
  "div",
661
961
  {
662
962
  style: {
@@ -673,10 +973,10 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
673
973
  },
674
974
  children: breadcrumbs.map((crumb, i) => {
675
975
  const isLast = i === breadcrumbs.length - 1;
676
- return /* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
677
- i > 0 && /* @__PURE__ */ jsx4("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
678
- isLast ? /* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
679
- /* @__PURE__ */ jsx4(
976
+ return /* @__PURE__ */ jsxs3("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
977
+ i > 0 && /* @__PURE__ */ jsx5("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
978
+ isLast ? /* @__PURE__ */ jsxs3("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
979
+ /* @__PURE__ */ jsx5(
680
980
  "span",
681
981
  {
682
982
  style: {
@@ -686,7 +986,7 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
686
986
  children: crumb.label
687
987
  }
688
988
  ),
689
- crumb.description && /* @__PURE__ */ jsxs2(
989
+ crumb.description && /* @__PURE__ */ jsxs3(
690
990
  "span",
691
991
  {
692
992
  style: {
@@ -700,7 +1000,7 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
700
1000
  ]
701
1001
  }
702
1002
  )
703
- ] }) : /* @__PURE__ */ jsx4(
1003
+ ] }) : /* @__PURE__ */ jsx5(
704
1004
  "button",
705
1005
  {
706
1006
  onClick: () => onNavigate(i),
@@ -731,13 +1031,83 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
731
1031
  );
732
1032
  });
733
1033
 
1034
+ // src/components/FlowchartView/useSubflowNavigation.ts
1035
+ import { useState as useState2, useCallback as useCallback3, useMemo as useMemo3 } from "react";
1036
+ function useSubflowNavigation(rootSpec, overlay, colors) {
1037
+ const [stack, setStack] = useState2([]);
1038
+ const currentSpec = stack.length > 0 ? stack[stack.length - 1].spec : rootSpec;
1039
+ const { nodes, edges } = useMemo3(() => {
1040
+ if (!currentSpec) return { nodes: [], edges: [] };
1041
+ return specToReactFlow(currentSpec, overlay, colors);
1042
+ }, [currentSpec, overlay, colors]);
1043
+ const subflowMap = useMemo3(() => {
1044
+ const map = /* @__PURE__ */ new Map();
1045
+ if (!currentSpec) return map;
1046
+ function collectSubflows(node) {
1047
+ if (node.isSubflowRoot && node.subflowStructure) {
1048
+ const id = node.name || node.id || "";
1049
+ map.set(id, node);
1050
+ }
1051
+ if (node.children) node.children.forEach(collectSubflows);
1052
+ if (node.next) collectSubflows(node.next);
1053
+ }
1054
+ collectSubflows(currentSpec);
1055
+ return map;
1056
+ }, [currentSpec]);
1057
+ const breadcrumbs = useMemo3(() => {
1058
+ const root = {
1059
+ label: rootSpec?.name || "Flowchart",
1060
+ spec: rootSpec,
1061
+ description: rootSpec?.description
1062
+ };
1063
+ return [root, ...stack];
1064
+ }, [rootSpec, stack]);
1065
+ const handleNodeClick = useCallback3(
1066
+ (nodeId) => {
1067
+ const subflowNode = subflowMap.get(nodeId);
1068
+ if (!subflowNode?.subflowStructure) return false;
1069
+ setStack((prev) => [
1070
+ ...prev,
1071
+ {
1072
+ label: subflowNode.subflowName || subflowNode.name,
1073
+ spec: subflowNode.subflowStructure,
1074
+ description: subflowNode.description
1075
+ }
1076
+ ]);
1077
+ return true;
1078
+ },
1079
+ [subflowMap]
1080
+ );
1081
+ const navigateTo = useCallback3(
1082
+ (level) => {
1083
+ if (level === 0) {
1084
+ setStack([]);
1085
+ } else {
1086
+ setStack((prev) => prev.slice(0, level));
1087
+ }
1088
+ },
1089
+ []
1090
+ );
1091
+ return {
1092
+ breadcrumbs,
1093
+ nodes,
1094
+ edges,
1095
+ handleNodeClick,
1096
+ navigateTo,
1097
+ isInSubflow: stack.length > 0,
1098
+ currentSubflowNodeName: stack.length > 0 ? stack[stack.length - 1].label : null
1099
+ };
1100
+ }
1101
+
734
1102
  // src/components/FlowchartView/SubflowTree.tsx
735
- import { memo as memo3, useState as useState2, useCallback as useCallback3, useMemo as useMemo3 } from "react";
736
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1103
+ import { memo as memo3, useState as useState3, useCallback as useCallback4, useMemo as useMemo4 } from "react";
1104
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
737
1105
  function specToTree(node) {
1106
+ if (!node) return [];
738
1107
  const entries = [];
739
1108
  const seen = /* @__PURE__ */ new Set();
740
- function walk2(n) {
1109
+ function walk(n) {
1110
+ if (!n) return;
741
1111
  const id = n.name || n.id || "";
742
1112
  if (seen.has(id)) return;
743
1113
  seen.add(id);
@@ -753,14 +1123,14 @@ function specToTree(node) {
753
1123
  entries.push(entry);
754
1124
  if (n.children) {
755
1125
  for (const child of n.children) {
756
- walk2(child);
1126
+ if (child) walk(child);
757
1127
  }
758
1128
  }
759
1129
  if (n.next) {
760
- walk2(n.next);
1130
+ walk(n.next);
761
1131
  }
762
1132
  }
763
- walk2(node);
1133
+ walk(node);
764
1134
  return entries;
765
1135
  }
766
1136
  var TreeNode = memo3(function TreeNode2({
@@ -770,18 +1140,18 @@ var TreeNode = memo3(function TreeNode2({
770
1140
  doneStages,
771
1141
  onNodeSelect
772
1142
  }) {
773
- const [expanded, setExpanded] = useState2(true);
1143
+ const [expanded, setExpanded] = useState3(true);
774
1144
  const hasChildren = entry.children && entry.children.length > 0;
775
1145
  const isActive = activeStage === entry.name;
776
1146
  const isDone = doneStages?.has(entry.name);
777
- const handleClick = useCallback3(() => {
1147
+ const handleClick = useCallback4(() => {
778
1148
  if (hasChildren) {
779
1149
  setExpanded((prev) => !prev);
780
1150
  }
781
1151
  onNodeSelect?.(entry.name, !!entry.isSubflow);
782
1152
  }, [hasChildren, onNodeSelect, entry.name, entry.isSubflow]);
783
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
784
- /* @__PURE__ */ jsxs3(
1153
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1154
+ /* @__PURE__ */ jsxs4(
785
1155
  "button",
786
1156
  {
787
1157
  onClick: handleClick,
@@ -812,7 +1182,7 @@ var TreeNode = memo3(function TreeNode2({
812
1182
  }
813
1183
  },
814
1184
  children: [
815
- hasChildren ? /* @__PURE__ */ jsx5(
1185
+ hasChildren ? /* @__PURE__ */ jsx6(
816
1186
  "span",
817
1187
  {
818
1188
  style: {
@@ -827,8 +1197,8 @@ var TreeNode = memo3(function TreeNode2({
827
1197
  },
828
1198
  children: "\u25B6"
829
1199
  }
830
- ) : /* @__PURE__ */ jsx5("span", { style: { width: 12, flexShrink: 0 } }),
831
- /* @__PURE__ */ jsx5(
1200
+ ) : /* @__PURE__ */ jsx6("span", { style: { width: 12, flexShrink: 0 } }),
1201
+ /* @__PURE__ */ jsx6(
832
1202
  "span",
833
1203
  {
834
1204
  style: {
@@ -840,8 +1210,8 @@ var TreeNode = memo3(function TreeNode2({
840
1210
  }
841
1211
  }
842
1212
  ),
843
- /* @__PURE__ */ jsxs3("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
844
- /* @__PURE__ */ jsxs3(
1213
+ /* @__PURE__ */ jsxs4("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1214
+ /* @__PURE__ */ jsxs4(
845
1215
  "span",
846
1216
  {
847
1217
  style: {
@@ -853,11 +1223,11 @@ var TreeNode = memo3(function TreeNode2({
853
1223
  },
854
1224
  children: [
855
1225
  entry.name,
856
- entry.isSubflow && /* @__PURE__ */ jsx5("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
1226
+ entry.isSubflow && /* @__PURE__ */ jsx6("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
857
1227
  ]
858
1228
  }
859
1229
  ),
860
- entry.description && /* @__PURE__ */ jsx5(
1230
+ entry.description && /* @__PURE__ */ jsx6(
861
1231
  "span",
862
1232
  {
863
1233
  style: {
@@ -874,7 +1244,7 @@ var TreeNode = memo3(function TreeNode2({
874
1244
  ]
875
1245
  }
876
1246
  ),
877
- hasChildren && expanded && /* @__PURE__ */ jsx5("div", { children: entry.children.map((child, i) => /* @__PURE__ */ jsx5(
1247
+ hasChildren && expanded && /* @__PURE__ */ jsx6("div", { children: entry.children.map((child, i) => /* @__PURE__ */ jsx6(
878
1248
  TreeNode2,
879
1249
  {
880
1250
  entry: child,
@@ -883,12 +1253,12 @@ var TreeNode = memo3(function TreeNode2({
883
1253
  doneStages,
884
1254
  onNodeSelect
885
1255
  },
886
- `${child.name}-${i}`
1256
+ child.subflowId ?? `${child.name}-${i}`
887
1257
  )) })
888
1258
  ] });
889
1259
  });
890
1260
  var SectionLabel = memo3(function SectionLabel2({ children }) {
891
- return /* @__PURE__ */ jsx5(
1261
+ return /* @__PURE__ */ jsx6(
892
1262
  "div",
893
1263
  {
894
1264
  style: {
@@ -912,21 +1282,10 @@ var SubflowTree = memo3(function SubflowTree2({
912
1282
  className,
913
1283
  style
914
1284
  }) {
915
- const tree = useMemo3(() => specToTree(spec), [spec]);
916
- const mainStages = useMemo3(() => tree.filter((e) => !e.isSubflow), [tree]);
917
- const subflowStages = useMemo3(() => tree.filter((e) => e.isSubflow), [tree]);
918
- const renderEntries = (entries) => entries.map((entry, i) => /* @__PURE__ */ jsx5(
919
- TreeNode,
920
- {
921
- entry,
922
- depth: 0,
923
- activeStage,
924
- doneStages,
925
- onNodeSelect
926
- },
927
- `${entry.name}-${i}`
928
- ));
929
- return /* @__PURE__ */ jsxs3(
1285
+ const tree = useMemo4(() => specToTree(spec), [spec]);
1286
+ const subflowStages = useMemo4(() => tree.filter((e) => e.isSubflow), [tree]);
1287
+ if (subflowStages.length === 0) return null;
1288
+ return /* @__PURE__ */ jsxs4(
930
1289
  "div",
931
1290
  {
932
1291
  className,
@@ -944,185 +1303,28 @@ var SubflowTree = memo3(function SubflowTree2({
944
1303
  ...style
945
1304
  },
946
1305
  children: [
947
- !unstyled && /* @__PURE__ */ jsx5(SectionLabel, { children: "Flowchart" }),
948
- renderEntries(mainStages),
949
- subflowStages.length > 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
950
- !unstyled && /* @__PURE__ */ jsx5("div", { style: { height: 1, background: theme.border, margin: "8px 12px" } }),
951
- !unstyled && /* @__PURE__ */ jsx5(SectionLabel, { children: "Subflows" }),
952
- renderEntries(subflowStages)
953
- ] })
954
- ]
955
- }
956
- );
957
- });
958
-
959
- // src/components/FlowchartView/TracedFlowchartView.tsx
960
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
961
- var nodeTypes2 = { stage: StageNode };
962
- function TracedFlowchartView({
963
- spec,
964
- snapshots,
965
- snapshotIndex = 0,
966
- onNodeClick,
967
- onSubflowChange,
968
- showTree = false,
969
- treeWidth = 200,
970
- unstyled = false,
971
- className,
972
- style
973
- }) {
974
- const [treeVisible, setTreeVisible] = useState3(showTree);
975
- const subflowNav = useSubflowNavigation(spec);
976
- const currentSpec = subflowNav.breadcrumbs.length > 0 ? subflowNav.breadcrumbs[subflowNav.breadcrumbs.length - 1].spec : null;
977
- const overlay = useMemo4(() => {
978
- if (!snapshots || snapshots.length === 0) return void 0;
979
- const executionOrder = snapshots.slice(0, snapshotIndex + 1).map((s) => s.stageLabel);
980
- const doneStages = new Set(
981
- snapshots.slice(0, snapshotIndex).map((s) => s.stageLabel)
982
- );
983
- const activeStage = snapshots[snapshotIndex]?.stageLabel ?? null;
984
- const executedStages = /* @__PURE__ */ new Set([...doneStages]);
985
- if (activeStage) executedStages.add(activeStage);
986
- return { doneStages, activeStage, executedStages, executionOrder };
987
- }, [snapshots, snapshotIndex]);
988
- const { nodes, edges } = useMemo4(() => {
989
- if (!currentSpec) return { nodes: [], edges: [] };
990
- return specToReactFlow(currentSpec, overlay);
991
- }, [currentSpec, overlay]);
992
- const handleNodeClick = useCallback4(
993
- (_, node) => {
994
- if (subflowNav.handleNodeClick(node.id)) {
995
- onSubflowChange?.(true, node.id);
996
- return;
997
- }
998
- if (onNodeClick && snapshots) {
999
- const idx = snapshots.findIndex((s) => s.stageLabel === node.id);
1000
- if (idx >= 0) onNodeClick(idx);
1001
- } else if (onNodeClick) {
1002
- onNodeClick(node.id);
1003
- }
1004
- },
1005
- [subflowNav, onNodeClick, onSubflowChange, snapshots]
1006
- );
1007
- const handleBreadcrumbNavigate = useCallback4(
1008
- (level) => {
1009
- subflowNav.navigateTo(level);
1010
- onSubflowChange?.(level > 0, null);
1011
- },
1012
- [subflowNav, onSubflowChange]
1013
- );
1014
- const handleTreeNodeSelect = useCallback4(
1015
- (name, isSubflow) => {
1016
- if (isSubflow) {
1017
- if (subflowNav.handleNodeClick(name)) {
1018
- onSubflowChange?.(true, name);
1019
- }
1020
- } else if (onNodeClick && snapshots) {
1021
- const idx = snapshots.findIndex((s) => s.stageLabel === name);
1022
- if (idx >= 0) onNodeClick(idx);
1023
- }
1024
- },
1025
- [subflowNav, onNodeClick, onSubflowChange, snapshots]
1026
- );
1027
- return /* @__PURE__ */ jsxs4(
1028
- "div",
1029
- {
1030
- className,
1031
- style: { width: "100%", height: "100%", display: "flex", flexDirection: "row", ...style },
1032
- "data-fp": "traced-flowchart",
1033
- children: [
1034
- showTree && treeVisible && /* @__PURE__ */ jsx6(
1035
- SubflowTree,
1306
+ !unstyled && /* @__PURE__ */ jsx6(SectionLabel, { children: "Subflows" }),
1307
+ subflowStages.map((entry, i) => /* @__PURE__ */ jsx6(
1308
+ TreeNode,
1036
1309
  {
1037
- spec,
1038
- activeStage: overlay?.activeStage,
1039
- doneStages: overlay?.doneStages,
1040
- onNodeSelect: handleTreeNodeSelect,
1041
- unstyled,
1042
- style: { width: treeWidth, flexShrink: 0, height: "100%" }
1043
- }
1044
- ),
1045
- /* @__PURE__ */ jsxs4("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0, height: "100%" }, children: [
1046
- (subflowNav.isInSubflow || showTree && !treeVisible) && /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "stretch", flexShrink: 0 }, children: [
1047
- showTree && !treeVisible && /* @__PURE__ */ jsx6(
1048
- "button",
1049
- {
1050
- onClick: () => setTreeVisible(true),
1051
- "data-fp": "tree-toggle",
1052
- style: unstyled ? {} : {
1053
- background: "transparent",
1054
- border: "none",
1055
- cursor: "pointer",
1056
- padding: "6px 8px",
1057
- fontSize: 10,
1058
- flexShrink: 0
1059
- },
1060
- children: "\u25B6"
1061
- }
1062
- ),
1063
- /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(
1064
- SubflowBreadcrumb,
1065
- {
1066
- breadcrumbs: subflowNav.breadcrumbs,
1067
- onNavigate: handleBreadcrumbNavigate
1068
- }
1069
- ) })
1070
- ] }),
1071
- showTree && treeVisible && /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "stretch", flexShrink: 0 }, children: [
1072
- /* @__PURE__ */ jsx6(
1073
- "button",
1074
- {
1075
- onClick: () => setTreeVisible(false),
1076
- "data-fp": "tree-toggle",
1077
- style: unstyled ? {} : {
1078
- background: "transparent",
1079
- border: "none",
1080
- cursor: "pointer",
1081
- padding: "6px 8px",
1082
- fontSize: 10,
1083
- flexShrink: 0
1084
- },
1085
- children: "\u25C0"
1086
- }
1087
- ),
1088
- /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: subflowNav.isInSubflow && /* @__PURE__ */ jsx6(
1089
- SubflowBreadcrumb,
1090
- {
1091
- breadcrumbs: subflowNav.breadcrumbs,
1092
- onNavigate: handleBreadcrumbNavigate
1093
- }
1094
- ) })
1095
- ] }),
1096
- /* @__PURE__ */ jsx6("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx6(
1097
- ReactFlow2,
1098
- {
1099
- nodes,
1100
- edges,
1101
- onNodeClick: handleNodeClick,
1102
- nodeTypes: nodeTypes2,
1103
- fitView: true,
1104
- panOnDrag: false,
1105
- zoomOnScroll: false,
1106
- zoomOnPinch: false,
1107
- zoomOnDoubleClick: false,
1108
- preventScrolling: false,
1109
- nodesDraggable: false,
1110
- nodesConnectable: false,
1111
- elementsSelectable: !!onNodeClick,
1112
- children: !unstyled && /* @__PURE__ */ jsx6(Background2, { variant: BackgroundVariant2.Dots, gap: 16, size: 1 })
1113
- }
1114
- ) })
1115
- ] })
1310
+ entry,
1311
+ depth: 0,
1312
+ activeStage,
1313
+ doneStages,
1314
+ onNodeSelect
1315
+ },
1316
+ entry.subflowId ?? `${entry.name}-${i}`
1317
+ ))
1116
1318
  ]
1117
1319
  }
1118
1320
  );
1119
- }
1321
+ });
1120
1322
 
1121
1323
  // src/components/TimeTravelDebugger/TimeTravelDebugger.tsx
1122
1324
  import { useState as useState5 } from "react";
1123
1325
 
1124
1326
  // src/components/MemoryInspector/MemoryInspector.tsx
1125
- import { useMemo as useMemo5 } from "react";
1327
+ import { useMemo as useMemo5, useRef as useRef2 } from "react";
1126
1328
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1127
1329
  function MemoryInspector({
1128
1330
  data,
@@ -1135,6 +1337,7 @@ function MemoryInspector({
1135
1337
  className,
1136
1338
  style
1137
1339
  }) {
1340
+ const cacheRef = useRef2(null);
1138
1341
  const { memory, newKeys } = useMemo5(() => {
1139
1342
  if (data) {
1140
1343
  return { memory: data, newKeys: /* @__PURE__ */ new Set() };
@@ -1142,21 +1345,37 @@ function MemoryInspector({
1142
1345
  if (!snapshots || snapshots.length === 0) {
1143
1346
  return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
1144
1347
  }
1145
- const merged = {};
1146
- for (let i = 0; i <= Math.min(selectedIndex, snapshots.length - 1); i++) {
1147
- Object.assign(merged, snapshots[i]?.memory);
1348
+ const safeIdx = Math.min(selectedIndex, snapshots.length - 1);
1349
+ let merged;
1350
+ const cache = cacheRef.current;
1351
+ if (cache && cache.snapshots === snapshots && cache.index <= safeIdx) {
1352
+ merged = { ...cache.accumulated };
1353
+ for (let i = cache.index + 1; i <= safeIdx; i++) {
1354
+ Object.assign(merged, snapshots[i]?.memory);
1355
+ }
1356
+ } else {
1357
+ merged = {};
1358
+ for (let i = 0; i <= safeIdx; i++) {
1359
+ Object.assign(merged, snapshots[i]?.memory);
1360
+ }
1148
1361
  }
1362
+ cacheRef.current = { snapshots, index: safeIdx, accumulated: merged };
1149
1363
  const nk = /* @__PURE__ */ new Set();
1150
- if (highlightNew && selectedIndex > 0) {
1151
- const prev = {};
1152
- for (let i = 0; i < selectedIndex; i++) {
1153
- Object.assign(prev, snapshots[i]?.memory);
1364
+ if (highlightNew && safeIdx > 0) {
1365
+ let prev;
1366
+ if (cache && cache.snapshots === snapshots && cache.index === safeIdx - 1) {
1367
+ prev = cache.accumulated;
1368
+ } else {
1369
+ prev = {};
1370
+ for (let i = 0; i < safeIdx; i++) {
1371
+ Object.assign(prev, snapshots[i]?.memory);
1372
+ }
1154
1373
  }
1155
- const current = snapshots[selectedIndex]?.memory ?? {};
1374
+ const current = snapshots[safeIdx]?.memory ?? {};
1156
1375
  for (const k of Object.keys(current)) {
1157
1376
  if (!(k in prev)) nk.add(k);
1158
1377
  }
1159
- } else if (highlightNew && selectedIndex === 0 && snapshots[0]) {
1378
+ } else if (highlightNew && safeIdx === 0 && snapshots[0]) {
1160
1379
  for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
1161
1380
  }
1162
1381
  return { memory: merged, newKeys: nk };
@@ -1165,9 +1384,9 @@ function MemoryInspector({
1165
1384
  const fs = fontSize[size];
1166
1385
  const pad = padding[size];
1167
1386
  if (unstyled) {
1168
- return /* @__PURE__ */ jsxs5("div", { className, style, "data-fp": "memory-inspector", children: [
1387
+ return /* @__PURE__ */ jsxs5("div", { className, style, "data-fp": "memory-inspector", role: "region", "aria-label": "Memory state", children: [
1169
1388
  /* @__PURE__ */ jsx7("div", { "data-fp": "memory-label", children: "Memory State" }),
1170
- /* @__PURE__ */ jsx7("pre", { "data-fp": "memory-json", children: JSON.stringify(memory, null, 2) })
1389
+ /* @__PURE__ */ jsx7("pre", { "data-fp": "memory-json", children: /* @__PURE__ */ jsx7("code", { children: JSON.stringify(memory, null, 2) }) })
1171
1390
  ] });
1172
1391
  }
1173
1392
  return /* @__PURE__ */ jsxs5(
@@ -1180,6 +1399,8 @@ function MemoryInspector({
1180
1399
  ...style
1181
1400
  },
1182
1401
  "data-fp": "memory-inspector",
1402
+ role: "region",
1403
+ "aria-label": "Memory state",
1183
1404
  children: [
1184
1405
  /* @__PURE__ */ jsx7(
1185
1406
  "span",
@@ -1412,7 +1633,7 @@ function NarrativeLog({
1412
1633
  }
1413
1634
 
1414
1635
  // src/components/GanttTimeline/GanttTimeline.tsx
1415
- import { useState as useState4, useMemo as useMemo7, useRef, useEffect } from "react";
1636
+ import { useState as useState4, useMemo as useMemo7, useRef as useRef3, useEffect as useEffect4 } from "react";
1416
1637
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1417
1638
  function GanttTimeline({
1418
1639
  snapshots,
@@ -1425,8 +1646,8 @@ function GanttTimeline({
1425
1646
  maxVisibleRows = 5
1426
1647
  }) {
1427
1648
  const [expanded, setExpanded] = useState4(false);
1428
- const activeRowRef = useRef(null);
1429
- const scrollContainerRef = useRef(null);
1649
+ const activeRowRef = useRef3(null);
1650
+ const scrollContainerRef = useRef3(null);
1430
1651
  const totalWallTime = useMemo7(
1431
1652
  () => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
1432
1653
  [snapshots]
@@ -1438,7 +1659,7 @@ function GanttTimeline({
1438
1659
  const rowHeight = size === "compact" ? 18 : 22;
1439
1660
  const collapsible = maxVisibleRows > 0 && snapshots.length > maxVisibleRows;
1440
1661
  const showAll = expanded || !collapsible;
1441
- useEffect(() => {
1662
+ useEffect4(() => {
1442
1663
  if (!showAll && activeRowRef.current && scrollContainerRef.current) {
1443
1664
  activeRowRef.current.scrollIntoView({
1444
1665
  block: "nearest",
@@ -1447,12 +1668,15 @@ function GanttTimeline({
1447
1668
  }
1448
1669
  }, [selectedIndex, showAll]);
1449
1670
  if (unstyled) {
1450
- return /* @__PURE__ */ jsx9("div", { className, style, "data-fp": "gantt-timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs7(
1671
+ return /* @__PURE__ */ jsx9("div", { className, style, "data-fp": "gantt-timeline", role: "listbox", "aria-label": "Execution timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs7(
1451
1672
  "div",
1452
1673
  {
1453
1674
  "data-fp": "gantt-bar",
1454
1675
  "data-selected": idx === selectedIndex,
1455
1676
  "data-visible": idx <= selectedIndex,
1677
+ role: "option",
1678
+ "aria-selected": idx === selectedIndex,
1679
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
1456
1680
  onClick: () => onSelect?.(idx),
1457
1681
  children: [
1458
1682
  /* @__PURE__ */ jsx9("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
@@ -1462,7 +1686,7 @@ function GanttTimeline({
1462
1686
  ] })
1463
1687
  ]
1464
1688
  },
1465
- snap.stageName
1689
+ `${snap.stageName}-${idx}`
1466
1690
  )) });
1467
1691
  }
1468
1692
  return /* @__PURE__ */ jsxs7(
@@ -1518,6 +1742,8 @@ function GanttTimeline({
1518
1742
  "div",
1519
1743
  {
1520
1744
  ref: scrollContainerRef,
1745
+ role: "listbox",
1746
+ "aria-label": "Execution timeline",
1521
1747
  style: {
1522
1748
  marginTop: 8,
1523
1749
  display: "flex",
@@ -1538,6 +1764,9 @@ function GanttTimeline({
1538
1764
  "div",
1539
1765
  {
1540
1766
  ref: isSelected ? activeRowRef : void 0,
1767
+ role: "option",
1768
+ "aria-selected": isSelected,
1769
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
1541
1770
  onClick: () => onSelect?.(idx),
1542
1771
  style: {
1543
1772
  display: "flex",
@@ -1553,6 +1782,7 @@ function GanttTimeline({
1553
1782
  /* @__PURE__ */ jsx9(
1554
1783
  "span",
1555
1784
  {
1785
+ title: snap.stageLabel,
1556
1786
  style: {
1557
1787
  width: labelWidth,
1558
1788
  fontSize: fs.small,
@@ -1612,7 +1842,7 @@ function GanttTimeline({
1612
1842
  )
1613
1843
  ]
1614
1844
  },
1615
- snap.stageName
1845
+ `${snap.stageName}-${idx}`
1616
1846
  );
1617
1847
  })
1618
1848
  }
@@ -1966,6 +2196,8 @@ export {
1966
2196
  SubflowTree,
1967
2197
  TimeTravelDebugger,
1968
2198
  TracedFlowchartView,
2199
+ applyOverlay,
2200
+ specToLayout,
1969
2201
  specToReactFlow,
1970
2202
  useSubflowNavigation
1971
2203
  };