footprint-explainable-ui 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1486 @@
1
+ // src/components/FlowchartView/FlowchartView.tsx
2
+ import { useMemo, useCallback } from "react";
3
+ import {
4
+ ReactFlow,
5
+ Background,
6
+ BackgroundVariant,
7
+ useNodesState,
8
+ useEdgesState
9
+ } from "@xyflow/react";
10
+
11
+ // src/components/StageNode/StageNode.tsx
12
+ import { memo } from "react";
13
+ import { Handle, Position } from "@xyflow/react";
14
+
15
+ // src/theme/ThemeProvider.tsx
16
+ import { createContext, useContext } from "react";
17
+
18
+ // src/theme/tokens.ts
19
+ var defaultTokens = {
20
+ colors: {
21
+ primary: "#6366f1",
22
+ success: "#22c55e",
23
+ error: "#ef4444",
24
+ warning: "#f59e0b",
25
+ bgPrimary: "#0f172a",
26
+ bgSecondary: "#1e293b",
27
+ bgTertiary: "#334155",
28
+ textPrimary: "#f8fafc",
29
+ textSecondary: "#94a3b8",
30
+ textMuted: "#64748b",
31
+ border: "#334155"
32
+ },
33
+ radius: "8px",
34
+ fontFamily: {
35
+ sans: "Inter, system-ui, -apple-system, sans-serif",
36
+ mono: "'JetBrains Mono', 'Fira Code', monospace"
37
+ }
38
+ };
39
+
40
+ // src/theme/ThemeProvider.tsx
41
+ import { jsx } from "react/jsx-runtime";
42
+ var ThemeContext = createContext({});
43
+
44
+ // src/theme/styles.ts
45
+ function v(varName, fallback) {
46
+ return `var(${varName}, ${fallback})`;
47
+ }
48
+ var theme = {
49
+ primary: v("--fp-color-primary", "#6366f1"),
50
+ success: v("--fp-color-success", "#22c55e"),
51
+ error: v("--fp-color-error", "#ef4444"),
52
+ warning: v("--fp-color-warning", "#f59e0b"),
53
+ bgPrimary: v("--fp-bg-primary", "#0f172a"),
54
+ bgSecondary: v("--fp-bg-secondary", "#1e293b"),
55
+ bgTertiary: v("--fp-bg-tertiary", "#334155"),
56
+ textPrimary: v("--fp-text-primary", "#f8fafc"),
57
+ textSecondary: v("--fp-text-secondary", "#94a3b8"),
58
+ textMuted: v("--fp-text-muted", "#64748b"),
59
+ border: v("--fp-border", "#334155"),
60
+ radius: v("--fp-radius", "8px"),
61
+ fontSans: v("--fp-font-sans", "Inter, system-ui, -apple-system, sans-serif"),
62
+ fontMono: v("--fp-font-mono", "'JetBrains Mono', 'Fira Code', monospace")
63
+ };
64
+ var fontSize = {
65
+ compact: { label: 10, body: 11, small: 9 },
66
+ default: { label: 11, body: 12, small: 10 },
67
+ detailed: { label: 12, body: 13, small: 11 }
68
+ };
69
+ var padding = {
70
+ compact: 8,
71
+ default: 12,
72
+ detailed: 16
73
+ };
74
+
75
+ // src/components/StageNode/StageNode.tsx
76
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
77
+ var StageNode = memo(function StageNode2({
78
+ data
79
+ }) {
80
+ const { label, active, done, error, linked, stepNumbers, dimmed, isSubflow } = data;
81
+ const isOnPath = active || done;
82
+ const bg = active ? theme.primary : done ? theme.success : error ? theme.error : theme.bgSecondary;
83
+ const borderColor = active ? theme.primary : done ? theme.success : error ? theme.error : theme.border;
84
+ 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)`;
85
+ const textColor = active || done || error ? "#fff" : theme.textPrimary;
86
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
87
+ /* @__PURE__ */ jsx2(Handle, { type: "target", position: Position.Top, style: { opacity: 0 } }),
88
+ /* @__PURE__ */ jsxs(
89
+ "div",
90
+ {
91
+ style: {
92
+ position: "relative",
93
+ display: "flex",
94
+ alignItems: "center",
95
+ gap: 6
96
+ },
97
+ children: [
98
+ stepNumbers && stepNumbers.length > 0 && isOnPath && /* @__PURE__ */ jsx2(
99
+ "div",
100
+ {
101
+ style: {
102
+ position: "absolute",
103
+ top: -10,
104
+ left: -10,
105
+ display: "flex",
106
+ gap: 3,
107
+ zIndex: 10
108
+ },
109
+ children: stepNumbers.map((num, i) => {
110
+ const isLatest = i === stepNumbers.length - 1;
111
+ const badgeBg = isLatest && active ? theme.primary : theme.success;
112
+ const glow = isLatest && active ? `color-mix(in srgb, ${theme.primary} 50%, transparent)` : `color-mix(in srgb, ${theme.success} 40%, transparent)`;
113
+ return /* @__PURE__ */ jsx2(
114
+ "div",
115
+ {
116
+ style: {
117
+ width: 22,
118
+ height: 22,
119
+ borderRadius: "50%",
120
+ background: badgeBg,
121
+ color: "#fff",
122
+ fontSize: 11,
123
+ fontWeight: 700,
124
+ display: "flex",
125
+ alignItems: "center",
126
+ justifyContent: "center",
127
+ boxShadow: `0 0 8px ${glow}`
128
+ },
129
+ children: num
130
+ },
131
+ num
132
+ );
133
+ })
134
+ }
135
+ ),
136
+ linked && /* @__PURE__ */ jsx2(
137
+ "div",
138
+ {
139
+ style: {
140
+ position: "absolute",
141
+ inset: -6,
142
+ borderRadius: `calc(${theme.radius} + 4px)`,
143
+ border: `2px solid ${theme.primary}`,
144
+ opacity: 0.4,
145
+ animation: "fp-pulse 2s ease-in-out infinite"
146
+ }
147
+ }
148
+ ),
149
+ active && /* @__PURE__ */ jsx2(
150
+ "div",
151
+ {
152
+ style: {
153
+ position: "absolute",
154
+ inset: -6,
155
+ borderRadius: `calc(${theme.radius} + 4px)`,
156
+ border: `2px solid ${theme.primary}`,
157
+ opacity: 0.3,
158
+ animation: "fp-pulse 1.5s ease-out infinite"
159
+ }
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsxs(
163
+ "div",
164
+ {
165
+ style: {
166
+ background: bg,
167
+ border: `2px solid ${borderColor}`,
168
+ borderRadius: theme.radius,
169
+ padding: "10px 20px",
170
+ display: "flex",
171
+ alignItems: "center",
172
+ gap: 6,
173
+ boxShadow: shadow,
174
+ transition: "all 0.3s ease",
175
+ fontFamily: theme.fontSans,
176
+ minWidth: 100,
177
+ justifyContent: "center"
178
+ },
179
+ children: [
180
+ done && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2713" }),
181
+ active && /* @__PURE__ */ jsx2(
182
+ "span",
183
+ {
184
+ style: {
185
+ width: 8,
186
+ height: 8,
187
+ borderRadius: "50%",
188
+ background: "#fff",
189
+ animation: "fp-blink 1s ease-in-out infinite",
190
+ flexShrink: 0
191
+ }
192
+ }
193
+ ),
194
+ error && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2717" }),
195
+ /* @__PURE__ */ jsx2(
196
+ "span",
197
+ {
198
+ style: {
199
+ fontSize: 13,
200
+ fontWeight: 500,
201
+ color: textColor,
202
+ whiteSpace: "nowrap"
203
+ },
204
+ children: label
205
+ }
206
+ ),
207
+ isSubflow && /* @__PURE__ */ jsx2(
208
+ "span",
209
+ {
210
+ style: {
211
+ display: "inline-flex",
212
+ alignItems: "center",
213
+ justifyContent: "center",
214
+ width: 16,
215
+ height: 16,
216
+ borderRadius: 3,
217
+ border: `1.5px solid ${textColor}`,
218
+ position: "relative",
219
+ opacity: 0.7,
220
+ flexShrink: 0
221
+ },
222
+ children: /* @__PURE__ */ jsx2(
223
+ "span",
224
+ {
225
+ style: {
226
+ width: 8,
227
+ height: 8,
228
+ borderRadius: 2,
229
+ border: `1px solid ${textColor}`
230
+ }
231
+ }
232
+ )
233
+ }
234
+ )
235
+ ]
236
+ }
237
+ )
238
+ ]
239
+ }
240
+ ),
241
+ /* @__PURE__ */ jsx2(Handle, { type: "source", position: Position.Bottom, style: { opacity: 0 } }),
242
+ /* @__PURE__ */ jsx2(
243
+ Handle,
244
+ {
245
+ id: "loop-source",
246
+ type: "source",
247
+ position: Position.Right,
248
+ style: { background: "transparent", border: "none", width: 6, height: 6 }
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsx2(
252
+ Handle,
253
+ {
254
+ id: "loop-target",
255
+ type: "target",
256
+ position: Position.Right,
257
+ style: { background: "transparent", border: "none", width: 6, height: 6 }
258
+ }
259
+ )
260
+ ] });
261
+ });
262
+
263
+ // src/components/FlowchartView/FlowchartView.tsx
264
+ import { jsx as jsx3 } from "react/jsx-runtime";
265
+ var nodeTypes = { stageNode: StageNode };
266
+ function FlowchartView({
267
+ nodes: rawNodes,
268
+ edges: rawEdges,
269
+ snapshots,
270
+ selectedIndex = 0,
271
+ onNodeClick,
272
+ unstyled = false,
273
+ className,
274
+ style
275
+ }) {
276
+ const enhancedNodes = useMemo(() => {
277
+ if (!snapshots || snapshots.length === 0) {
278
+ return rawNodes.map((n) => ({
279
+ ...n,
280
+ type: "stageNode",
281
+ data: {
282
+ ...n.data,
283
+ label: n.data.label || n.id,
284
+ active: false,
285
+ done: false,
286
+ error: false
287
+ }
288
+ }));
289
+ }
290
+ const doneNames = new Set(
291
+ snapshots.slice(0, selectedIndex).map((s) => s.stageName)
292
+ );
293
+ const activeName = snapshots[selectedIndex]?.stageName;
294
+ return rawNodes.map((n) => ({
295
+ ...n,
296
+ type: "stageNode",
297
+ data: {
298
+ ...n.data,
299
+ label: n.data.label || n.id,
300
+ active: n.id === activeName,
301
+ done: doneNames.has(n.id),
302
+ error: false
303
+ }
304
+ }));
305
+ }, [rawNodes, snapshots, selectedIndex]);
306
+ const enhancedEdges = useMemo(() => {
307
+ if (!snapshots || snapshots.length === 0) {
308
+ return rawEdges.map((e) => ({
309
+ ...e,
310
+ style: { stroke: theme.textMuted, strokeWidth: 1.5 },
311
+ animated: false
312
+ }));
313
+ }
314
+ const doneNames = new Set(
315
+ snapshots.slice(0, selectedIndex + 1).map((s) => s.stageName)
316
+ );
317
+ const activeName = snapshots[selectedIndex]?.stageName;
318
+ return rawEdges.map((e) => {
319
+ const sourceIsDone = doneNames.has(e.source);
320
+ const isFromActive = e.source === activeName;
321
+ return {
322
+ ...e,
323
+ style: {
324
+ stroke: sourceIsDone ? theme.success : theme.textMuted,
325
+ strokeWidth: 1.5
326
+ },
327
+ animated: isFromActive
328
+ };
329
+ });
330
+ }, [rawEdges, snapshots, selectedIndex]);
331
+ const [nodes, , onNodesChange] = useNodesState(enhancedNodes);
332
+ const [edges, , onEdgesChange] = useEdgesState(enhancedEdges);
333
+ const handleNodeClick = useCallback(
334
+ (_, node) => {
335
+ if (!onNodeClick || !snapshots) return;
336
+ const idx = snapshots.findIndex((s) => s.stageName === node.id);
337
+ if (idx >= 0) onNodeClick(idx);
338
+ },
339
+ [onNodeClick, snapshots]
340
+ );
341
+ return /* @__PURE__ */ jsx3(
342
+ "div",
343
+ {
344
+ className,
345
+ style: {
346
+ width: "100%",
347
+ height: "100%",
348
+ ...style
349
+ },
350
+ "data-fp": "flowchart-view",
351
+ children: /* @__PURE__ */ jsx3(
352
+ ReactFlow,
353
+ {
354
+ nodes,
355
+ edges,
356
+ onNodesChange,
357
+ onEdgesChange,
358
+ onNodeClick: handleNodeClick,
359
+ nodeTypes,
360
+ fitView: true,
361
+ panOnDrag: false,
362
+ zoomOnScroll: false,
363
+ zoomOnPinch: false,
364
+ zoomOnDoubleClick: false,
365
+ preventScrolling: false,
366
+ nodesDraggable: false,
367
+ nodesConnectable: false,
368
+ elementsSelectable: !!onNodeClick,
369
+ children: !unstyled && /* @__PURE__ */ jsx3(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 })
370
+ }
371
+ )
372
+ }
373
+ );
374
+ }
375
+
376
+ // src/components/FlowchartView/SubflowBreadcrumb.tsx
377
+ import { memo as memo2 } from "react";
378
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
379
+ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
380
+ breadcrumbs,
381
+ onNavigate
382
+ }) {
383
+ if (breadcrumbs.length <= 1) return null;
384
+ return /* @__PURE__ */ jsx4(
385
+ "div",
386
+ {
387
+ style: {
388
+ display: "flex",
389
+ alignItems: "center",
390
+ gap: 4,
391
+ padding: "6px 12px",
392
+ background: theme.bgSecondary,
393
+ borderBottom: `1px solid ${theme.border}`,
394
+ fontSize: 12,
395
+ fontFamily: theme.fontSans,
396
+ flexShrink: 0,
397
+ overflowX: "auto"
398
+ },
399
+ children: breadcrumbs.map((crumb, i) => {
400
+ const isLast = i === breadcrumbs.length - 1;
401
+ return /* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
402
+ i > 0 && /* @__PURE__ */ jsx4("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
403
+ isLast ? /* @__PURE__ */ jsx4(
404
+ "span",
405
+ {
406
+ style: {
407
+ color: theme.primary,
408
+ fontWeight: 600
409
+ },
410
+ children: crumb.label
411
+ }
412
+ ) : /* @__PURE__ */ jsx4(
413
+ "button",
414
+ {
415
+ onClick: () => onNavigate(i),
416
+ style: {
417
+ background: "none",
418
+ border: "none",
419
+ color: theme.textSecondary,
420
+ cursor: "pointer",
421
+ padding: "2px 4px",
422
+ borderRadius: 4,
423
+ fontSize: 12,
424
+ fontFamily: "inherit",
425
+ fontWeight: 500,
426
+ transition: "color 0.15s"
427
+ },
428
+ onMouseEnter: (e) => {
429
+ e.currentTarget.style.color = `${theme.primary}`;
430
+ },
431
+ onMouseLeave: (e) => {
432
+ e.currentTarget.style.color = `${theme.textSecondary}`;
433
+ },
434
+ children: crumb.label
435
+ }
436
+ )
437
+ ] }, i);
438
+ })
439
+ }
440
+ );
441
+ });
442
+
443
+ // src/components/FlowchartView/useSubflowNavigation.ts
444
+ import { useState, useCallback as useCallback2, useMemo as useMemo2 } from "react";
445
+
446
+ // src/components/FlowchartView/specToReactFlow.ts
447
+ var DEFAULT_COLORS = {
448
+ edgeDefault: defaultTokens.colors.textMuted,
449
+ edgeExecuted: defaultTokens.colors.success,
450
+ edgeActive: defaultTokens.colors.primary,
451
+ edgeLoop: defaultTokens.colors.warning,
452
+ labelDefault: defaultTokens.colors.textSecondary,
453
+ labelExecuted: defaultTokens.colors.success,
454
+ labelLoop: defaultTokens.colors.warning,
455
+ pathGlow: `${defaultTokens.colors.success}4D`
456
+ // ~30% opacity hex
457
+ };
458
+ var Y_STEP = 100;
459
+ var X_SPREAD = 200;
460
+ function nid(n) {
461
+ return n.name || n.id || `spec-${Math.random()}`;
462
+ }
463
+ function addEdge(state, source, target, label, isLoop) {
464
+ state.edgeCounter++;
465
+ const o = state.overlay;
466
+ const c = state.colors;
467
+ const executed = o && o.executedStages.has(source) && o.executedStages.has(target);
468
+ const isLeadingEdge = o && source === o.activeStage && !o.doneStages.has(target);
469
+ if (isLoop) {
470
+ let loopExecuted = false;
471
+ if (o?.executionOrder) {
472
+ const lastSourceIdx = o.executionOrder.lastIndexOf(source);
473
+ if (lastSourceIdx >= 0) {
474
+ loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(target);
475
+ }
476
+ }
477
+ state.edges.push({
478
+ id: `se${state.edgeCounter}`,
479
+ source,
480
+ target,
481
+ sourceHandle: "loop-source",
482
+ targetHandle: "loop-target",
483
+ label: label ?? "loop",
484
+ type: "smoothstep",
485
+ pathOptions: { offset: 40, borderRadius: 16 },
486
+ style: {
487
+ stroke: c.edgeLoop,
488
+ strokeWidth: loopExecuted ? 3 : 2,
489
+ strokeDasharray: "6 3",
490
+ opacity: o && !loopExecuted ? 0.35 : 1
491
+ },
492
+ labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
493
+ animated: loopExecuted,
494
+ zIndex: 2
495
+ });
496
+ return;
497
+ }
498
+ if (executed) {
499
+ state.edges.push({
500
+ id: `se${state.edgeCounter}-glow`,
501
+ source,
502
+ target,
503
+ style: {
504
+ stroke: c.pathGlow,
505
+ strokeWidth: 8,
506
+ opacity: 0.4
507
+ },
508
+ zIndex: 0,
509
+ selectable: false,
510
+ focusable: false
511
+ });
512
+ state.edges.push({
513
+ id: `se${state.edgeCounter}`,
514
+ source,
515
+ target,
516
+ label,
517
+ style: {
518
+ stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
519
+ strokeWidth: 3.5
520
+ },
521
+ labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
522
+ animated: !!isLeadingEdge,
523
+ zIndex: 1
524
+ });
525
+ } else {
526
+ state.edges.push({
527
+ id: `se${state.edgeCounter}`,
528
+ source,
529
+ target,
530
+ label,
531
+ style: {
532
+ stroke: c.edgeDefault,
533
+ strokeWidth: 1.5,
534
+ opacity: o ? 0.3 : 1
535
+ },
536
+ labelStyle: { fontSize: 10, fill: c.labelDefault }
537
+ });
538
+ }
539
+ }
540
+ function walk(node, state, x, y) {
541
+ const id = nid(node);
542
+ if (state.seen.has(id)) {
543
+ return { lastIds: [id], bottomY: y };
544
+ }
545
+ state.seen.add(id);
546
+ const isDecider = node.type === "decider" || node.hasDecider;
547
+ const isFork = node.type === "fork";
548
+ const o = state.overlay;
549
+ const isDone = o ? o.doneStages.has(id) : false;
550
+ const isActive = o ? o.activeStage === id : false;
551
+ const wasExecuted = o ? o.executedStages.has(id) : false;
552
+ const dimmed = o && !wasExecuted;
553
+ let stepNumbers;
554
+ if (o?.executionOrder) {
555
+ const nums = [];
556
+ for (let i = 0; i < o.executionOrder.length; i++) {
557
+ if (o.executionOrder[i] === id) nums.push(i + 1);
558
+ }
559
+ if (nums.length > 0) stepNumbers = nums;
560
+ }
561
+ state.nodes.push({
562
+ id,
563
+ position: { x, y },
564
+ data: {
565
+ label: node.name,
566
+ active: isActive,
567
+ done: isDone,
568
+ error: false,
569
+ isDecider,
570
+ isFork,
571
+ description: node.description,
572
+ dimmed,
573
+ stepNumbers,
574
+ isSubflow: !!node.isSubflowRoot
575
+ },
576
+ type: "stage",
577
+ style: dimmed ? { opacity: 0.35 } : void 0
578
+ });
579
+ let lastIds = [id];
580
+ let bottomY = y;
581
+ if (node.children && node.children.length > 0) {
582
+ const totalWidth = (node.children.length - 1) * X_SPREAD;
583
+ const startX = x - totalWidth / 2;
584
+ const childY = y + Y_STEP;
585
+ const childResults = [];
586
+ for (let i = 0; i < node.children.length; i++) {
587
+ const child = node.children[i];
588
+ const childX = startX + i * X_SPREAD;
589
+ const edgeLabel = node.branchIds?.[i];
590
+ addEdge(state, id, nid(child), edgeLabel);
591
+ const result = walk(child, state, childX, childY);
592
+ childResults.push(result);
593
+ }
594
+ lastIds = childResults.flatMap((r) => r.lastIds);
595
+ bottomY = Math.max(...childResults.map((r) => r.bottomY));
596
+ }
597
+ if (node.loopTarget) {
598
+ addEdge(state, id, node.loopTarget, "loop", true);
599
+ }
600
+ if (node.next) {
601
+ const nextY = bottomY + Y_STEP;
602
+ const nextId = nid(node.next);
603
+ for (const lid of lastIds) {
604
+ if (node.loopTarget && lid === id && node.loopTarget === nextId) continue;
605
+ addEdge(state, lid, nextId);
606
+ }
607
+ const result = walk(node.next, state, x, nextY);
608
+ return result;
609
+ }
610
+ return { lastIds, bottomY };
611
+ }
612
+ function specToReactFlow(spec, overlay, colors) {
613
+ const state = {
614
+ nodes: [],
615
+ edges: [],
616
+ edgeCounter: 0,
617
+ seen: /* @__PURE__ */ new Set(),
618
+ overlay: overlay ?? null,
619
+ colors: { ...DEFAULT_COLORS, ...colors }
620
+ };
621
+ walk(spec, state, 300, 0);
622
+ return { nodes: state.nodes, edges: state.edges };
623
+ }
624
+
625
+ // src/components/FlowchartView/useSubflowNavigation.ts
626
+ function useSubflowNavigation(rootSpec, overlay, colors) {
627
+ const [stack, setStack] = useState([]);
628
+ const currentSpec = stack.length > 0 ? stack[stack.length - 1].spec : rootSpec;
629
+ const { nodes, edges } = useMemo2(() => {
630
+ if (!currentSpec) return { nodes: [], edges: [] };
631
+ return specToReactFlow(currentSpec, overlay, colors);
632
+ }, [currentSpec, overlay, colors]);
633
+ const subflowMap = useMemo2(() => {
634
+ const map = /* @__PURE__ */ new Map();
635
+ if (!currentSpec) return map;
636
+ function collectSubflows(node) {
637
+ if (node.isSubflowRoot && node.subflowStructure) {
638
+ const id = node.name || node.id || "";
639
+ map.set(id, node);
640
+ }
641
+ if (node.children) node.children.forEach(collectSubflows);
642
+ if (node.next) collectSubflows(node.next);
643
+ }
644
+ collectSubflows(currentSpec);
645
+ return map;
646
+ }, [currentSpec]);
647
+ const breadcrumbs = useMemo2(() => {
648
+ const root = {
649
+ label: rootSpec?.name || "Pipeline",
650
+ spec: rootSpec
651
+ };
652
+ return [root, ...stack];
653
+ }, [rootSpec, stack]);
654
+ const handleNodeClick = useCallback2(
655
+ (nodeId) => {
656
+ const subflowNode = subflowMap.get(nodeId);
657
+ if (!subflowNode?.subflowStructure) return false;
658
+ setStack((prev) => [
659
+ ...prev,
660
+ {
661
+ label: subflowNode.subflowName || subflowNode.name,
662
+ spec: subflowNode.subflowStructure
663
+ }
664
+ ]);
665
+ return true;
666
+ },
667
+ [subflowMap]
668
+ );
669
+ const navigateTo = useCallback2(
670
+ (level) => {
671
+ if (level === 0) {
672
+ setStack([]);
673
+ } else {
674
+ setStack((prev) => prev.slice(0, level));
675
+ }
676
+ },
677
+ []
678
+ );
679
+ return {
680
+ breadcrumbs,
681
+ nodes,
682
+ edges,
683
+ handleNodeClick,
684
+ navigateTo,
685
+ isInSubflow: stack.length > 0,
686
+ currentSubflowNodeName: stack.length > 0 ? stack[0].label : null
687
+ };
688
+ }
689
+
690
+ // src/components/TimeTravelDebugger/TimeTravelDebugger.tsx
691
+ import { useState as useState2 } from "react";
692
+
693
+ // src/components/MemoryInspector/MemoryInspector.tsx
694
+ import { useMemo as useMemo3 } from "react";
695
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
696
+ function MemoryInspector({
697
+ data,
698
+ snapshots,
699
+ selectedIndex = 0,
700
+ showTypes = false,
701
+ highlightNew = true,
702
+ size = "default",
703
+ unstyled = false,
704
+ className,
705
+ style
706
+ }) {
707
+ const { memory, newKeys } = useMemo3(() => {
708
+ if (data) {
709
+ return { memory: data, newKeys: /* @__PURE__ */ new Set() };
710
+ }
711
+ if (!snapshots || snapshots.length === 0) {
712
+ return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
713
+ }
714
+ const merged = {};
715
+ for (let i = 0; i <= Math.min(selectedIndex, snapshots.length - 1); i++) {
716
+ Object.assign(merged, snapshots[i]?.memory);
717
+ }
718
+ const nk = /* @__PURE__ */ new Set();
719
+ if (highlightNew && selectedIndex > 0) {
720
+ const prev = {};
721
+ for (let i = 0; i < selectedIndex; i++) {
722
+ Object.assign(prev, snapshots[i]?.memory);
723
+ }
724
+ const current = snapshots[selectedIndex]?.memory ?? {};
725
+ for (const k of Object.keys(current)) {
726
+ if (!(k in prev)) nk.add(k);
727
+ }
728
+ } else if (highlightNew && selectedIndex === 0 && snapshots[0]) {
729
+ for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
730
+ }
731
+ return { memory: merged, newKeys: nk };
732
+ }, [data, snapshots, selectedIndex, highlightNew]);
733
+ const entries = Object.entries(memory);
734
+ const fs = fontSize[size];
735
+ const pad = padding[size];
736
+ if (unstyled) {
737
+ return /* @__PURE__ */ jsxs3("div", { className, style, "data-fp": "memory-inspector", children: [
738
+ /* @__PURE__ */ jsx5("div", { "data-fp": "memory-label", children: "Memory State" }),
739
+ /* @__PURE__ */ jsx5("pre", { "data-fp": "memory-json", children: JSON.stringify(memory, null, 2) })
740
+ ] });
741
+ }
742
+ return /* @__PURE__ */ jsxs3(
743
+ "div",
744
+ {
745
+ className,
746
+ style: {
747
+ padding: pad,
748
+ fontFamily: theme.fontSans,
749
+ ...style
750
+ },
751
+ "data-fp": "memory-inspector",
752
+ children: [
753
+ /* @__PURE__ */ jsx5(
754
+ "span",
755
+ {
756
+ style: {
757
+ fontSize: fs.label,
758
+ fontWeight: 600,
759
+ color: theme.textMuted,
760
+ textTransform: "uppercase",
761
+ letterSpacing: "0.08em"
762
+ },
763
+ children: "Memory State"
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsxs3(
767
+ "div",
768
+ {
769
+ style: {
770
+ marginTop: 8,
771
+ background: theme.bgSecondary,
772
+ border: `1px solid ${theme.border}`,
773
+ borderRadius: theme.radius,
774
+ padding: `${pad}px ${pad + 4}px`,
775
+ fontFamily: theme.fontMono,
776
+ fontSize: fs.body,
777
+ lineHeight: 1.8
778
+ },
779
+ children: [
780
+ /* @__PURE__ */ jsx5("span", { style: { color: theme.textMuted }, children: "{" }),
781
+ entries.length === 0 && /* @__PURE__ */ jsx5(
782
+ "div",
783
+ {
784
+ style: {
785
+ paddingLeft: 16,
786
+ color: theme.textMuted,
787
+ fontStyle: "italic"
788
+ },
789
+ children: "// empty"
790
+ }
791
+ ),
792
+ entries.map(([key, value], i) => {
793
+ const isNew = newKeys.has(key);
794
+ const isLast = i === entries.length - 1;
795
+ return /* @__PURE__ */ jsxs3(
796
+ "div",
797
+ {
798
+ style: {
799
+ paddingLeft: 16,
800
+ background: isNew ? `color-mix(in srgb, ${theme.success} 10%, transparent)` : "transparent",
801
+ borderRadius: 4,
802
+ marginLeft: -4,
803
+ marginRight: -4,
804
+ paddingRight: 4
805
+ },
806
+ children: [
807
+ /* @__PURE__ */ jsxs3("span", { style: { color: theme.primary }, children: [
808
+ '"',
809
+ key,
810
+ '"'
811
+ ] }),
812
+ /* @__PURE__ */ jsx5("span", { style: { color: theme.textMuted }, children: ": " }),
813
+ /* @__PURE__ */ jsx5("span", { style: { color: theme.success }, children: formatValue(value) }),
814
+ showTypes && /* @__PURE__ */ jsxs3(
815
+ "span",
816
+ {
817
+ style: {
818
+ color: theme.textMuted,
819
+ fontSize: fs.small,
820
+ marginLeft: 8,
821
+ opacity: 0.6
822
+ },
823
+ children: [
824
+ "(",
825
+ typeof value,
826
+ ")"
827
+ ]
828
+ }
829
+ ),
830
+ !isLast && /* @__PURE__ */ jsx5("span", { style: { color: theme.textMuted }, children: "," })
831
+ ]
832
+ },
833
+ key
834
+ );
835
+ }),
836
+ /* @__PURE__ */ jsx5("span", { style: { color: theme.textMuted }, children: "}" })
837
+ ]
838
+ }
839
+ )
840
+ ]
841
+ }
842
+ );
843
+ }
844
+ function formatValue(value) {
845
+ if (typeof value === "string") return `"${value}"`;
846
+ if (typeof value === "object" && value !== null) return JSON.stringify(value);
847
+ return String(value);
848
+ }
849
+
850
+ // src/components/NarrativeLog/NarrativeLog.tsx
851
+ import { useMemo as useMemo4 } from "react";
852
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
853
+ function NarrativeLog({
854
+ snapshots,
855
+ selectedIndex,
856
+ narrative,
857
+ size = "default",
858
+ unstyled = false,
859
+ className,
860
+ style
861
+ }) {
862
+ const entries = useMemo4(() => {
863
+ if (narrative) {
864
+ return [{ label: "Output", text: narrative, isCurrent: true }];
865
+ }
866
+ const idx = selectedIndex ?? snapshots.length - 1;
867
+ return snapshots.slice(0, idx + 1).map((s, i) => ({
868
+ label: s.stageLabel,
869
+ text: s.narrative,
870
+ isCurrent: i === idx
871
+ }));
872
+ }, [snapshots, selectedIndex, narrative]);
873
+ const fs = fontSize[size];
874
+ const pad = padding[size];
875
+ if (unstyled) {
876
+ return /* @__PURE__ */ jsx6("div", { className, style, "data-fp": "narrative-log", children: entries.map((entry, i) => /* @__PURE__ */ jsxs4("div", { "data-fp": "narrative-entry", "data-current": entry.isCurrent, children: [
877
+ /* @__PURE__ */ jsx6("strong", { children: entry.label }),
878
+ /* @__PURE__ */ jsx6("p", { children: entry.text })
879
+ ] }, i)) });
880
+ }
881
+ return /* @__PURE__ */ jsxs4(
882
+ "div",
883
+ {
884
+ className,
885
+ style: { padding: pad, fontFamily: theme.fontSans, ...style },
886
+ "data-fp": "narrative-log",
887
+ children: [
888
+ /* @__PURE__ */ jsx6(
889
+ "span",
890
+ {
891
+ style: {
892
+ fontSize: fs.label,
893
+ fontWeight: 600,
894
+ color: theme.textMuted,
895
+ textTransform: "uppercase",
896
+ letterSpacing: "0.08em"
897
+ },
898
+ children: "Execution Log"
899
+ }
900
+ ),
901
+ /* @__PURE__ */ jsx6("div", { style: { marginTop: 8, display: "flex", flexDirection: "column" }, children: entries.map((entry, i) => /* @__PURE__ */ jsxs4(
902
+ "div",
903
+ {
904
+ style: {
905
+ display: "flex",
906
+ gap: 10,
907
+ padding: `${pad}px 0`,
908
+ borderBottom: i < entries.length - 1 ? `1px solid ${theme.border}` : "none"
909
+ },
910
+ children: [
911
+ /* @__PURE__ */ jsxs4(
912
+ "div",
913
+ {
914
+ style: {
915
+ display: "flex",
916
+ flexDirection: "column",
917
+ alignItems: "center",
918
+ width: 12,
919
+ flexShrink: 0,
920
+ paddingTop: 5
921
+ },
922
+ children: [
923
+ /* @__PURE__ */ jsx6(
924
+ "div",
925
+ {
926
+ style: {
927
+ width: 8,
928
+ height: 8,
929
+ borderRadius: "50%",
930
+ background: entry.isCurrent ? theme.primary : theme.success,
931
+ flexShrink: 0
932
+ }
933
+ }
934
+ ),
935
+ i < entries.length - 1 && /* @__PURE__ */ jsx6(
936
+ "div",
937
+ {
938
+ style: {
939
+ width: 1,
940
+ flex: 1,
941
+ background: theme.border,
942
+ marginTop: 4
943
+ }
944
+ }
945
+ )
946
+ ]
947
+ }
948
+ ),
949
+ /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0 }, children: [
950
+ /* @__PURE__ */ jsx6(
951
+ "span",
952
+ {
953
+ style: {
954
+ fontSize: fs.label,
955
+ fontWeight: 600,
956
+ color: entry.isCurrent ? theme.primary : theme.textMuted
957
+ },
958
+ children: entry.label
959
+ }
960
+ ),
961
+ /* @__PURE__ */ jsx6(
962
+ "div",
963
+ {
964
+ style: {
965
+ fontSize: fs.body,
966
+ lineHeight: 1.5,
967
+ color: entry.isCurrent ? theme.textPrimary : theme.textSecondary,
968
+ marginTop: 2
969
+ },
970
+ children: entry.text
971
+ }
972
+ )
973
+ ] })
974
+ ]
975
+ },
976
+ i
977
+ )) })
978
+ ]
979
+ }
980
+ );
981
+ }
982
+
983
+ // src/components/GanttTimeline/GanttTimeline.tsx
984
+ import { useMemo as useMemo5 } from "react";
985
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
986
+ function GanttTimeline({
987
+ snapshots,
988
+ selectedIndex = 0,
989
+ onSelect,
990
+ size = "default",
991
+ unstyled = false,
992
+ className,
993
+ style
994
+ }) {
995
+ const totalWallTime = useMemo5(
996
+ () => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
997
+ [snapshots]
998
+ );
999
+ const fs = fontSize[size];
1000
+ const pad = padding[size];
1001
+ const labelWidth = size === "compact" ? 50 : size === "detailed" ? 100 : 80;
1002
+ const msWidth = size === "compact" ? 28 : 36;
1003
+ if (unstyled) {
1004
+ return /* @__PURE__ */ jsx7("div", { className, style, "data-fp": "gantt-timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs5(
1005
+ "div",
1006
+ {
1007
+ "data-fp": "gantt-bar",
1008
+ "data-selected": idx === selectedIndex,
1009
+ "data-visible": idx <= selectedIndex,
1010
+ onClick: () => onSelect?.(idx),
1011
+ children: [
1012
+ /* @__PURE__ */ jsx7("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
1013
+ /* @__PURE__ */ jsxs5("span", { "data-fp": "gantt-duration", children: [
1014
+ snap.durationMs,
1015
+ "ms"
1016
+ ] })
1017
+ ]
1018
+ },
1019
+ snap.stageName
1020
+ )) });
1021
+ }
1022
+ return /* @__PURE__ */ jsxs5(
1023
+ "div",
1024
+ {
1025
+ className,
1026
+ style: { padding: pad, fontFamily: theme.fontSans, ...style },
1027
+ "data-fp": "gantt-timeline",
1028
+ children: [
1029
+ /* @__PURE__ */ jsx7(
1030
+ "span",
1031
+ {
1032
+ style: {
1033
+ fontSize: fs.label,
1034
+ fontWeight: 600,
1035
+ color: theme.textMuted,
1036
+ textTransform: "uppercase",
1037
+ letterSpacing: "0.08em"
1038
+ },
1039
+ children: size === "compact" ? "Timeline" : "Execution Timeline"
1040
+ }
1041
+ ),
1042
+ /* @__PURE__ */ jsx7(
1043
+ "div",
1044
+ {
1045
+ style: {
1046
+ marginTop: 8,
1047
+ display: "flex",
1048
+ flexDirection: "column",
1049
+ gap: 4
1050
+ },
1051
+ children: snapshots.map((snap, idx) => {
1052
+ const leftPct = snap.startMs / totalWallTime * 100;
1053
+ const widthPct = Math.max(snap.durationMs / totalWallTime * 100, 1);
1054
+ const isSelected = idx === selectedIndex;
1055
+ const isVisible = idx <= selectedIndex;
1056
+ return /* @__PURE__ */ jsxs5(
1057
+ "div",
1058
+ {
1059
+ onClick: () => onSelect?.(idx),
1060
+ style: {
1061
+ display: "flex",
1062
+ alignItems: "center",
1063
+ gap: size === "compact" ? 4 : 8,
1064
+ cursor: onSelect ? "pointer" : "default",
1065
+ opacity: isVisible ? 1 : 0.3,
1066
+ transition: "opacity 0.3s ease"
1067
+ },
1068
+ children: [
1069
+ /* @__PURE__ */ jsx7(
1070
+ "span",
1071
+ {
1072
+ style: {
1073
+ width: labelWidth,
1074
+ fontSize: fs.small,
1075
+ color: isSelected ? theme.primary : theme.textMuted,
1076
+ fontWeight: isSelected ? 600 : 400,
1077
+ textAlign: "right",
1078
+ flexShrink: 0,
1079
+ overflow: "hidden",
1080
+ textOverflow: "ellipsis",
1081
+ whiteSpace: "nowrap"
1082
+ },
1083
+ children: snap.stageLabel
1084
+ }
1085
+ ),
1086
+ /* @__PURE__ */ jsx7(
1087
+ "div",
1088
+ {
1089
+ style: {
1090
+ flex: 1,
1091
+ height: size === "compact" ? 6 : 8,
1092
+ position: "relative",
1093
+ background: theme.bgTertiary,
1094
+ borderRadius: 3
1095
+ },
1096
+ children: isVisible && /* @__PURE__ */ jsx7(
1097
+ "div",
1098
+ {
1099
+ style: {
1100
+ position: "absolute",
1101
+ left: `${leftPct}%`,
1102
+ top: 0,
1103
+ width: `${widthPct}%`,
1104
+ height: "100%",
1105
+ borderRadius: 3,
1106
+ background: isSelected ? theme.primary : theme.success,
1107
+ transition: "width 0.3s ease"
1108
+ }
1109
+ }
1110
+ )
1111
+ }
1112
+ ),
1113
+ /* @__PURE__ */ jsxs5(
1114
+ "span",
1115
+ {
1116
+ style: {
1117
+ fontSize: fs.small,
1118
+ color: theme.textMuted,
1119
+ fontFamily: theme.fontMono,
1120
+ width: msWidth,
1121
+ flexShrink: 0
1122
+ },
1123
+ children: [
1124
+ snap.durationMs,
1125
+ "ms"
1126
+ ]
1127
+ }
1128
+ )
1129
+ ]
1130
+ },
1131
+ snap.stageName
1132
+ );
1133
+ })
1134
+ }
1135
+ ),
1136
+ /* @__PURE__ */ jsxs5(
1137
+ "div",
1138
+ {
1139
+ style: {
1140
+ marginTop: 4,
1141
+ marginLeft: labelWidth + (size === "compact" ? 4 : 8),
1142
+ marginRight: msWidth + (size === "compact" ? 4 : 8),
1143
+ display: "flex",
1144
+ justifyContent: "space-between",
1145
+ fontSize: fs.small - 1,
1146
+ color: theme.textMuted,
1147
+ fontFamily: theme.fontMono
1148
+ },
1149
+ children: [
1150
+ /* @__PURE__ */ jsx7("span", { children: "0ms" }),
1151
+ size !== "compact" && /* @__PURE__ */ jsxs5("span", { children: [
1152
+ (totalWallTime / 2).toFixed(1),
1153
+ "ms"
1154
+ ] }),
1155
+ /* @__PURE__ */ jsxs5("span", { children: [
1156
+ totalWallTime.toFixed(1),
1157
+ "ms"
1158
+ ] })
1159
+ ]
1160
+ }
1161
+ )
1162
+ ]
1163
+ }
1164
+ );
1165
+ }
1166
+
1167
+ // src/components/TimeTravelDebugger/TimeTravelDebugger.tsx
1168
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1169
+ function TimeTravelDebugger({
1170
+ snapshots,
1171
+ nodes,
1172
+ edges,
1173
+ showGantt = true,
1174
+ layout = "horizontal",
1175
+ title = "Time-Travel Debugger",
1176
+ size = "default",
1177
+ unstyled = false,
1178
+ className,
1179
+ style
1180
+ }) {
1181
+ const [selectedIndex, setSelectedIndex] = useState2(0);
1182
+ const fs = fontSize[size];
1183
+ const pad = padding[size];
1184
+ if (snapshots.length === 0) {
1185
+ return /* @__PURE__ */ jsx8(
1186
+ "div",
1187
+ {
1188
+ className,
1189
+ style: {
1190
+ padding: pad * 2,
1191
+ textAlign: "center",
1192
+ color: theme.textMuted,
1193
+ ...style
1194
+ },
1195
+ children: "No snapshots to debug"
1196
+ }
1197
+ );
1198
+ }
1199
+ const isHorizontal = layout === "horizontal";
1200
+ if (unstyled) {
1201
+ return /* @__PURE__ */ jsxs6("div", { className, style, "data-fp": "time-travel-debugger", children: [
1202
+ /* @__PURE__ */ jsx8("h3", { children: title }),
1203
+ /* @__PURE__ */ jsx8(
1204
+ "input",
1205
+ {
1206
+ type: "range",
1207
+ min: 0,
1208
+ max: snapshots.length - 1,
1209
+ value: selectedIndex,
1210
+ onChange: (e) => setSelectedIndex(parseInt(e.target.value))
1211
+ }
1212
+ ),
1213
+ /* @__PURE__ */ jsx8(
1214
+ FlowchartView,
1215
+ {
1216
+ nodes,
1217
+ edges,
1218
+ snapshots,
1219
+ selectedIndex,
1220
+ onNodeClick: setSelectedIndex,
1221
+ unstyled: true
1222
+ }
1223
+ ),
1224
+ /* @__PURE__ */ jsx8(
1225
+ MemoryInspector,
1226
+ {
1227
+ snapshots,
1228
+ selectedIndex,
1229
+ unstyled: true
1230
+ }
1231
+ ),
1232
+ /* @__PURE__ */ jsx8(
1233
+ NarrativeLog,
1234
+ {
1235
+ snapshots,
1236
+ selectedIndex,
1237
+ unstyled: true
1238
+ }
1239
+ ),
1240
+ showGantt && /* @__PURE__ */ jsx8(
1241
+ GanttTimeline,
1242
+ {
1243
+ snapshots,
1244
+ selectedIndex,
1245
+ onSelect: setSelectedIndex,
1246
+ unstyled: true
1247
+ }
1248
+ )
1249
+ ] });
1250
+ }
1251
+ return /* @__PURE__ */ jsxs6(
1252
+ "div",
1253
+ {
1254
+ className,
1255
+ style: {
1256
+ display: "flex",
1257
+ flexDirection: "column",
1258
+ height: "100%",
1259
+ background: theme.bgPrimary,
1260
+ fontFamily: theme.fontSans,
1261
+ overflow: "hidden",
1262
+ ...style
1263
+ },
1264
+ "data-fp": "time-travel-debugger",
1265
+ children: [
1266
+ /* @__PURE__ */ jsxs6(
1267
+ "div",
1268
+ {
1269
+ style: {
1270
+ padding: `${pad}px ${pad + 4}px`,
1271
+ borderBottom: `1px solid ${theme.border}`,
1272
+ background: theme.bgSecondary,
1273
+ flexShrink: 0
1274
+ },
1275
+ children: [
1276
+ /* @__PURE__ */ jsxs6(
1277
+ "div",
1278
+ {
1279
+ style: {
1280
+ display: "flex",
1281
+ alignItems: "center",
1282
+ gap: 8,
1283
+ marginBottom: 8
1284
+ },
1285
+ children: [
1286
+ /* @__PURE__ */ jsx8(
1287
+ "span",
1288
+ {
1289
+ style: {
1290
+ fontSize: fs.body + 2,
1291
+ fontWeight: 600,
1292
+ color: theme.textPrimary
1293
+ },
1294
+ children: title
1295
+ }
1296
+ ),
1297
+ /* @__PURE__ */ jsx8(
1298
+ "span",
1299
+ {
1300
+ style: {
1301
+ fontSize: fs.small,
1302
+ color: theme.textMuted
1303
+ },
1304
+ children: "Scrub to replay execution"
1305
+ }
1306
+ )
1307
+ ]
1308
+ }
1309
+ ),
1310
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1311
+ /* @__PURE__ */ jsx8(
1312
+ ScrubButton,
1313
+ {
1314
+ label: "\\u25C0",
1315
+ disabled: selectedIndex === 0,
1316
+ onClick: () => setSelectedIndex((i) => Math.max(0, i - 1))
1317
+ }
1318
+ ),
1319
+ /* @__PURE__ */ jsx8(
1320
+ "input",
1321
+ {
1322
+ type: "range",
1323
+ min: 0,
1324
+ max: snapshots.length - 1,
1325
+ value: selectedIndex,
1326
+ onChange: (e) => setSelectedIndex(parseInt(e.target.value)),
1327
+ style: {
1328
+ flex: 1,
1329
+ height: 4,
1330
+ accentColor: theme.primary,
1331
+ cursor: "pointer"
1332
+ }
1333
+ }
1334
+ ),
1335
+ /* @__PURE__ */ jsx8(
1336
+ ScrubButton,
1337
+ {
1338
+ label: "\\u25B6",
1339
+ disabled: selectedIndex === snapshots.length - 1,
1340
+ onClick: () => setSelectedIndex((i) => Math.min(snapshots.length - 1, i + 1))
1341
+ }
1342
+ ),
1343
+ /* @__PURE__ */ jsxs6(
1344
+ "span",
1345
+ {
1346
+ style: {
1347
+ fontSize: fs.small,
1348
+ color: theme.textMuted,
1349
+ flexShrink: 0,
1350
+ fontFamily: theme.fontMono
1351
+ },
1352
+ children: [
1353
+ selectedIndex + 1,
1354
+ "/",
1355
+ snapshots.length
1356
+ ]
1357
+ }
1358
+ )
1359
+ ] })
1360
+ ]
1361
+ }
1362
+ ),
1363
+ /* @__PURE__ */ jsxs6(
1364
+ "div",
1365
+ {
1366
+ style: {
1367
+ flex: 1,
1368
+ display: "flex",
1369
+ flexDirection: isHorizontal ? "row" : "column",
1370
+ overflow: "hidden"
1371
+ },
1372
+ children: [
1373
+ /* @__PURE__ */ jsx8(
1374
+ "div",
1375
+ {
1376
+ style: {
1377
+ flex: 1,
1378
+ overflow: "hidden",
1379
+ borderRight: isHorizontal ? `1px solid ${theme.border}` : "none",
1380
+ borderBottom: !isHorizontal ? `1px solid ${theme.border}` : "none"
1381
+ },
1382
+ children: /* @__PURE__ */ jsx8(
1383
+ FlowchartView,
1384
+ {
1385
+ nodes,
1386
+ edges,
1387
+ snapshots,
1388
+ selectedIndex,
1389
+ onNodeClick: setSelectedIndex,
1390
+ size
1391
+ }
1392
+ )
1393
+ }
1394
+ ),
1395
+ /* @__PURE__ */ jsxs6("div", { style: { flex: 1, overflow: "auto" }, children: [
1396
+ /* @__PURE__ */ jsx8(
1397
+ MemoryInspector,
1398
+ {
1399
+ snapshots,
1400
+ selectedIndex,
1401
+ size
1402
+ }
1403
+ ),
1404
+ /* @__PURE__ */ jsx8(
1405
+ "div",
1406
+ {
1407
+ style: {
1408
+ height: 1,
1409
+ background: theme.border,
1410
+ margin: `0 ${pad}px`
1411
+ }
1412
+ }
1413
+ ),
1414
+ /* @__PURE__ */ jsx8(
1415
+ NarrativeLog,
1416
+ {
1417
+ snapshots,
1418
+ selectedIndex,
1419
+ size
1420
+ }
1421
+ )
1422
+ ] })
1423
+ ]
1424
+ }
1425
+ ),
1426
+ showGantt && /* @__PURE__ */ jsx8(
1427
+ "div",
1428
+ {
1429
+ style: {
1430
+ borderTop: `1px solid ${theme.border}`,
1431
+ background: theme.bgSecondary,
1432
+ flexShrink: 0
1433
+ },
1434
+ children: /* @__PURE__ */ jsx8(
1435
+ GanttTimeline,
1436
+ {
1437
+ snapshots,
1438
+ selectedIndex,
1439
+ onSelect: setSelectedIndex,
1440
+ size
1441
+ }
1442
+ )
1443
+ }
1444
+ )
1445
+ ]
1446
+ }
1447
+ );
1448
+ }
1449
+ function ScrubButton({
1450
+ label,
1451
+ disabled,
1452
+ onClick
1453
+ }) {
1454
+ return /* @__PURE__ */ jsx8(
1455
+ "button",
1456
+ {
1457
+ onClick,
1458
+ disabled,
1459
+ style: {
1460
+ background: theme.bgTertiary,
1461
+ border: `1px solid ${theme.border}`,
1462
+ color: disabled ? theme.textMuted : theme.textPrimary,
1463
+ borderRadius: 6,
1464
+ width: 28,
1465
+ height: 28,
1466
+ display: "flex",
1467
+ alignItems: "center",
1468
+ justifyContent: "center",
1469
+ cursor: disabled ? "not-allowed" : "pointer",
1470
+ opacity: disabled ? 0.5 : 1,
1471
+ fontSize: 12,
1472
+ flexShrink: 0
1473
+ },
1474
+ children: label
1475
+ }
1476
+ );
1477
+ }
1478
+ export {
1479
+ FlowchartView,
1480
+ StageNode,
1481
+ SubflowBreadcrumb,
1482
+ TimeTravelDebugger,
1483
+ specToReactFlow,
1484
+ useSubflowNavigation
1485
+ };
1486
+ //# sourceMappingURL=flowchart.js.map