footprint-explainable-ui 0.3.2 → 0.5.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.
Files changed (161) hide show
  1. package/README.md +42 -3
  2. package/dist/adapters/fromRuntimeSnapshot.d.ts +52 -0
  3. package/dist/adapters/fromRuntimeSnapshot.d.ts.map +1 -0
  4. package/dist/adapters/fromRuntimeSnapshot.js +97 -0
  5. package/dist/adapters/fromRuntimeSnapshot.js.map +1 -0
  6. package/dist/components/ExplainableShell/ExplainableShell.d.ts +26 -0
  7. package/dist/components/ExplainableShell/ExplainableShell.d.ts.map +1 -0
  8. package/dist/components/ExplainableShell/ExplainableShell.js +94 -0
  9. package/dist/components/ExplainableShell/ExplainableShell.js.map +1 -0
  10. package/dist/components/ExplainableShell/index.d.ts +3 -0
  11. package/dist/components/ExplainableShell/index.d.ts.map +1 -0
  12. package/dist/components/ExplainableShell/index.js +2 -0
  13. package/dist/components/ExplainableShell/index.js.map +1 -0
  14. package/dist/components/FlowchartView/FlowchartView.d.ts +20 -0
  15. package/dist/components/FlowchartView/FlowchartView.d.ts.map +1 -0
  16. package/dist/components/FlowchartView/FlowchartView.js +80 -0
  17. package/dist/components/FlowchartView/FlowchartView.js.map +1 -0
  18. package/dist/components/FlowchartView/SubflowBreadcrumb.d.ts +11 -0
  19. package/dist/components/FlowchartView/SubflowBreadcrumb.d.ts.map +1 -0
  20. package/dist/components/FlowchartView/SubflowBreadcrumb.js +49 -0
  21. package/dist/components/FlowchartView/SubflowBreadcrumb.js.map +1 -0
  22. package/dist/components/FlowchartView/SubflowTree.d.ts +36 -0
  23. package/dist/components/FlowchartView/SubflowTree.d.ts.map +1 -0
  24. package/dist/components/FlowchartView/SubflowTree.js +143 -0
  25. package/dist/components/FlowchartView/SubflowTree.js.map +1 -0
  26. package/dist/components/FlowchartView/TracedFlowchartView.d.ts +20 -0
  27. package/dist/components/FlowchartView/TracedFlowchartView.d.ts.map +1 -0
  28. package/dist/components/FlowchartView/TracedFlowchartView.js +101 -0
  29. package/dist/components/FlowchartView/TracedFlowchartView.js.map +1 -0
  30. package/dist/components/FlowchartView/index.d.ts +11 -0
  31. package/dist/components/FlowchartView/index.d.ts.map +1 -0
  32. package/dist/components/FlowchartView/index.js +6 -0
  33. package/dist/components/FlowchartView/index.js.map +1 -0
  34. package/dist/components/FlowchartView/specToReactFlow.d.ts +56 -0
  35. package/dist/components/FlowchartView/specToReactFlow.d.ts.map +1 -0
  36. package/dist/components/FlowchartView/specToReactFlow.js +202 -0
  37. package/dist/components/FlowchartView/specToReactFlow.js.map +1 -0
  38. package/dist/components/FlowchartView/useSubflowNavigation.d.ts +35 -0
  39. package/dist/components/FlowchartView/useSubflowNavigation.d.ts.map +1 -0
  40. package/dist/components/FlowchartView/useSubflowNavigation.js +80 -0
  41. package/dist/components/FlowchartView/useSubflowNavigation.js.map +1 -0
  42. package/dist/components/GanttTimeline/GanttTimeline.d.ts +18 -0
  43. package/dist/components/GanttTimeline/GanttTimeline.d.ts.map +1 -0
  44. package/dist/components/GanttTimeline/GanttTimeline.js +123 -0
  45. package/dist/components/GanttTimeline/GanttTimeline.js.map +1 -0
  46. package/dist/components/GanttTimeline/index.d.ts +3 -0
  47. package/dist/components/GanttTimeline/index.d.ts.map +1 -0
  48. package/dist/components/GanttTimeline/index.js +2 -0
  49. package/dist/components/GanttTimeline/index.js.map +1 -0
  50. package/dist/components/MemoryInspector/MemoryInspector.d.ts +19 -0
  51. package/dist/components/MemoryInspector/MemoryInspector.d.ts.map +1 -0
  52. package/dist/components/MemoryInspector/MemoryInspector.js +95 -0
  53. package/dist/components/MemoryInspector/MemoryInspector.js.map +1 -0
  54. package/dist/components/MemoryInspector/index.d.ts +3 -0
  55. package/dist/components/MemoryInspector/index.d.ts.map +1 -0
  56. package/dist/components/MemoryInspector/index.js +2 -0
  57. package/dist/components/MemoryInspector/index.js.map +1 -0
  58. package/dist/components/NarrativeLog/NarrativeLog.d.ts +15 -0
  59. package/dist/components/NarrativeLog/NarrativeLog.d.ts.map +1 -0
  60. package/dist/components/NarrativeLog/NarrativeLog.js +65 -0
  61. package/dist/components/NarrativeLog/NarrativeLog.js.map +1 -0
  62. package/dist/components/NarrativeLog/index.d.ts +3 -0
  63. package/dist/components/NarrativeLog/index.d.ts.map +1 -0
  64. package/dist/components/NarrativeLog/index.js +2 -0
  65. package/dist/components/NarrativeLog/index.js.map +1 -0
  66. package/dist/components/NarrativeTrace/NarrativeTrace.d.ts +13 -0
  67. package/dist/components/NarrativeTrace/NarrativeTrace.d.ts.map +1 -0
  68. package/dist/components/NarrativeTrace/NarrativeTrace.js +127 -0
  69. package/dist/components/NarrativeTrace/NarrativeTrace.js.map +1 -0
  70. package/dist/components/NarrativeTrace/index.d.ts +3 -0
  71. package/dist/components/NarrativeTrace/index.d.ts.map +1 -0
  72. package/dist/components/NarrativeTrace/index.js +2 -0
  73. package/dist/components/NarrativeTrace/index.js.map +1 -0
  74. package/dist/components/ResultPanel/ResultPanel.d.ts +11 -0
  75. package/dist/components/ResultPanel/ResultPanel.d.ts.map +1 -0
  76. package/dist/components/ResultPanel/ResultPanel.js +54 -0
  77. package/dist/components/ResultPanel/ResultPanel.js.map +1 -0
  78. package/dist/components/ResultPanel/index.d.ts +3 -0
  79. package/dist/components/ResultPanel/index.d.ts.map +1 -0
  80. package/dist/components/ResultPanel/index.js +2 -0
  81. package/dist/components/ResultPanel/index.js.map +1 -0
  82. package/dist/components/ScopeDiff/ScopeDiff.d.ts +17 -0
  83. package/dist/components/ScopeDiff/ScopeDiff.d.ts.map +1 -0
  84. package/dist/components/ScopeDiff/ScopeDiff.js +87 -0
  85. package/dist/components/ScopeDiff/ScopeDiff.js.map +1 -0
  86. package/dist/components/ScopeDiff/index.d.ts +3 -0
  87. package/dist/components/ScopeDiff/index.d.ts.map +1 -0
  88. package/dist/components/ScopeDiff/index.js +2 -0
  89. package/dist/components/ScopeDiff/index.js.map +1 -0
  90. package/dist/components/SnapshotPanel/SnapshotPanel.d.ts +17 -0
  91. package/dist/components/SnapshotPanel/SnapshotPanel.d.ts.map +1 -0
  92. package/dist/components/SnapshotPanel/SnapshotPanel.js +85 -0
  93. package/dist/components/SnapshotPanel/SnapshotPanel.js.map +1 -0
  94. package/dist/components/SnapshotPanel/index.d.ts +3 -0
  95. package/dist/components/SnapshotPanel/index.d.ts.map +1 -0
  96. package/dist/components/SnapshotPanel/index.js +2 -0
  97. package/dist/components/SnapshotPanel/index.js.map +1 -0
  98. package/dist/components/StageNode/StageNode.d.ts +31 -0
  99. package/dist/components/StageNode/StageNode.d.ts.map +1 -0
  100. package/dist/components/StageNode/StageNode.js +134 -0
  101. package/dist/components/StageNode/StageNode.js.map +1 -0
  102. package/dist/components/StageNode/index.d.ts +3 -0
  103. package/dist/components/StageNode/index.d.ts.map +1 -0
  104. package/dist/components/StageNode/index.js +2 -0
  105. package/dist/components/StageNode/index.js.map +1 -0
  106. package/dist/components/TimeTravelControls/TimeTravelControls.d.ts +13 -0
  107. package/dist/components/TimeTravelControls/TimeTravelControls.d.ts.map +1 -0
  108. package/dist/components/TimeTravelControls/TimeTravelControls.js +104 -0
  109. package/dist/components/TimeTravelControls/TimeTravelControls.js.map +1 -0
  110. package/dist/components/TimeTravelControls/index.d.ts +3 -0
  111. package/dist/components/TimeTravelControls/index.d.ts.map +1 -0
  112. package/dist/components/TimeTravelControls/index.js +2 -0
  113. package/dist/components/TimeTravelControls/index.js.map +1 -0
  114. package/dist/components/TimeTravelDebugger/TimeTravelDebugger.d.ts +22 -0
  115. package/dist/components/TimeTravelDebugger/TimeTravelDebugger.d.ts.map +1 -0
  116. package/dist/components/TimeTravelDebugger/TimeTravelDebugger.js +104 -0
  117. package/dist/components/TimeTravelDebugger/TimeTravelDebugger.js.map +1 -0
  118. package/dist/components/TimeTravelDebugger/index.d.ts +3 -0
  119. package/dist/components/TimeTravelDebugger/index.d.ts.map +1 -0
  120. package/dist/components/TimeTravelDebugger/index.js +2 -0
  121. package/dist/components/TimeTravelDebugger/index.js.map +1 -0
  122. package/dist/flowchart.cjs +704 -220
  123. package/dist/flowchart.cjs.map +1 -1
  124. package/dist/flowchart.d.cts +55 -1
  125. package/dist/flowchart.d.ts +55 -1
  126. package/dist/flowchart.d.ts.map +1 -0
  127. package/dist/flowchart.js +700 -214
  128. package/dist/flowchart.js.map +1 -1
  129. package/dist/index.cjs +849 -76
  130. package/dist/index.cjs.map +1 -1
  131. package/dist/index.d.cts +95 -3
  132. package/dist/index.d.ts +95 -3
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +851 -80
  135. package/dist/index.js.map +1 -1
  136. package/dist/theme/ThemeProvider.d.ts +13 -0
  137. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  138. package/dist/theme/ThemeProvider.js +16 -0
  139. package/dist/theme/ThemeProvider.js.map +1 -0
  140. package/dist/theme/index.d.ts +7 -0
  141. package/dist/theme/index.d.ts.map +1 -0
  142. package/dist/theme/index.js +5 -0
  143. package/dist/theme/index.js.map +1 -0
  144. package/dist/theme/presets.d.ts +15 -0
  145. package/dist/theme/presets.d.ts.map +1 -0
  146. package/dist/theme/presets.js +70 -0
  147. package/dist/theme/presets.js.map +1 -0
  148. package/dist/theme/styles.d.ts +32 -0
  149. package/dist/theme/styles.d.ts.map +1 -0
  150. package/dist/theme/styles.js +37 -0
  151. package/dist/theme/styles.js.map +1 -0
  152. package/dist/theme/tokens.d.ts +28 -0
  153. package/dist/theme/tokens.d.ts.map +1 -0
  154. package/dist/theme/tokens.js +58 -0
  155. package/dist/theme/tokens.js.map +1 -0
  156. package/dist/tsconfig.tsbuildinfo +1 -0
  157. package/dist/types.d.ts +35 -0
  158. package/dist/types.d.ts.map +1 -0
  159. package/dist/types.js +2 -0
  160. package/dist/types.js.map +1 -0
  161. package/package.json +10 -4
package/dist/index.cjs CHANGED
@@ -29,6 +29,8 @@ __export(src_exports, {
29
29
  ResultPanel: () => ResultPanel,
30
30
  ScopeDiff: () => ScopeDiff,
31
31
  SnapshotPanel: () => SnapshotPanel,
32
+ StageDetailPanel: () => StageDetailPanel,
33
+ SubflowTree: () => SubflowTree,
32
34
  TimeTravelControls: () => TimeTravelControls,
33
35
  coolDark: () => coolDark,
34
36
  createSnapshots: () => createSnapshots,
@@ -697,8 +699,12 @@ function GanttTimeline({
697
699
  size = "default",
698
700
  unstyled = false,
699
701
  className,
700
- style
702
+ style,
703
+ maxVisibleRows = 5
701
704
  }) {
705
+ const [expanded, setExpanded] = (0, import_react5.useState)(false);
706
+ const activeRowRef = (0, import_react5.useRef)(null);
707
+ const scrollContainerRef = (0, import_react5.useRef)(null);
702
708
  const totalWallTime = (0, import_react5.useMemo)(
703
709
  () => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
704
710
  [snapshots]
@@ -707,6 +713,17 @@ function GanttTimeline({
707
713
  const pad = padding[size];
708
714
  const labelWidth = size === "compact" ? 50 : size === "detailed" ? 100 : 80;
709
715
  const msWidth = size === "compact" ? 28 : 36;
716
+ const rowHeight = size === "compact" ? 18 : 22;
717
+ const collapsible = maxVisibleRows > 0 && snapshots.length > maxVisibleRows;
718
+ const showAll = expanded || !collapsible;
719
+ (0, import_react5.useEffect)(() => {
720
+ if (!showAll && activeRowRef.current && scrollContainerRef.current) {
721
+ activeRowRef.current.scrollIntoView({
722
+ block: "nearest",
723
+ behavior: "smooth"
724
+ });
725
+ }
726
+ }, [selectedIndex, showAll]);
710
727
  if (unstyled) {
711
728
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style, "data-fp": "gantt-timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
712
729
  "div",
@@ -733,27 +750,62 @@ function GanttTimeline({
733
750
  style: { padding: pad, fontFamily: theme.fontSans, ...style },
734
751
  "data-fp": "gantt-timeline",
735
752
  children: [
736
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
737
- "span",
753
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
754
+ "div",
738
755
  {
739
756
  style: {
740
- fontSize: fs.label,
741
- fontWeight: 600,
742
- color: theme.textMuted,
743
- textTransform: "uppercase",
744
- letterSpacing: "0.08em"
757
+ display: "flex",
758
+ alignItems: "center",
759
+ justifyContent: "space-between"
745
760
  },
746
- children: size === "compact" ? "Timeline" : "Execution Timeline"
761
+ children: [
762
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
763
+ "span",
764
+ {
765
+ style: {
766
+ fontSize: fs.label,
767
+ fontWeight: 600,
768
+ color: theme.textMuted,
769
+ textTransform: "uppercase",
770
+ letterSpacing: "0.08em"
771
+ },
772
+ children: size === "compact" ? "Timeline" : "Execution Timeline"
773
+ }
774
+ ),
775
+ collapsible && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
776
+ "button",
777
+ {
778
+ onClick: () => setExpanded((e) => !e),
779
+ style: {
780
+ background: "none",
781
+ border: `1px solid ${theme.border}`,
782
+ borderRadius: 4,
783
+ color: theme.textSecondary,
784
+ fontSize: fs.small,
785
+ padding: "2px 8px",
786
+ cursor: "pointer",
787
+ fontFamily: theme.fontSans
788
+ },
789
+ children: expanded ? "Collapse" : `${snapshots.length - maxVisibleRows} more...`
790
+ }
791
+ )
792
+ ]
747
793
  }
748
794
  ),
749
795
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
750
796
  "div",
751
797
  {
798
+ ref: scrollContainerRef,
752
799
  style: {
753
800
  marginTop: 8,
754
801
  display: "flex",
755
802
  flexDirection: "column",
756
- gap: 4
803
+ gap: 4,
804
+ ...showAll ? {} : {
805
+ maxHeight: maxVisibleRows * (rowHeight + 4),
806
+ overflowY: "auto",
807
+ scrollbarWidth: "thin"
808
+ }
757
809
  },
758
810
  children: snapshots.map((snap, idx) => {
759
811
  const leftPct = snap.startMs / totalWallTime * 100;
@@ -763,6 +815,7 @@ function GanttTimeline({
763
815
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
764
816
  "div",
765
817
  {
818
+ ref: isSelected ? activeRowRef : void 0,
766
819
  onClick: () => onSelect?.(idx),
767
820
  style: {
768
821
  display: "flex",
@@ -770,7 +823,9 @@ function GanttTimeline({
770
823
  gap: size === "compact" ? 4 : 8,
771
824
  cursor: onSelect ? "pointer" : "default",
772
825
  opacity: isVisible ? 1 : 0.3,
773
- transition: "opacity 0.3s ease"
826
+ transition: "opacity 0.3s ease",
827
+ height: rowHeight,
828
+ flexShrink: 0
774
829
  },
775
830
  children: [
776
831
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -1374,9 +1429,487 @@ function ResultPanel({
1374
1429
  );
1375
1430
  }
1376
1431
 
1377
- // src/components/TimeTravelControls/TimeTravelControls.tsx
1432
+ // src/components/StageDetailPanel/StageDetailPanel.tsx
1378
1433
  var import_react8 = require("react");
1379
1434
  var import_jsx_runtime9 = require("react/jsx-runtime");
1435
+ function computeChanges(prev, curr) {
1436
+ const changes = [];
1437
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev ?? {}), ...Object.keys(curr)]);
1438
+ for (const key of allKeys) {
1439
+ const inPrev = prev != null && key in prev;
1440
+ const inCurr = key in curr;
1441
+ const oldVal = prev?.[key];
1442
+ const newVal = curr[key];
1443
+ if (!inPrev && inCurr) {
1444
+ changes.push({ key, type: "added", newValue: newVal });
1445
+ } else if (inPrev && !inCurr) {
1446
+ changes.push({ key, type: "removed", oldValue: oldVal });
1447
+ } else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
1448
+ changes.push({ key, type: "updated", oldValue: oldVal, newValue: newVal });
1449
+ }
1450
+ }
1451
+ const order = { added: 0, updated: 1, removed: 2 };
1452
+ changes.sort((a, b) => order[a.type] - order[b.type]);
1453
+ return changes;
1454
+ }
1455
+ function fmt2(v2) {
1456
+ if (typeof v2 === "string") return `"${v2}"`;
1457
+ if (typeof v2 === "object" && v2 !== null) return JSON.stringify(v2, null, 2);
1458
+ return String(v2);
1459
+ }
1460
+ var changeBadge = {
1461
+ added: { bg: "rgba(34,197,94,0.12)", fg: "#22c55e", label: "ADD" },
1462
+ updated: { bg: "rgba(245,158,11,0.12)", fg: "#f59e0b", label: "UPD" },
1463
+ removed: { bg: "rgba(239,68,68,0.12)", fg: "#ef4444", label: "DEL" }
1464
+ };
1465
+ function SimpleView({
1466
+ snapshot,
1467
+ fs,
1468
+ pad
1469
+ }) {
1470
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
1471
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1472
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1473
+ "div",
1474
+ {
1475
+ style: {
1476
+ fontSize: fs.label + 2,
1477
+ fontWeight: 700,
1478
+ color: theme.textPrimary
1479
+ },
1480
+ children: snapshot.stageLabel
1481
+ }
1482
+ ),
1483
+ snapshot.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1484
+ "div",
1485
+ {
1486
+ style: {
1487
+ fontSize: fs.body,
1488
+ color: theme.textSecondary,
1489
+ marginTop: 4,
1490
+ lineHeight: 1.5
1491
+ },
1492
+ children: snapshot.description
1493
+ }
1494
+ )
1495
+ ] }),
1496
+ snapshot.status && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1498
+ "div",
1499
+ {
1500
+ style: {
1501
+ width: 8,
1502
+ height: 8,
1503
+ borderRadius: "50%",
1504
+ background: snapshot.status === "done" ? theme.success : snapshot.status === "active" ? theme.primary : snapshot.status === "error" ? theme.error : theme.textMuted
1505
+ }
1506
+ }
1507
+ ),
1508
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1509
+ "span",
1510
+ {
1511
+ style: {
1512
+ fontSize: fs.small,
1513
+ color: theme.textMuted,
1514
+ textTransform: "uppercase",
1515
+ letterSpacing: "0.05em"
1516
+ },
1517
+ children: snapshot.status
1518
+ }
1519
+ )
1520
+ ] }),
1521
+ snapshot.narrative && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1522
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1523
+ "div",
1524
+ {
1525
+ style: {
1526
+ fontSize: fs.label,
1527
+ fontWeight: 600,
1528
+ color: theme.textMuted,
1529
+ textTransform: "uppercase",
1530
+ letterSpacing: "0.08em",
1531
+ marginBottom: 6
1532
+ },
1533
+ children: "What happened"
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1537
+ "div",
1538
+ {
1539
+ style: {
1540
+ fontSize: fs.body,
1541
+ lineHeight: 1.6,
1542
+ color: theme.textPrimary,
1543
+ background: theme.bgSecondary,
1544
+ border: `1px solid ${theme.border}`,
1545
+ borderRadius: theme.radius,
1546
+ padding: pad
1547
+ },
1548
+ children: snapshot.narrative
1549
+ }
1550
+ )
1551
+ ] }),
1552
+ snapshot.durationMs > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1553
+ "div",
1554
+ {
1555
+ style: {
1556
+ fontSize: fs.small,
1557
+ color: theme.textMuted
1558
+ },
1559
+ children: [
1560
+ "Completed in ",
1561
+ snapshot.durationMs < 1 ? "<1" : snapshot.durationMs,
1562
+ "ms"
1563
+ ]
1564
+ }
1565
+ )
1566
+ ] });
1567
+ }
1568
+ function buildMemoryRows(currMemory, changes) {
1569
+ const changeMap = new Map(changes.map((c) => [c.key, c]));
1570
+ const rows = [];
1571
+ for (const change of changes) {
1572
+ rows.push({ kind: "change", change });
1573
+ }
1574
+ const unchangedKeys = Object.keys(currMemory).filter((k) => !changeMap.has(k)).sort();
1575
+ for (const key of unchangedKeys) {
1576
+ rows.push({ kind: "unchanged", key, value: currMemory[key] });
1577
+ }
1578
+ return rows;
1579
+ }
1580
+ function DevView({
1581
+ snapshot,
1582
+ changes,
1583
+ currMemory,
1584
+ fs,
1585
+ pad
1586
+ }) {
1587
+ const rows = (0, import_react8.useMemo)(() => buildMemoryRows(currMemory, changes), [currMemory, changes]);
1588
+ const totalKeys = Object.keys(currMemory).length;
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1590
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1591
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1592
+ "span",
1593
+ {
1594
+ style: {
1595
+ fontSize: fs.label + 2,
1596
+ fontWeight: 700,
1597
+ color: theme.textPrimary,
1598
+ fontFamily: theme.fontMono
1599
+ },
1600
+ children: snapshot.stageLabel
1601
+ }
1602
+ ),
1603
+ snapshot.durationMs > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1604
+ "span",
1605
+ {
1606
+ style: {
1607
+ fontSize: fs.small,
1608
+ color: theme.textMuted,
1609
+ fontFamily: theme.fontMono
1610
+ },
1611
+ children: [
1612
+ snapshot.durationMs,
1613
+ "ms"
1614
+ ]
1615
+ }
1616
+ )
1617
+ ] }),
1618
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1619
+ "div",
1620
+ {
1621
+ style: {
1622
+ fontSize: fs.label,
1623
+ fontWeight: 600,
1624
+ color: theme.textMuted,
1625
+ textTransform: "uppercase",
1626
+ letterSpacing: "0.08em"
1627
+ },
1628
+ children: [
1629
+ "Memory",
1630
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { fontWeight: 400, marginLeft: 6 }, children: [
1631
+ "(",
1632
+ totalKeys,
1633
+ " key",
1634
+ totalKeys !== 1 ? "s" : "",
1635
+ changes.length > 0 && `, ${changes.length} changed`,
1636
+ ")"
1637
+ ] })
1638
+ ]
1639
+ }
1640
+ ),
1641
+ rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1642
+ "div",
1643
+ {
1644
+ style: {
1645
+ fontSize: fs.body,
1646
+ color: theme.textMuted,
1647
+ fontStyle: "italic",
1648
+ fontFamily: theme.fontMono,
1649
+ padding: `${pad}px`,
1650
+ background: theme.bgSecondary,
1651
+ borderRadius: theme.radius
1652
+ },
1653
+ children: "Empty memory"
1654
+ }
1655
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1656
+ "div",
1657
+ {
1658
+ style: {
1659
+ fontFamily: theme.fontMono,
1660
+ fontSize: fs.body,
1661
+ background: theme.bgSecondary,
1662
+ border: `1px solid ${theme.border}`,
1663
+ borderRadius: theme.radius,
1664
+ overflow: "hidden"
1665
+ },
1666
+ children: rows.map((row) => {
1667
+ if (row.kind === "change") {
1668
+ const { change } = row;
1669
+ const badge = changeBadge[change.type];
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1671
+ "div",
1672
+ {
1673
+ style: {
1674
+ display: "flex",
1675
+ alignItems: "flex-start",
1676
+ gap: 8,
1677
+ padding: `6px ${pad}px`,
1678
+ borderBottom: `1px solid ${theme.border}`,
1679
+ background: badge.bg
1680
+ },
1681
+ "data-fp": "memory-change",
1682
+ "data-type": change.type,
1683
+ children: [
1684
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1685
+ "span",
1686
+ {
1687
+ style: {
1688
+ fontSize: fs.small,
1689
+ fontWeight: 700,
1690
+ color: badge.fg,
1691
+ width: 28,
1692
+ flexShrink: 0,
1693
+ textAlign: "center",
1694
+ lineHeight: 1.8
1695
+ },
1696
+ children: badge.label
1697
+ }
1698
+ ),
1699
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1700
+ "span",
1701
+ {
1702
+ style: {
1703
+ color: theme.primary,
1704
+ fontWeight: 600,
1705
+ flexShrink: 0,
1706
+ lineHeight: 1.8
1707
+ },
1708
+ children: change.key
1709
+ }
1710
+ ),
1711
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { flex: 1, minWidth: 0, lineHeight: 1.8 }, children: change.type === "updated" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1713
+ "span",
1714
+ {
1715
+ style: {
1716
+ color: theme.error,
1717
+ textDecoration: "line-through",
1718
+ opacity: 0.7
1719
+ },
1720
+ children: fmt2(change.oldValue)
1721
+ }
1722
+ ),
1723
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: theme.textMuted, margin: "0 4px" }, children: "\u2192" }),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: theme.success }, children: fmt2(change.newValue) })
1725
+ ] }) : change.type === "added" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: theme.success }, children: fmt2(change.newValue) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: theme.error, textDecoration: "line-through" }, children: fmt2(change.oldValue) }) })
1726
+ ]
1727
+ },
1728
+ change.key
1729
+ );
1730
+ }
1731
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1732
+ "div",
1733
+ {
1734
+ style: {
1735
+ display: "flex",
1736
+ alignItems: "flex-start",
1737
+ gap: 8,
1738
+ padding: `6px ${pad}px`,
1739
+ borderBottom: `1px solid ${theme.border}`,
1740
+ opacity: 0.5
1741
+ },
1742
+ "data-fp": "memory-unchanged",
1743
+ children: [
1744
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1745
+ "span",
1746
+ {
1747
+ style: {
1748
+ width: 28,
1749
+ flexShrink: 0,
1750
+ lineHeight: 1.8
1751
+ }
1752
+ }
1753
+ ),
1754
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1755
+ "span",
1756
+ {
1757
+ style: {
1758
+ color: theme.textSecondary,
1759
+ fontWeight: 500,
1760
+ flexShrink: 0,
1761
+ lineHeight: 1.8
1762
+ },
1763
+ children: row.key
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { flex: 1, minWidth: 0, lineHeight: 1.8, color: theme.textMuted }, children: fmt2(row.value) })
1767
+ ]
1768
+ },
1769
+ row.key
1770
+ );
1771
+ })
1772
+ }
1773
+ )
1774
+ ] });
1775
+ }
1776
+ function UnstyledSimpleView({ snapshot }) {
1777
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-fp": "stage-detail-simple", children: [
1778
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "stage-label", children: snapshot.stageLabel }),
1779
+ snapshot.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "stage-description", children: snapshot.description }),
1780
+ snapshot.status && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "stage-status", children: snapshot.status }),
1781
+ snapshot.narrative && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "stage-narrative", children: snapshot.narrative })
1782
+ ] });
1783
+ }
1784
+ function UnstyledDevView({
1785
+ snapshot,
1786
+ changes,
1787
+ currMemory
1788
+ }) {
1789
+ const rows = (0, import_react8.useMemo)(() => buildMemoryRows(currMemory, changes), [currMemory, changes]);
1790
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-fp": "stage-detail-dev", children: [
1791
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "stage-label", children: snapshot.stageLabel }),
1792
+ rows.map((row) => {
1793
+ if (row.kind === "change") {
1794
+ const c = row.change;
1795
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-fp": "memory-change", "data-type": c.type, children: [
1796
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "change-key", children: c.key }),
1797
+ c.type === "updated" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1798
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "change-old", children: fmt2(c.oldValue) }),
1799
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "change-new", children: fmt2(c.newValue) })
1800
+ ] }),
1801
+ c.type === "added" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "change-value", children: fmt2(c.newValue) }),
1802
+ c.type === "removed" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "change-value", children: fmt2(c.oldValue) })
1803
+ ] }, c.key);
1804
+ }
1805
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-fp": "memory-unchanged", children: [
1806
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "unchanged-key", children: row.key }),
1807
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-fp": "unchanged-value", children: fmt2(row.value) })
1808
+ ] }, row.key);
1809
+ })
1810
+ ] });
1811
+ }
1812
+ function ModeToggle({
1813
+ activeMode,
1814
+ onToggle,
1815
+ fs,
1816
+ unstyled
1817
+ }) {
1818
+ if (unstyled) {
1819
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { "data-fp": "mode-toggle", "data-mode": activeMode, onClick: onToggle, children: activeMode === "simple" ? "Dev" : "Simple" });
1820
+ }
1821
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1822
+ "div",
1823
+ {
1824
+ style: {
1825
+ display: "inline-flex",
1826
+ borderRadius: 6,
1827
+ border: `1px solid ${theme.border}`,
1828
+ overflow: "hidden",
1829
+ flexShrink: 0
1830
+ },
1831
+ "data-fp": "mode-toggle",
1832
+ children: ["simple", "dev"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1833
+ "button",
1834
+ {
1835
+ onClick: m !== activeMode ? onToggle : void 0,
1836
+ style: {
1837
+ padding: "4px 10px",
1838
+ fontSize: fs.small,
1839
+ fontWeight: m === activeMode ? 700 : 400,
1840
+ textTransform: "uppercase",
1841
+ letterSpacing: "0.05em",
1842
+ color: m === activeMode ? theme.textPrimary : theme.textMuted,
1843
+ background: m === activeMode ? theme.bgTertiary : "transparent",
1844
+ border: "none",
1845
+ cursor: m === activeMode ? "default" : "pointer"
1846
+ },
1847
+ children: m === "simple" ? "Simple" : "Dev"
1848
+ },
1849
+ m
1850
+ ))
1851
+ }
1852
+ );
1853
+ }
1854
+ function StageDetailPanel({
1855
+ snapshots,
1856
+ selectedIndex,
1857
+ mode: controlledMode,
1858
+ showToggle = false,
1859
+ onModeChange,
1860
+ size = "default",
1861
+ unstyled = false,
1862
+ className,
1863
+ style
1864
+ }) {
1865
+ const [internalMode, setInternalMode] = (0, import_react8.useState)(controlledMode ?? "simple");
1866
+ const activeMode = controlledMode ?? internalMode;
1867
+ const handleToggle = (0, import_react8.useCallback)(() => {
1868
+ const next = activeMode === "simple" ? "dev" : "simple";
1869
+ setInternalMode(next);
1870
+ onModeChange?.(next);
1871
+ }, [activeMode, onModeChange]);
1872
+ const snapshot = snapshots[selectedIndex];
1873
+ const prevMemory = selectedIndex > 0 ? snapshots[selectedIndex - 1]?.memory ?? null : null;
1874
+ const currMemory = snapshot?.memory ?? {};
1875
+ const changes = (0, import_react8.useMemo)(
1876
+ () => computeChanges(prevMemory, currMemory),
1877
+ [prevMemory, currMemory]
1878
+ );
1879
+ const fs = fontSize[size];
1880
+ const pad = padding[size];
1881
+ if (!snapshot) {
1882
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className, style, "data-fp": "stage-detail-panel", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: unstyled ? {} : { color: theme.textMuted, fontSize: fs.body, fontStyle: "italic", padding: pad }, children: "No stage selected" }) });
1883
+ }
1884
+ if (unstyled) {
1885
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className, style, "data-fp": "stage-detail-panel", "data-mode": activeMode, children: [
1886
+ showToggle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ModeToggle, { activeMode, onToggle: handleToggle, fs, unstyled: true }),
1887
+ activeMode === "simple" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UnstyledSimpleView, { snapshot }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UnstyledDevView, { snapshot, changes, currMemory })
1888
+ ] });
1889
+ }
1890
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1891
+ "div",
1892
+ {
1893
+ className,
1894
+ style: {
1895
+ padding: pad,
1896
+ fontFamily: theme.fontSans,
1897
+ overflow: "auto",
1898
+ ...style
1899
+ },
1900
+ "data-fp": "stage-detail-panel",
1901
+ "data-mode": activeMode,
1902
+ children: [
1903
+ showToggle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", justifyContent: "flex-end", marginBottom: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ModeToggle, { activeMode, onToggle: handleToggle, fs, unstyled: false }) }),
1904
+ activeMode === "simple" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SimpleView, { snapshot, fs, pad }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DevView, { snapshot, changes, currMemory, fs, pad })
1905
+ ]
1906
+ }
1907
+ );
1908
+ }
1909
+
1910
+ // src/components/TimeTravelControls/TimeTravelControls.tsx
1911
+ var import_react9 = require("react");
1912
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1380
1913
  function TimeTravelControls({
1381
1914
  snapshots,
1382
1915
  selectedIndex,
@@ -1387,12 +1920,12 @@ function TimeTravelControls({
1387
1920
  className,
1388
1921
  style
1389
1922
  }) {
1390
- const [playing, setPlaying] = (0, import_react8.useState)(false);
1391
- const playRef = (0, import_react8.useRef)(null);
1923
+ const [playing, setPlaying] = (0, import_react9.useState)(false);
1924
+ const playRef = (0, import_react9.useRef)(null);
1392
1925
  const total = snapshots.length;
1393
1926
  const canPrev = selectedIndex > 0;
1394
1927
  const canNext = selectedIndex < total - 1;
1395
- (0, import_react8.useEffect)(() => {
1928
+ (0, import_react9.useEffect)(() => {
1396
1929
  if (!playing || !autoPlayable) return;
1397
1930
  if (selectedIndex >= total - 1) {
1398
1931
  setPlaying(false);
@@ -1410,7 +1943,7 @@ function TimeTravelControls({
1410
1943
  if (playRef.current) clearTimeout(playRef.current);
1411
1944
  };
1412
1945
  }, [playing, selectedIndex, snapshots, total, onIndexChange, autoPlayable]);
1413
- const togglePlay = (0, import_react8.useCallback)(() => {
1946
+ const togglePlay = (0, import_react9.useCallback)(() => {
1414
1947
  if (playing) {
1415
1948
  setPlaying(false);
1416
1949
  } else {
@@ -1420,8 +1953,8 @@ function TimeTravelControls({
1420
1953
  }, [playing, selectedIndex, total, onIndexChange]);
1421
1954
  const fs = fontSize[size];
1422
1955
  if (unstyled) {
1423
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className, style, "data-fp": "time-travel-controls", children: [
1424
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1956
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, style, "data-fp": "time-travel-controls", children: [
1957
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1425
1958
  "button",
1426
1959
  {
1427
1960
  "data-fp": "tt-prev",
@@ -1433,8 +1966,8 @@ function TimeTravelControls({
1433
1966
  children: "Prev"
1434
1967
  }
1435
1968
  ),
1436
- autoPlayable && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { "data-fp": "tt-play", onClick: togglePlay, children: playing ? "Pause" : "Play" }),
1437
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1969
+ autoPlayable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "data-fp": "tt-play", onClick: togglePlay, children: playing ? "Pause" : "Play" }),
1970
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1438
1971
  "button",
1439
1972
  {
1440
1973
  "data-fp": "tt-next",
@@ -1446,7 +1979,7 @@ function TimeTravelControls({
1446
1979
  children: "Next"
1447
1980
  }
1448
1981
  ),
1449
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1982
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1450
1983
  "button",
1451
1984
  {
1452
1985
  "data-fp": "tt-tick",
@@ -1474,7 +2007,7 @@ function TimeTravelControls({
1474
2007
  opacity: disabled ? 0.5 : 1,
1475
2008
  flexShrink: 0
1476
2009
  });
1477
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2010
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1478
2011
  "div",
1479
2012
  {
1480
2013
  className,
@@ -1490,7 +2023,7 @@ function TimeTravelControls({
1490
2023
  },
1491
2024
  "data-fp": "time-travel-controls",
1492
2025
  children: [
1493
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2026
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1494
2027
  "button",
1495
2028
  {
1496
2029
  style: btnStyle(!canPrev || playing),
@@ -1502,7 +2035,7 @@ function TimeTravelControls({
1502
2035
  children: "\u25C0"
1503
2036
  }
1504
2037
  ),
1505
- autoPlayable && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2038
+ autoPlayable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1506
2039
  "button",
1507
2040
  {
1508
2041
  onClick: togglePlay,
@@ -1524,7 +2057,7 @@ function TimeTravelControls({
1524
2057
  children: playing ? "\u23F8" : "\u25B6"
1525
2058
  }
1526
2059
  ),
1527
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2060
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1528
2061
  "button",
1529
2062
  {
1530
2063
  style: btnStyle(!canNext || playing),
@@ -1536,7 +2069,7 @@ function TimeTravelControls({
1536
2069
  children: "\u25B6"
1537
2070
  }
1538
2071
  ),
1539
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2072
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1540
2073
  "div",
1541
2074
  {
1542
2075
  style: {
@@ -1549,7 +2082,7 @@ function TimeTravelControls({
1549
2082
  children: snapshots.map((snap, i) => {
1550
2083
  const isActive = i === selectedIndex;
1551
2084
  const isDone = i < selectedIndex;
1552
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2085
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1553
2086
  "button",
1554
2087
  {
1555
2088
  onClick: () => {
@@ -1579,8 +2112,8 @@ function TimeTravelControls({
1579
2112
  }
1580
2113
 
1581
2114
  // src/components/ExplainableShell/ExplainableShell.tsx
1582
- var import_react9 = require("react");
1583
- var import_jsx_runtime10 = require("react/jsx-runtime");
2115
+ var import_react10 = require("react");
2116
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1584
2117
  function ExplainableShell({
1585
2118
  snapshots,
1586
2119
  resultData,
@@ -1595,14 +2128,14 @@ function ExplainableShell({
1595
2128
  className,
1596
2129
  style
1597
2130
  }) {
1598
- const [activeTab, setActiveTab] = (0, import_react9.useState)(defaultTab ?? tabs[0]);
1599
- const [snapshotIdx, setSnapshotIdx] = (0, import_react9.useState)(0);
2131
+ const [activeTab, setActiveTab] = (0, import_react10.useState)(defaultTab ?? tabs[0]);
2132
+ const [snapshotIdx, setSnapshotIdx] = (0, import_react10.useState)(0);
1600
2133
  const fs = fontSize[size];
1601
2134
  const pad = padding[size];
1602
- const handleSnapshotChange = (0, import_react9.useCallback)((idx) => {
2135
+ const handleSnapshotChange = (0, import_react10.useCallback)((idx) => {
1603
2136
  setSnapshotIdx(Math.max(0, Math.min(idx, snapshots.length - 1)));
1604
2137
  }, [snapshots.length]);
1605
- const revealedCount = (0, import_react9.useMemo)(() => {
2138
+ const revealedCount = (0, import_react10.useMemo)(() => {
1606
2139
  if (snapshots.length === 0 || narrative.length === 0) return narrative.length;
1607
2140
  const boundaries = [];
1608
2141
  for (let i = 0; i < narrative.length; i++) {
@@ -1633,8 +2166,8 @@ function ExplainableShell({
1633
2166
  "ai-compatible": "AI-Compatible"
1634
2167
  };
1635
2168
  if (unstyled) {
1636
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, style, "data-fp": "explainable-shell", children: [
1637
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-fp": "shell-tabs", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2169
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, style, "data-fp": "explainable-shell", children: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-fp": "shell-tabs", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1638
2171
  "button",
1639
2172
  {
1640
2173
  "data-fp": "shell-tab",
@@ -1644,8 +2177,8 @@ function ExplainableShell({
1644
2177
  },
1645
2178
  tab
1646
2179
  )) }),
1647
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { "data-fp": "shell-content", "data-tab": activeTab, children: [
1648
- activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2180
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-fp": "shell-content", "data-tab": activeTab, children: [
2181
+ activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1649
2182
  ResultPanel,
1650
2183
  {
1651
2184
  data: resultData ?? null,
@@ -1654,8 +2187,8 @@ function ExplainableShell({
1654
2187
  unstyled: true
1655
2188
  }
1656
2189
  ),
1657
- activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1658
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2190
+ activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2191
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1659
2192
  TimeTravelControls,
1660
2193
  {
1661
2194
  snapshots,
@@ -1665,9 +2198,9 @@ function ExplainableShell({
1665
2198
  }
1666
2199
  ),
1667
2200
  renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
1668
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MemoryInspector, { snapshots, selectedIndex: snapshotIdx, unstyled: true }),
1669
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ScopeDiff, { previous: prevMemory, current: currMemory, unstyled: true }),
1670
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2201
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MemoryInspector, { snapshots, selectedIndex: snapshotIdx, unstyled: true }),
2202
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScopeDiff, { previous: prevMemory, current: currMemory, unstyled: true }),
2203
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1671
2204
  GanttTimeline,
1672
2205
  {
1673
2206
  snapshots,
@@ -1677,8 +2210,8 @@ function ExplainableShell({
1677
2210
  }
1678
2211
  )
1679
2212
  ] }),
1680
- activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1681
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2213
+ activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2214
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1682
2215
  TimeTravelControls,
1683
2216
  {
1684
2217
  snapshots,
@@ -1688,7 +2221,7 @@ function ExplainableShell({
1688
2221
  }
1689
2222
  ),
1690
2223
  renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
1691
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2224
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1692
2225
  NarrativeTrace,
1693
2226
  {
1694
2227
  narrative,
@@ -1700,7 +2233,7 @@ function ExplainableShell({
1700
2233
  ] })
1701
2234
  ] });
1702
2235
  }
1703
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2236
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1704
2237
  "div",
1705
2238
  {
1706
2239
  className,
@@ -1716,7 +2249,7 @@ function ExplainableShell({
1716
2249
  },
1717
2250
  "data-fp": "explainable-shell",
1718
2251
  children: [
1719
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2252
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1720
2253
  "div",
1721
2254
  {
1722
2255
  style: {
@@ -1728,7 +2261,7 @@ function ExplainableShell({
1728
2261
  },
1729
2262
  children: tabs.map((tab) => {
1730
2263
  const active = tab === activeTab;
1731
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2264
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1732
2265
  "button",
1733
2266
  {
1734
2267
  onClick: () => setActiveTab(tab),
@@ -1752,8 +2285,8 @@ function ExplainableShell({
1752
2285
  })
1753
2286
  }
1754
2287
  ),
1755
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }, children: [
1756
- activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2288
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }, children: [
2289
+ activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1757
2290
  ResultPanel,
1758
2291
  {
1759
2292
  data: resultData ?? null,
@@ -1762,8 +2295,8 @@ function ExplainableShell({
1762
2295
  size
1763
2296
  }
1764
2297
  ),
1765
- activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1766
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2298
+ activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2299
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1767
2300
  TimeTravelControls,
1768
2301
  {
1769
2302
  snapshots,
@@ -1772,9 +2305,9 @@ function ExplainableShell({
1772
2305
  size
1773
2306
  }
1774
2307
  ),
1775
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
1776
- renderFlowchart && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { flex: 1, overflow: "hidden", borderRight: `1px solid ${theme.border}` }, children: renderFlowchart({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }) }),
1777
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2308
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2309
+ renderFlowchart && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { flex: 1, overflow: "hidden", borderRight: `1px solid ${theme.border}` }, children: renderFlowchart({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }) }),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1778
2311
  "div",
1779
2312
  {
1780
2313
  style: {
@@ -1785,7 +2318,7 @@ function ExplainableShell({
1785
2318
  flexDirection: "column"
1786
2319
  },
1787
2320
  children: [
1788
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2321
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1789
2322
  MemoryInspector,
1790
2323
  {
1791
2324
  snapshots,
@@ -1793,7 +2326,7 @@ function ExplainableShell({
1793
2326
  size
1794
2327
  }
1795
2328
  ),
1796
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2329
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1797
2330
  ScopeDiff,
1798
2331
  {
1799
2332
  previous: prevMemory,
@@ -1806,7 +2339,7 @@ function ExplainableShell({
1806
2339
  }
1807
2340
  )
1808
2341
  ] }),
1809
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { borderTop: `1px solid ${theme.border}`, flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2342
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}`, flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1810
2343
  GanttTimeline,
1811
2344
  {
1812
2345
  snapshots,
@@ -1816,8 +2349,8 @@ function ExplainableShell({
1816
2349
  }
1817
2350
  ) })
1818
2351
  ] }),
1819
- activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1820
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2352
+ activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2353
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1821
2354
  TimeTravelControls,
1822
2355
  {
1823
2356
  snapshots,
@@ -1826,9 +2359,9 @@ function ExplainableShell({
1826
2359
  size
1827
2360
  }
1828
2361
  ),
1829
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
1830
- renderFlowchart && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { flex: 1, overflow: "hidden", borderRight: `1px solid ${theme.border}` }, children: renderFlowchart({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }) }),
1831
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2362
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2363
+ renderFlowchart && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { flex: 1, overflow: "hidden", borderRight: `1px solid ${theme.border}` }, children: renderFlowchart({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }) }),
2364
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1832
2365
  NarrativeTrace,
1833
2366
  {
1834
2367
  narrative,
@@ -1848,40 +2381,276 @@ function ExplainableShell({
1848
2381
  );
1849
2382
  }
1850
2383
 
2384
+ // src/components/FlowchartView/SubflowTree.tsx
2385
+ var import_react11 = require("react");
2386
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2387
+ function specToTree(node) {
2388
+ const entries = [];
2389
+ const seen = /* @__PURE__ */ new Set();
2390
+ function walk(n) {
2391
+ const id = n.name || n.id || "";
2392
+ if (seen.has(id)) return;
2393
+ seen.add(id);
2394
+ const entry = {
2395
+ name: n.name,
2396
+ description: n.description,
2397
+ subflowId: n.subflowId,
2398
+ isSubflow: !!n.isSubflowRoot
2399
+ };
2400
+ if (n.isSubflowRoot && n.subflowStructure) {
2401
+ entry.children = specToTree(n.subflowStructure);
2402
+ }
2403
+ entries.push(entry);
2404
+ if (n.children) {
2405
+ for (const child of n.children) {
2406
+ walk(child);
2407
+ }
2408
+ }
2409
+ if (n.next) {
2410
+ walk(n.next);
2411
+ }
2412
+ }
2413
+ walk(node);
2414
+ return entries;
2415
+ }
2416
+ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2417
+ entry,
2418
+ depth,
2419
+ activeStage,
2420
+ doneStages,
2421
+ onNodeSelect
2422
+ }) {
2423
+ const [expanded, setExpanded] = (0, import_react11.useState)(true);
2424
+ const hasChildren = entry.children && entry.children.length > 0;
2425
+ const isActive = activeStage === entry.name;
2426
+ const isDone = doneStages?.has(entry.name);
2427
+ const handleClick = (0, import_react11.useCallback)(() => {
2428
+ if (hasChildren) {
2429
+ setExpanded((prev) => !prev);
2430
+ }
2431
+ onNodeSelect?.(entry.name, !!entry.isSubflow);
2432
+ }, [hasChildren, onNodeSelect, entry.name, entry.isSubflow]);
2433
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2434
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2435
+ "button",
2436
+ {
2437
+ onClick: handleClick,
2438
+ "data-fp": "subflow-tree-node",
2439
+ style: {
2440
+ display: "flex",
2441
+ alignItems: "center",
2442
+ gap: 6,
2443
+ width: "100%",
2444
+ border: "none",
2445
+ background: isActive ? `color-mix(in srgb, ${theme.primary} 15%, transparent)` : "transparent",
2446
+ cursor: "pointer",
2447
+ padding: `4px 8px 4px ${8 + depth * 16}px`,
2448
+ fontFamily: theme.fontSans,
2449
+ fontSize: 12,
2450
+ textAlign: "left",
2451
+ borderRadius: 4,
2452
+ transition: "background 0.15s"
2453
+ },
2454
+ onMouseEnter: (e) => {
2455
+ if (!isActive) {
2456
+ e.currentTarget.style.background = `color-mix(in srgb, ${theme.textMuted} 10%, transparent)`;
2457
+ }
2458
+ },
2459
+ onMouseLeave: (e) => {
2460
+ if (!isActive) {
2461
+ e.currentTarget.style.background = "transparent";
2462
+ }
2463
+ },
2464
+ children: [
2465
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2466
+ "span",
2467
+ {
2468
+ style: {
2469
+ fontSize: 10,
2470
+ color: theme.textMuted,
2471
+ width: 12,
2472
+ textAlign: "center",
2473
+ flexShrink: 0,
2474
+ transition: "transform 0.15s",
2475
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
2476
+ display: "inline-block"
2477
+ },
2478
+ children: "\u25B6"
2479
+ }
2480
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { width: 12, flexShrink: 0 } }),
2481
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2482
+ "span",
2483
+ {
2484
+ style: {
2485
+ width: 6,
2486
+ height: 6,
2487
+ borderRadius: "50%",
2488
+ flexShrink: 0,
2489
+ background: isActive ? theme.primary : isDone ? theme.success : theme.border
2490
+ }
2491
+ }
2492
+ ),
2493
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
2494
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2495
+ "span",
2496
+ {
2497
+ style: {
2498
+ color: isActive ? theme.primary : isDone ? theme.textPrimary : theme.textSecondary,
2499
+ fontWeight: isActive ? 600 : entry.isSubflow ? 500 : 400,
2500
+ whiteSpace: "nowrap",
2501
+ overflow: "hidden",
2502
+ textOverflow: "ellipsis"
2503
+ },
2504
+ children: [
2505
+ entry.name,
2506
+ entry.isSubflow && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
2507
+ ]
2508
+ }
2509
+ ),
2510
+ entry.description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2511
+ "span",
2512
+ {
2513
+ style: {
2514
+ color: theme.textMuted,
2515
+ fontSize: 10,
2516
+ whiteSpace: "nowrap",
2517
+ overflow: "hidden",
2518
+ textOverflow: "ellipsis"
2519
+ },
2520
+ children: entry.description
2521
+ }
2522
+ )
2523
+ ] })
2524
+ ]
2525
+ }
2526
+ ),
2527
+ hasChildren && expanded && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { children: entry.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2528
+ TreeNode2,
2529
+ {
2530
+ entry: child,
2531
+ depth: depth + 1,
2532
+ activeStage,
2533
+ doneStages,
2534
+ onNodeSelect
2535
+ },
2536
+ `${child.name}-${i}`
2537
+ )) })
2538
+ ] });
2539
+ });
2540
+ var SectionLabel = (0, import_react11.memo)(function SectionLabel2({ children }) {
2541
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2542
+ "div",
2543
+ {
2544
+ style: {
2545
+ padding: "4px 12px 8px",
2546
+ fontSize: 10,
2547
+ fontWeight: 600,
2548
+ textTransform: "uppercase",
2549
+ letterSpacing: "0.05em",
2550
+ color: theme.textMuted
2551
+ },
2552
+ children
2553
+ }
2554
+ );
2555
+ });
2556
+ var SubflowTree = (0, import_react11.memo)(function SubflowTree2({
2557
+ spec,
2558
+ activeStage,
2559
+ doneStages,
2560
+ onNodeSelect,
2561
+ unstyled = false,
2562
+ className,
2563
+ style
2564
+ }) {
2565
+ const tree = (0, import_react11.useMemo)(() => specToTree(spec), [spec]);
2566
+ const mainStages = (0, import_react11.useMemo)(() => tree.filter((e) => !e.isSubflow), [tree]);
2567
+ const subflowStages = (0, import_react11.useMemo)(() => tree.filter((e) => e.isSubflow), [tree]);
2568
+ const renderEntries = (entries) => entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2569
+ TreeNode,
2570
+ {
2571
+ entry,
2572
+ depth: 0,
2573
+ activeStage,
2574
+ doneStages,
2575
+ onNodeSelect
2576
+ },
2577
+ `${entry.name}-${i}`
2578
+ ));
2579
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2580
+ "div",
2581
+ {
2582
+ className,
2583
+ "data-fp": "subflow-tree",
2584
+ style: {
2585
+ ...unstyled ? {} : {
2586
+ fontFamily: theme.fontSans,
2587
+ fontSize: 12,
2588
+ background: theme.bgPrimary,
2589
+ borderRight: `1px solid ${theme.border}`,
2590
+ overflowY: "auto",
2591
+ overflowX: "hidden",
2592
+ padding: "8px 0"
2593
+ },
2594
+ ...style
2595
+ },
2596
+ children: [
2597
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Flowchart" }),
2598
+ renderEntries(mainStages),
2599
+ subflowStages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2600
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { height: 1, background: theme.border, margin: "8px 12px" } }),
2601
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Subflows" }),
2602
+ renderEntries(subflowStages)
2603
+ ] })
2604
+ ]
2605
+ }
2606
+ );
2607
+ });
2608
+
1851
2609
  // src/adapters/fromRuntimeSnapshot.ts
1852
2610
  function toVisualizationSnapshots(runtime) {
1853
2611
  const snapshots = [];
1854
- flattenTree(runtime.executionTree, snapshots, runtime.sharedState);
2612
+ flattenTree(runtime.executionTree, snapshots, runtime.sharedState, 0, runtime.subflowResults, {});
1855
2613
  return snapshots;
1856
2614
  }
1857
- function flattenTree(node, out, sharedState, accumulatedMs = 0) {
2615
+ function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults, cumulativeMemory = {}) {
1858
2616
  const durationMs = typeof node.metrics?.durationMs === "number" ? node.metrics.durationMs : 1;
1859
2617
  const startMs = accumulatedMs;
1860
2618
  const narrative = buildNarrative(node);
1861
- const memory = {};
1862
- if (node.logs) {
1863
- Object.assign(memory, node.logs);
2619
+ const memory = { ...cumulativeMemory };
2620
+ if (node.stageWrites) {
2621
+ for (const [key, value] of Object.entries(node.stageWrites)) {
2622
+ if (value === void 0) {
2623
+ delete memory[key];
2624
+ } else {
2625
+ memory[key] = value;
2626
+ }
2627
+ }
1864
2628
  }
2629
+ const stageId = node.name || node.id;
2630
+ const sfResult = subflowResults?.[node.subflowId ?? stageId];
1865
2631
  out.push({
1866
- stageName: node.id,
1867
- stageLabel: node.name || node.id,
2632
+ stageName: stageId,
2633
+ stageLabel: stageId,
1868
2634
  memory,
1869
2635
  narrative,
1870
2636
  startMs,
1871
2637
  durationMs,
1872
- status: "done"
2638
+ status: "done",
2639
+ ...node.description ? { description: node.description } : void 0,
2640
+ ...node.subflowId ? { subflowId: node.subflowId } : void 0,
2641
+ ...sfResult ? { subflowResult: sfResult } : void 0
1873
2642
  });
1874
2643
  let nextMs = startMs + durationMs;
1875
2644
  if (node.children && node.children.length > 0) {
1876
2645
  let maxChildEnd = nextMs;
1877
2646
  for (const child of node.children) {
1878
- const childEnd = flattenTree(child, out, sharedState, nextMs);
2647
+ const childEnd = flattenTree(child, out, sharedState, nextMs, subflowResults, memory);
1879
2648
  maxChildEnd = Math.max(maxChildEnd, childEnd);
1880
2649
  }
1881
2650
  nextMs = maxChildEnd;
1882
2651
  }
1883
2652
  if (node.next) {
1884
- nextMs = flattenTree(node.next, out, sharedState, nextMs);
2653
+ nextMs = flattenTree(node.next, out, sharedState, nextMs, subflowResults, memory);
1885
2654
  }
1886
2655
  return nextMs;
1887
2656
  }
@@ -1890,8 +2659,8 @@ function buildNarrative(node) {
1890
2659
  if (node.name) {
1891
2660
  parts.push(`Stage "${node.name}" executed.`);
1892
2661
  }
1893
- if (node.logs && Object.keys(node.logs).length > 0) {
1894
- const keys = Object.keys(node.logs);
2662
+ if (node.stageWrites && Object.keys(node.stageWrites).length > 0) {
2663
+ const keys = Object.keys(node.stageWrites);
1895
2664
  parts.push(`Wrote ${keys.length} key(s): ${keys.join(", ")}.`);
1896
2665
  }
1897
2666
  if (node.errors && Object.keys(node.errors).length > 0) {
@@ -1915,7 +2684,9 @@ function createSnapshots(stages) {
1915
2684
  narrative: s.narrative ?? `${s.label ?? s.name} completed.`,
1916
2685
  startMs: accMs,
1917
2686
  durationMs: duration,
1918
- status: "done"
2687
+ status: "done",
2688
+ ...s.description ? { description: s.description } : void 0,
2689
+ ...s.subflowId ? { subflowId: s.subflowId } : void 0
1919
2690
  };
1920
2691
  accMs += duration;
1921
2692
  return snap;
@@ -1932,6 +2703,8 @@ function createSnapshots(stages) {
1932
2703
  ResultPanel,
1933
2704
  ScopeDiff,
1934
2705
  SnapshotPanel,
2706
+ StageDetailPanel,
2707
+ SubflowTree,
1935
2708
  TimeTravelControls,
1936
2709
  coolDark,
1937
2710
  createSnapshots,