footprint-explainable-ui 0.6.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,18 +24,23 @@ __export(src_exports, {
24
24
  FootprintTheme: () => FootprintTheme,
25
25
  GanttTimeline: () => GanttTimeline,
26
26
  MemoryInspector: () => MemoryInspector,
27
+ MemoryPanel: () => MemoryPanel,
27
28
  NarrativeLog: () => NarrativeLog,
29
+ NarrativePanel: () => NarrativePanel,
28
30
  NarrativeTrace: () => NarrativeTrace,
29
31
  ResultPanel: () => ResultPanel,
30
32
  ScopeDiff: () => ScopeDiff,
31
33
  SnapshotPanel: () => SnapshotPanel,
32
34
  StageDetailPanel: () => StageDetailPanel,
35
+ StoryNarrative: () => StoryNarrative,
33
36
  SubflowTree: () => SubflowTree,
34
37
  TimeTravelControls: () => TimeTravelControls,
35
38
  coolDark: () => coolDark,
36
39
  coolLight: () => coolLight,
37
40
  createSnapshots: () => createSnapshots,
38
41
  defaultTokens: () => defaultTokens,
42
+ rawDefaults: () => rawDefaults,
43
+ subflowResultToSnapshots: () => subflowResultToSnapshots,
39
44
  themePresets: () => themePresets,
40
45
  toVisualizationSnapshots: () => toVisualizationSnapshots,
41
46
  tokensToCSSVars: () => tokensToCSSVars,
@@ -71,7 +76,7 @@ function tokensToCSSVars(tokens) {
71
76
  if (tokens.fontFamily?.mono) vars["--fp-font-mono"] = tokens.fontFamily.mono;
72
77
  return vars;
73
78
  }
74
- var defaultTokens = {
79
+ var rawDefaults = {
75
80
  colors: {
76
81
  primary: "#6366f1",
77
82
  success: "#22c55e",
@@ -91,6 +96,26 @@ var defaultTokens = {
91
96
  mono: "'JetBrains Mono', 'Fira Code', monospace"
92
97
  }
93
98
  };
99
+ var defaultTokens = {
100
+ colors: {
101
+ primary: `var(--fp-color-primary, ${rawDefaults.colors.primary})`,
102
+ success: `var(--fp-color-success, ${rawDefaults.colors.success})`,
103
+ error: `var(--fp-color-error, ${rawDefaults.colors.error})`,
104
+ warning: `var(--fp-color-warning, ${rawDefaults.colors.warning})`,
105
+ bgPrimary: `var(--fp-bg-primary, ${rawDefaults.colors.bgPrimary})`,
106
+ bgSecondary: `var(--fp-bg-secondary, ${rawDefaults.colors.bgSecondary})`,
107
+ bgTertiary: `var(--fp-bg-tertiary, ${rawDefaults.colors.bgTertiary})`,
108
+ textPrimary: `var(--fp-text-primary, ${rawDefaults.colors.textPrimary})`,
109
+ textSecondary: `var(--fp-text-secondary, ${rawDefaults.colors.textSecondary})`,
110
+ textMuted: `var(--fp-text-muted, ${rawDefaults.colors.textMuted})`,
111
+ border: `var(--fp-border, ${rawDefaults.colors.border})`
112
+ },
113
+ radius: `var(--fp-radius, ${rawDefaults.radius})`,
114
+ fontFamily: {
115
+ sans: `var(--fp-font-sans, ${rawDefaults.fontFamily.sans})`,
116
+ mono: `var(--fp-font-mono, ${rawDefaults.fontFamily.mono})`
117
+ }
118
+ };
94
119
 
95
120
  // src/theme/ThemeProvider.tsx
96
121
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -258,6 +283,7 @@ function MemoryInspector({
258
283
  className,
259
284
  style
260
285
  }) {
286
+ const cacheRef = (0, import_react3.useRef)(null);
261
287
  const { memory, newKeys } = (0, import_react3.useMemo)(() => {
262
288
  if (data) {
263
289
  return { memory: data, newKeys: /* @__PURE__ */ new Set() };
@@ -265,21 +291,37 @@ function MemoryInspector({
265
291
  if (!snapshots || snapshots.length === 0) {
266
292
  return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
267
293
  }
268
- const merged = {};
269
- for (let i = 0; i <= Math.min(selectedIndex, snapshots.length - 1); i++) {
270
- Object.assign(merged, snapshots[i]?.memory);
294
+ const safeIdx = Math.min(selectedIndex, snapshots.length - 1);
295
+ let merged;
296
+ const cache = cacheRef.current;
297
+ if (cache && cache.snapshots === snapshots && cache.index <= safeIdx) {
298
+ merged = { ...cache.accumulated };
299
+ for (let i = cache.index + 1; i <= safeIdx; i++) {
300
+ Object.assign(merged, snapshots[i]?.memory);
301
+ }
302
+ } else {
303
+ merged = {};
304
+ for (let i = 0; i <= safeIdx; i++) {
305
+ Object.assign(merged, snapshots[i]?.memory);
306
+ }
271
307
  }
308
+ cacheRef.current = { snapshots, index: safeIdx, accumulated: merged };
272
309
  const nk = /* @__PURE__ */ new Set();
273
- if (highlightNew && selectedIndex > 0) {
274
- const prev = {};
275
- for (let i = 0; i < selectedIndex; i++) {
276
- Object.assign(prev, snapshots[i]?.memory);
310
+ if (highlightNew && safeIdx > 0) {
311
+ let prev;
312
+ if (cache && cache.snapshots === snapshots && cache.index === safeIdx - 1) {
313
+ prev = cache.accumulated;
314
+ } else {
315
+ prev = {};
316
+ for (let i = 0; i < safeIdx; i++) {
317
+ Object.assign(prev, snapshots[i]?.memory);
318
+ }
277
319
  }
278
- const current = snapshots[selectedIndex]?.memory ?? {};
320
+ const current = snapshots[safeIdx]?.memory ?? {};
279
321
  for (const k of Object.keys(current)) {
280
322
  if (!(k in prev)) nk.add(k);
281
323
  }
282
- } else if (highlightNew && selectedIndex === 0 && snapshots[0]) {
324
+ } else if (highlightNew && safeIdx === 0 && snapshots[0]) {
283
325
  for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
284
326
  }
285
327
  return { memory: merged, newKeys: nk };
@@ -288,9 +330,9 @@ function MemoryInspector({
288
330
  const fs = fontSize[size];
289
331
  const pad = padding[size];
290
332
  if (unstyled) {
291
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style, "data-fp": "memory-inspector", children: [
333
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style, "data-fp": "memory-inspector", role: "region", "aria-label": "Memory state", children: [
292
334
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-fp": "memory-label", children: "Memory State" }),
293
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { "data-fp": "memory-json", children: JSON.stringify(memory, null, 2) })
335
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { "data-fp": "memory-json", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: JSON.stringify(memory, null, 2) }) })
294
336
  ] });
295
337
  }
296
338
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -303,6 +345,8 @@ function MemoryInspector({
303
345
  ...style
304
346
  },
305
347
  "data-fp": "memory-inspector",
348
+ role: "region",
349
+ "aria-label": "Memory state",
306
350
  children: [
307
351
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
308
352
  "span",
@@ -595,19 +639,32 @@ function NarrativeTrace({
595
639
  "data-fp": "narrative-header",
596
640
  "data-collapsible": group.steps.length > 0,
597
641
  "data-collapsed": collapsedSet.has(group.headerIdx),
642
+ role: group.steps.length > 0 ? "button" : void 0,
643
+ tabIndex: group.steps.length > 0 ? 0 : void 0,
644
+ "aria-expanded": group.steps.length > 0 ? !collapsedSet.has(group.headerIdx) : void 0,
645
+ "aria-label": `Stage ${gi + 1}, ${group.steps.length} steps${gi === lastIdx ? ", current" : ""}`,
598
646
  onClick: () => {
599
647
  if (group.steps.length > 0) toggle(group.headerIdx);
600
648
  onStageClick?.(group.headerIdx);
601
649
  },
650
+ onKeyDown: (e) => {
651
+ if (e.key === "Enter" || e.key === " ") {
652
+ e.preventDefault();
653
+ if (group.steps.length > 0) toggle(group.headerIdx);
654
+ onStageClick?.(group.headerIdx);
655
+ }
656
+ },
602
657
  children: group.header
603
658
  }
604
659
  ),
605
660
  !collapsedSet.has(group.headerIdx) && group.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-fp": "narrative-step", children: step.text }, step.idx))
606
661
  ] }, group.headerIdx)),
607
- futureGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-fp": "narrative-group", "data-future": true, children: [
608
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-fp": "narrative-header", children: group.header }),
609
- group.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-fp": "narrative-step", children: step.text }, `f-${step.idx}`))
610
- ] }, `f-${group.headerIdx}`))
662
+ futureGroups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-fp": "narrative-future-hint", children: [
663
+ futureGroups.length,
664
+ " more ",
665
+ futureGroups.length === 1 ? "stage" : "stages",
666
+ " ahead..."
667
+ ] })
611
668
  ] });
612
669
  }
613
670
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -637,10 +694,21 @@ function NarrativeTrace({
637
694
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
638
695
  "div",
639
696
  {
697
+ role: hasSteps ? "button" : void 0,
698
+ tabIndex: hasSteps ? 0 : void 0,
699
+ "aria-expanded": hasSteps ? !isCollapsed : void 0,
700
+ "aria-label": `Stage ${gi + 1}, ${group.steps.length} steps${isLatest ? ", current" : ", completed"}`,
640
701
  onClick: () => {
641
702
  if (hasSteps) toggle(group.headerIdx);
642
703
  onStageClick?.(group.headerIdx);
643
704
  },
705
+ onKeyDown: (e) => {
706
+ if (e.key === "Enter" || e.key === " ") {
707
+ e.preventDefault();
708
+ if (hasSteps) toggle(group.headerIdx);
709
+ onStageClick?.(group.headerIdx);
710
+ }
711
+ },
644
712
  style: {
645
713
  fontSize: fs.body,
646
714
  lineHeight: 1.7,
@@ -699,36 +767,18 @@ function NarrativeTrace({
699
767
  group.headerIdx
700
768
  );
701
769
  }),
702
- futureGroups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { opacity: 0.2 }, children: futureGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: 2 }, children: [
703
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
704
- "div",
705
- {
706
- style: {
707
- fontSize: fs.body,
708
- lineHeight: 1.7,
709
- color: theme.textMuted,
710
- padding: `4px ${pad - 4}px`,
711
- borderLeft: `3px solid ${theme.border}`,
712
- fontWeight: 600,
713
- paddingLeft: pad + 12
714
- },
715
- children: group.header
716
- }
717
- ),
718
- group.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
719
- "div",
720
- {
721
- style: {
722
- fontSize: fs.small,
723
- lineHeight: 1.6,
724
- color: theme.textMuted,
725
- padding: `2px ${pad - 4}px 2px ${pad + 20}px`
726
- },
727
- children: step.text
728
- },
729
- `f-${step.idx}`
730
- ))
731
- ] }, `f-${group.headerIdx}`)) })
770
+ futureGroups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
771
+ opacity: 0.3,
772
+ fontSize: fs.small,
773
+ color: theme.textMuted,
774
+ padding: `8px ${pad}px`,
775
+ fontStyle: "italic"
776
+ }, children: [
777
+ futureGroups.length,
778
+ " more ",
779
+ futureGroups.length === 1 ? "stage" : "stages",
780
+ " ahead..."
781
+ ] })
732
782
  ]
733
783
  }
734
784
  );
@@ -770,12 +820,15 @@ function GanttTimeline({
770
820
  }
771
821
  }, [selectedIndex, showAll]);
772
822
  if (unstyled) {
773
- 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)(
823
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style, "data-fp": "gantt-timeline", role: "listbox", "aria-label": "Execution timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
774
824
  "div",
775
825
  {
776
826
  "data-fp": "gantt-bar",
777
827
  "data-selected": idx === selectedIndex,
778
828
  "data-visible": idx <= selectedIndex,
829
+ role: "option",
830
+ "aria-selected": idx === selectedIndex,
831
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
779
832
  onClick: () => onSelect?.(idx),
780
833
  children: [
781
834
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
@@ -785,7 +838,7 @@ function GanttTimeline({
785
838
  ] })
786
839
  ]
787
840
  },
788
- snap.stageName
841
+ `${snap.stageName}-${idx}`
789
842
  )) });
790
843
  }
791
844
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
@@ -841,6 +894,8 @@ function GanttTimeline({
841
894
  "div",
842
895
  {
843
896
  ref: scrollContainerRef,
897
+ role: "listbox",
898
+ "aria-label": "Execution timeline",
844
899
  style: {
845
900
  marginTop: 8,
846
901
  display: "flex",
@@ -861,6 +916,9 @@ function GanttTimeline({
861
916
  "div",
862
917
  {
863
918
  ref: isSelected ? activeRowRef : void 0,
919
+ role: "option",
920
+ "aria-selected": isSelected,
921
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
864
922
  onClick: () => onSelect?.(idx),
865
923
  style: {
866
924
  display: "flex",
@@ -876,6 +934,7 @@ function GanttTimeline({
876
934
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
877
935
  "span",
878
936
  {
937
+ title: snap.stageLabel,
879
938
  style: {
880
939
  width: labelWidth,
881
940
  fontSize: fs.small,
@@ -935,7 +994,7 @@ function GanttTimeline({
935
994
  )
936
995
  ]
937
996
  },
938
- snap.stageName
997
+ `${snap.stageName}-${idx}`
939
998
  );
940
999
  })
941
1000
  }
@@ -1257,9 +1316,9 @@ function fmt(v2) {
1257
1316
  return String(v2);
1258
1317
  }
1259
1318
  var diffColors = {
1260
- added: { bg: "rgba(34,197,94,0.10)", fg: "#22c55e", icon: "+" },
1261
- removed: { bg: "rgba(239,68,68,0.10)", fg: "#ef4444", icon: "-" },
1262
- changed: { bg: "rgba(245,158,11,0.10)", fg: "#f59e0b", icon: "~" },
1319
+ added: { bg: `color-mix(in srgb, ${theme.success} 10%, transparent)`, fg: theme.success, icon: "+" },
1320
+ removed: { bg: `color-mix(in srgb, ${theme.error} 10%, transparent)`, fg: theme.error, icon: "-" },
1321
+ changed: { bg: `color-mix(in srgb, ${theme.warning} 10%, transparent)`, fg: theme.warning, icon: "~" },
1263
1322
  unchanged: { bg: "transparent", fg: "", icon: " " }
1264
1323
  };
1265
1324
  function ScopeDiff({
@@ -1996,49 +2055,80 @@ function TimeTravelControls({
1996
2055
  setPlaying(true);
1997
2056
  }
1998
2057
  }, [playing, selectedIndex, total, onIndexChange]);
2058
+ const handleKeyDown = (0, import_react10.useCallback)(
2059
+ (e) => {
2060
+ if (e.key === "ArrowLeft" && canPrev && !playing) {
2061
+ e.preventDefault();
2062
+ setPlaying(false);
2063
+ onIndexChange(selectedIndex - 1);
2064
+ } else if (e.key === "ArrowRight" && canNext && !playing) {
2065
+ e.preventDefault();
2066
+ setPlaying(false);
2067
+ onIndexChange(selectedIndex + 1);
2068
+ } else if (e.key === " " && autoPlayable) {
2069
+ e.preventDefault();
2070
+ togglePlay();
2071
+ }
2072
+ },
2073
+ [canPrev, canNext, playing, selectedIndex, onIndexChange, autoPlayable, togglePlay]
2074
+ );
1999
2075
  const fs = fontSize[size];
2000
2076
  if (unstyled) {
2001
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, style, "data-fp": "time-travel-controls", children: [
2002
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2003
- "button",
2004
- {
2005
- "data-fp": "tt-prev",
2006
- disabled: !canPrev || playing,
2007
- onClick: () => {
2008
- setPlaying(false);
2009
- onIndexChange(selectedIndex - 1);
2010
- },
2011
- children: "Prev"
2012
- }
2013
- ),
2014
- autoPlayable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "data-fp": "tt-play", onClick: togglePlay, children: playing ? "Pause" : "Play" }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2016
- "button",
2017
- {
2018
- "data-fp": "tt-next",
2019
- disabled: !canNext || playing,
2020
- onClick: () => {
2021
- setPlaying(false);
2022
- onIndexChange(selectedIndex + 1);
2023
- },
2024
- children: "Next"
2025
- }
2026
- ),
2027
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2028
- "button",
2029
- {
2030
- "data-fp": "tt-tick",
2031
- "data-active": i === selectedIndex,
2032
- "data-done": i < selectedIndex,
2033
- onClick: () => {
2034
- setPlaying(false);
2035
- onIndexChange(i);
2036
- },
2037
- title: snap.stageLabel
2038
- },
2039
- i
2040
- )) })
2041
- ] });
2077
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2078
+ "div",
2079
+ {
2080
+ className,
2081
+ style,
2082
+ "data-fp": "time-travel-controls",
2083
+ role: "toolbar",
2084
+ "aria-label": "Time travel controls",
2085
+ tabIndex: 0,
2086
+ onKeyDown: handleKeyDown,
2087
+ children: [
2088
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2089
+ "button",
2090
+ {
2091
+ "data-fp": "tt-prev",
2092
+ disabled: !canPrev || playing,
2093
+ onClick: () => {
2094
+ setPlaying(false);
2095
+ onIndexChange(selectedIndex - 1);
2096
+ },
2097
+ "aria-label": "Previous stage",
2098
+ children: "Prev"
2099
+ }
2100
+ ),
2101
+ autoPlayable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "data-fp": "tt-play", onClick: togglePlay, "aria-label": playing ? "Pause" : "Play", children: playing ? "Pause" : "Play" }),
2102
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2103
+ "button",
2104
+ {
2105
+ "data-fp": "tt-next",
2106
+ disabled: !canNext || playing,
2107
+ onClick: () => {
2108
+ setPlaying(false);
2109
+ onIndexChange(selectedIndex + 1);
2110
+ },
2111
+ "aria-label": "Next stage",
2112
+ children: "Next"
2113
+ }
2114
+ ),
2115
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2116
+ "button",
2117
+ {
2118
+ "data-fp": "tt-tick",
2119
+ "data-active": i === selectedIndex,
2120
+ "data-done": i < selectedIndex,
2121
+ onClick: () => {
2122
+ setPlaying(false);
2123
+ onIndexChange(i);
2124
+ },
2125
+ title: snap.stageLabel
2126
+ },
2127
+ i
2128
+ )) })
2129
+ ]
2130
+ }
2131
+ );
2042
2132
  }
2043
2133
  const btnStyle = (disabled) => ({
2044
2134
  background: theme.bgTertiary,
@@ -2067,6 +2157,10 @@ function TimeTravelControls({
2067
2157
  ...style
2068
2158
  },
2069
2159
  "data-fp": "time-travel-controls",
2160
+ role: "toolbar",
2161
+ "aria-label": "Time travel controls",
2162
+ tabIndex: 0,
2163
+ onKeyDown: handleKeyDown,
2070
2164
  children: [
2071
2165
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2072
2166
  "button",
@@ -2077,6 +2171,7 @@ function TimeTravelControls({
2077
2171
  setPlaying(false);
2078
2172
  onIndexChange(selectedIndex - 1);
2079
2173
  },
2174
+ "aria-label": "Previous stage",
2080
2175
  children: "\u25C0"
2081
2176
  }
2082
2177
  ),
@@ -2099,6 +2194,7 @@ function TimeTravelControls({
2099
2194
  flexShrink: 0
2100
2195
  },
2101
2196
  title: playing ? "Pause" : "Play",
2197
+ "aria-label": playing ? "Pause" : "Play",
2102
2198
  children: playing ? "\u23F8" : "\u25B6"
2103
2199
  }
2104
2200
  ),
@@ -2111,6 +2207,7 @@ function TimeTravelControls({
2111
2207
  setPlaying(false);
2112
2208
  onIndexChange(selectedIndex + 1);
2113
2209
  },
2210
+ "aria-label": "Next stage",
2114
2211
  children: "\u25B6"
2115
2212
  }
2116
2213
  ),
@@ -2157,125 +2254,146 @@ function TimeTravelControls({
2157
2254
  }
2158
2255
 
2159
2256
  // src/components/ExplainableShell/ExplainableShell.tsx
2160
- var import_react11 = require("react");
2257
+ var import_react15 = require("react");
2258
+
2259
+ // src/adapters/fromRuntimeSnapshot.ts
2260
+ function toVisualizationSnapshots(runtime, narrativeEntries) {
2261
+ const stageNarrativeMap = narrativeEntries?.length ? buildStageNarrativeMap(narrativeEntries) : /* @__PURE__ */ new Map();
2262
+ const snapshots = [];
2263
+ flattenTree(runtime.executionTree, snapshots, runtime.sharedState, 0, runtime.subflowResults, {}, stageNarrativeMap);
2264
+ return snapshots;
2265
+ }
2266
+ function buildStageNarrativeMap(entries) {
2267
+ const map = /* @__PURE__ */ new Map();
2268
+ let currentStageName;
2269
+ for (const entry of entries) {
2270
+ if (entry.stageName) {
2271
+ currentStageName = entry.stageName;
2272
+ }
2273
+ if (currentStageName) {
2274
+ if (!map.has(currentStageName)) {
2275
+ map.set(currentStageName, []);
2276
+ }
2277
+ const indent = " ".repeat(entry.depth);
2278
+ map.get(currentStageName).push(`${indent}${entry.text}`);
2279
+ }
2280
+ }
2281
+ return map;
2282
+ }
2283
+ function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults, cumulativeMemory = {}, stageNarrativeMap = /* @__PURE__ */ new Map()) {
2284
+ const durationMs = typeof node.metrics?.durationMs === "number" ? node.metrics.durationMs : 1;
2285
+ const startMs = accumulatedMs;
2286
+ const stageId = node.id || node.name || "unknown";
2287
+ const displayName = node.name || node.id || "unknown";
2288
+ const stageLines = stageNarrativeMap.get(stageId);
2289
+ let narrative;
2290
+ if (stageLines) {
2291
+ narrative = stageLines.join("\n");
2292
+ } else {
2293
+ const parts = [`${displayName} executed.`];
2294
+ if (node.description) parts.push(node.description);
2295
+ if (node.stageWrites) {
2296
+ const keys = Object.keys(node.stageWrites);
2297
+ if (keys.length > 0) parts.push(`Wrote: ${keys.join(", ")}`);
2298
+ }
2299
+ narrative = parts.join("\n");
2300
+ }
2301
+ const memory = { ...cumulativeMemory };
2302
+ if (node.stageWrites) {
2303
+ for (const [key, value] of Object.entries(node.stageWrites)) {
2304
+ if (value === void 0) {
2305
+ delete memory[key];
2306
+ } else {
2307
+ memory[key] = value;
2308
+ }
2309
+ }
2310
+ }
2311
+ const sfResult = subflowResults?.[node.subflowId ?? stageId];
2312
+ out.push({
2313
+ stageName: displayName,
2314
+ stageLabel: stageId,
2315
+ memory,
2316
+ narrative,
2317
+ startMs,
2318
+ durationMs,
2319
+ status: "done",
2320
+ ...node.description ? { description: node.description } : void 0,
2321
+ ...node.subflowId ? { subflowId: node.subflowId } : void 0,
2322
+ ...sfResult ? { subflowResult: sfResult } : void 0
2323
+ });
2324
+ let nextMs = startMs + durationMs;
2325
+ if (node.children && node.children.length > 0) {
2326
+ let maxChildEnd = nextMs;
2327
+ for (const child of node.children) {
2328
+ const childEnd = flattenTree(child, out, sharedState, nextMs, subflowResults, memory, stageNarrativeMap);
2329
+ maxChildEnd = Math.max(maxChildEnd, childEnd);
2330
+ }
2331
+ nextMs = maxChildEnd;
2332
+ }
2333
+ if (node.next) {
2334
+ nextMs = flattenTree(node.next, out, sharedState, nextMs, subflowResults, memory, stageNarrativeMap);
2335
+ }
2336
+ return nextMs;
2337
+ }
2338
+ function subflowResultToSnapshots(subflowResult, narrativeEntries) {
2339
+ if (!subflowResult || typeof subflowResult !== "object") return [];
2340
+ const sf = subflowResult;
2341
+ if (!sf.treeContext?.stageContexts) return [];
2342
+ const runtime = {
2343
+ sharedState: sf.treeContext.globalContext ?? {},
2344
+ executionTree: sf.treeContext.stageContexts,
2345
+ commitLog: sf.treeContext.history ?? []
2346
+ };
2347
+ const snapshots = toVisualizationSnapshots(runtime, narrativeEntries);
2348
+ const prefix = sf.subflowId ? `${sf.subflowId}/` : "";
2349
+ if (prefix) {
2350
+ for (const snap of snapshots) {
2351
+ if (snap.stageName.startsWith(prefix)) {
2352
+ snap.stageName = snap.stageName.slice(prefix.length);
2353
+ }
2354
+ if (snap.stageLabel.startsWith(prefix)) {
2355
+ snap.stageLabel = snap.stageLabel.slice(prefix.length);
2356
+ }
2357
+ }
2358
+ }
2359
+ return snapshots;
2360
+ }
2361
+ function createSnapshots(stages) {
2362
+ let accMs = 0;
2363
+ return stages.map((s) => {
2364
+ const duration = s.durationMs ?? 1;
2365
+ const snap = {
2366
+ stageName: s.name,
2367
+ stageLabel: s.label ?? s.name,
2368
+ memory: s.memory ?? {},
2369
+ narrative: s.narrative ?? `${s.label ?? s.name} completed.`,
2370
+ startMs: accMs,
2371
+ durationMs: duration,
2372
+ status: "done",
2373
+ ...s.description ? { description: s.description } : void 0,
2374
+ ...s.subflowId ? { subflowId: s.subflowId } : void 0
2375
+ };
2376
+ accMs += duration;
2377
+ return snap;
2378
+ });
2379
+ }
2380
+
2381
+ // src/components/MemoryPanel/MemoryPanel.tsx
2161
2382
  var import_jsx_runtime11 = require("react/jsx-runtime");
2162
- function ExplainableShell({
2383
+ function MemoryPanel({
2163
2384
  snapshots,
2164
- resultData,
2165
- logs = [],
2166
- narrative = [],
2167
- tabs = ["result", "explainable", "ai-compatible"],
2168
- defaultTab,
2169
- hideConsole = false,
2170
- renderFlowchart,
2385
+ selectedIndex,
2171
2386
  size = "default",
2172
2387
  unstyled = false,
2173
2388
  className,
2174
2389
  style
2175
2390
  }) {
2176
- const [activeTab, setActiveTab] = (0, import_react11.useState)(defaultTab ?? tabs[0]);
2177
- const [snapshotIdx, setSnapshotIdx] = (0, import_react11.useState)(0);
2178
- const fs = fontSize[size];
2179
- const pad = padding[size];
2180
- const handleSnapshotChange = (0, import_react11.useCallback)((idx) => {
2181
- setSnapshotIdx(Math.max(0, Math.min(idx, snapshots.length - 1)));
2182
- }, [snapshots.length]);
2183
- const revealedCount = (0, import_react11.useMemo)(() => {
2184
- if (snapshots.length === 0 || narrative.length === 0) return narrative.length;
2185
- const boundaries = [];
2186
- for (let i = 0; i < narrative.length; i++) {
2187
- const trimmed = narrative[i].trimStart();
2188
- if (trimmed.startsWith("Stage ") && !trimmed.match(/^Stage\s+\d+:\s*Step\s/) || trimmed.startsWith("[")) {
2189
- boundaries.push(i);
2190
- }
2191
- }
2192
- if (boundaries.length === 0) {
2193
- const ratio = (snapshotIdx + 1) / snapshots.length;
2194
- return Math.max(1, Math.ceil(narrative.length * ratio));
2195
- }
2196
- const groupsToShow = Math.max(
2197
- 1,
2198
- Math.min(
2199
- Math.floor((snapshotIdx + 1) / snapshots.length * boundaries.length) || 1,
2200
- boundaries.length
2201
- )
2202
- );
2203
- const endIdx = groupsToShow < boundaries.length ? boundaries[groupsToShow] : narrative.length;
2204
- return Math.max(1, endIdx);
2205
- }, [snapshots.length, snapshotIdx, narrative]);
2206
- const prevMemory = snapshotIdx > 0 ? snapshots[snapshotIdx - 1]?.memory : null;
2207
- const currMemory = snapshots[snapshotIdx]?.memory ?? {};
2208
- const tabLabels = {
2209
- result: "Result",
2210
- explainable: "Explainable",
2211
- "ai-compatible": "AI-Compatible"
2212
- };
2391
+ const prevMemory = selectedIndex > 0 ? snapshots[selectedIndex - 1]?.memory ?? null : null;
2392
+ const currMemory = snapshots[selectedIndex]?.memory ?? {};
2213
2393
  if (unstyled) {
2214
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, style, "data-fp": "explainable-shell", children: [
2215
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-fp": "shell-tabs", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2216
- "button",
2217
- {
2218
- "data-fp": "shell-tab",
2219
- "data-active": tab === activeTab,
2220
- onClick: () => setActiveTab(tab),
2221
- children: tabLabels[tab]
2222
- },
2223
- tab
2224
- )) }),
2225
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-fp": "shell-content", "data-tab": activeTab, children: [
2226
- activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2227
- ResultPanel,
2228
- {
2229
- data: resultData ?? null,
2230
- logs,
2231
- hideConsole,
2232
- unstyled: true
2233
- }
2234
- ),
2235
- activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2236
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2237
- TimeTravelControls,
2238
- {
2239
- snapshots,
2240
- selectedIndex: snapshotIdx,
2241
- onIndexChange: handleSnapshotChange,
2242
- unstyled: true
2243
- }
2244
- ),
2245
- renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
2246
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MemoryInspector, { snapshots, selectedIndex: snapshotIdx, unstyled: true }),
2247
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScopeDiff, { previous: prevMemory, current: currMemory, unstyled: true }),
2248
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2249
- GanttTimeline,
2250
- {
2251
- snapshots,
2252
- selectedIndex: snapshotIdx,
2253
- onSelect: handleSnapshotChange,
2254
- unstyled: true
2255
- }
2256
- )
2257
- ] }),
2258
- activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2259
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2260
- TimeTravelControls,
2261
- {
2262
- snapshots,
2263
- selectedIndex: snapshotIdx,
2264
- onIndexChange: handleSnapshotChange,
2265
- unstyled: true
2266
- }
2267
- ),
2268
- renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
2269
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2270
- NarrativeTrace,
2271
- {
2272
- narrative,
2273
- revealedCount,
2274
- unstyled: true
2275
- }
2276
- )
2277
- ] })
2278
- ] })
2394
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, style, "data-fp": "memory-panel", children: [
2395
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MemoryInspector, { snapshots, selectedIndex, unstyled: true }),
2396
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScopeDiff, { previous: prevMemory, current: currMemory, unstyled: true })
2279
2397
  ] });
2280
2398
  }
2281
2399
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -2283,156 +2401,251 @@ function ExplainableShell({
2283
2401
  {
2284
2402
  className,
2285
2403
  style: {
2286
- height: "100%",
2404
+ overflow: "auto",
2287
2405
  display: "flex",
2288
2406
  flexDirection: "column",
2289
- overflow: "hidden",
2290
- background: theme.bgPrimary,
2291
- color: theme.textPrimary,
2292
- fontFamily: theme.fontSans,
2293
2407
  ...style
2294
2408
  },
2295
- "data-fp": "explainable-shell",
2409
+ "data-fp": "memory-panel",
2296
2410
  children: [
2297
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2298
- "div",
2299
- {
2300
- style: {
2301
- display: "flex",
2302
- gap: 0,
2303
- borderBottom: `1px solid ${theme.border}`,
2304
- background: theme.bgSecondary,
2305
- flexShrink: 0
2306
- },
2307
- children: tabs.map((tab) => {
2308
- const active = tab === activeTab;
2309
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2310
- "button",
2311
- {
2312
- onClick: () => setActiveTab(tab),
2313
- style: {
2314
- padding: `${pad - 4}px ${pad}px`,
2315
- fontSize: fs.label,
2316
- fontWeight: active ? 700 : 500,
2317
- textTransform: "uppercase",
2318
- letterSpacing: "0.08em",
2319
- color: active ? theme.primary : theme.textMuted,
2320
- background: "transparent",
2321
- border: "none",
2322
- borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
2323
- cursor: "pointer",
2324
- transition: "all 0.15s ease"
2325
- },
2326
- children: tabLabels[tab]
2327
- },
2328
- tab
2329
- );
2330
- })
2331
- }
2332
- ),
2333
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }, children: [
2334
- activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2335
- ResultPanel,
2336
- {
2337
- data: resultData ?? null,
2338
- logs,
2339
- hideConsole,
2340
- size
2341
- }
2342
- ),
2343
- activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2344
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2345
- TimeTravelControls,
2346
- {
2347
- snapshots,
2348
- selectedIndex: snapshotIdx,
2349
- onIndexChange: handleSnapshotChange,
2350
- size
2351
- }
2352
- ),
2353
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2354
- 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 }) }),
2355
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2356
- "div",
2357
- {
2358
- style: {
2359
- width: renderFlowchart ? "40%" : "100%",
2360
- minWidth: 280,
2361
- overflow: "auto",
2362
- display: "flex",
2363
- flexDirection: "column"
2364
- },
2365
- children: [
2366
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2367
- MemoryInspector,
2368
- {
2369
- snapshots,
2370
- selectedIndex: snapshotIdx,
2371
- size
2372
- }
2373
- ),
2374
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2375
- ScopeDiff,
2376
- {
2377
- previous: prevMemory,
2378
- current: currMemory,
2379
- hideUnchanged: true,
2380
- size
2381
- }
2382
- ) })
2383
- ]
2384
- }
2385
- )
2386
- ] }),
2387
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}`, flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2388
- GanttTimeline,
2389
- {
2390
- snapshots,
2391
- selectedIndex: snapshotIdx,
2392
- onSelect: handleSnapshotChange,
2393
- size
2394
- }
2395
- ) })
2396
- ] }),
2397
- activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2398
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2399
- TimeTravelControls,
2400
- {
2401
- snapshots,
2402
- selectedIndex: snapshotIdx,
2403
- onIndexChange: handleSnapshotChange,
2404
- size
2405
- }
2406
- ),
2407
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2408
- 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 }) }),
2409
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2410
- NarrativeTrace,
2411
- {
2412
- narrative,
2413
- revealedCount,
2414
- size,
2415
- style: {
2416
- width: renderFlowchart ? "40%" : "100%",
2417
- minWidth: 280
2411
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MemoryInspector, { snapshots, selectedIndex, size }),
2412
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScopeDiff, { previous: prevMemory, current: currMemory, hideUnchanged: true, size }) })
2413
+ ]
2414
+ }
2415
+ );
2416
+ }
2417
+
2418
+ // src/components/NarrativePanel/NarrativePanel.tsx
2419
+ var import_react12 = require("react");
2420
+
2421
+ // src/components/StoryNarrative/StoryNarrative.tsx
2422
+ var import_react11 = require("react");
2423
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2424
+ var ENTRY_ICONS = {
2425
+ stage: { icon: "\u25B8", color: theme.primary, label: "Stage" },
2426
+ step: { icon: "\xB7", color: theme.textMuted, label: "Data operation" },
2427
+ condition: { icon: "\u25C7", color: theme.warning, label: "Decision" },
2428
+ fork: { icon: "\u2443", color: theme.primary, label: "Parallel" },
2429
+ subflow: { icon: "\u21B3", color: theme.textSecondary, label: "Subflow" },
2430
+ loop: { icon: "\u21BB", color: theme.warning, label: "Loop" },
2431
+ break: { icon: "\u25A0", color: theme.error, label: "Break" },
2432
+ error: { icon: "\u2717", color: theme.error, label: "Error" }
2433
+ };
2434
+ function StoryNarrative({
2435
+ entries,
2436
+ stageCount,
2437
+ size = "default",
2438
+ unstyled = false,
2439
+ className,
2440
+ style: outerStyle
2441
+ }) {
2442
+ const fs = fontSize[size];
2443
+ const pad = padding[size];
2444
+ const revealedCount = (0, import_react11.useMemo)(() => {
2445
+ let stagesSeen = 0;
2446
+ for (let i = 0; i < entries.length; i++) {
2447
+ const e = entries[i];
2448
+ const isBoundary = e.type === "stage" || e.type === "subflow" && e.text.startsWith("Entering");
2449
+ if (isBoundary) stagesSeen++;
2450
+ if (stagesSeen > stageCount) return i;
2451
+ }
2452
+ return entries.length;
2453
+ }, [entries, stageCount]);
2454
+ const revealed = entries.slice(0, revealedCount);
2455
+ const future = entries.slice(revealedCount);
2456
+ const latestRef = (0, import_react11.useRef)(null);
2457
+ (0, import_react11.useEffect)(() => {
2458
+ latestRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
2459
+ }, [revealed.length]);
2460
+ if (unstyled) {
2461
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className, style: outerStyle, "data-fp": "story-narrative", role: "log", children: revealed.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { "data-fp": "narrative-entry", "data-type": entry.type, children: entry.text }, i)) });
2462
+ }
2463
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2464
+ "div",
2465
+ {
2466
+ className,
2467
+ style: {
2468
+ flex: 1,
2469
+ overflow: "auto",
2470
+ padding: pad,
2471
+ fontFamily: theme.fontSans,
2472
+ ...outerStyle
2473
+ },
2474
+ "data-fp": "story-narrative",
2475
+ role: "log",
2476
+ "aria-label": "Execution narrative",
2477
+ children: [
2478
+ revealed.map((entry, i) => {
2479
+ const meta = ENTRY_ICONS[entry.type] ?? ENTRY_ICONS.step;
2480
+ const isStage = entry.type === "stage";
2481
+ const isDecision = entry.type === "condition";
2482
+ const isError = entry.type === "error";
2483
+ const isLast = i === revealed.length - 1;
2484
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2485
+ "div",
2486
+ {
2487
+ ref: isLast ? latestRef : void 0,
2488
+ style: {
2489
+ display: "flex",
2490
+ gap: 8,
2491
+ padding: isStage ? `${pad - 4}px 0` : `2px 0`,
2492
+ marginLeft: entry.depth * 16,
2493
+ borderBottom: isStage ? `1px solid ${theme.border}` : void 0,
2494
+ marginTop: isStage && i > 0 ? 8 : 0
2495
+ },
2496
+ children: [
2497
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2498
+ "span",
2499
+ {
2500
+ style: {
2501
+ color: meta.color,
2502
+ fontWeight: 700,
2503
+ fontSize: isStage ? fs.body : fs.small,
2504
+ width: 16,
2505
+ textAlign: "center",
2506
+ flexShrink: 0
2507
+ },
2508
+ title: meta.label,
2509
+ "aria-label": meta.label,
2510
+ children: meta.icon
2418
2511
  }
2419
- }
2420
- )
2421
- ] })
2422
- ] })
2512
+ ),
2513
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2514
+ "span",
2515
+ {
2516
+ style: {
2517
+ fontSize: isStage ? fs.body : fs.small,
2518
+ fontWeight: isStage ? 600 : 400,
2519
+ color: isError ? theme.error : isDecision ? theme.warning : isStage ? theme.textPrimary : theme.textSecondary,
2520
+ lineHeight: 1.6,
2521
+ fontFamily: entry.type === "step" ? theme.fontMono : theme.fontSans
2522
+ },
2523
+ children: entry.text
2524
+ }
2525
+ )
2526
+ ]
2527
+ },
2528
+ i
2529
+ );
2530
+ }),
2531
+ future.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: {
2532
+ opacity: 0.3,
2533
+ fontSize: fs.small,
2534
+ color: theme.textMuted,
2535
+ padding: `8px 0`,
2536
+ fontStyle: "italic"
2537
+ }, children: [
2538
+ future.length,
2539
+ " more ",
2540
+ future.length === 1 ? "entry" : "entries",
2541
+ " ahead..."
2423
2542
  ] })
2424
2543
  ]
2425
2544
  }
2426
2545
  );
2427
2546
  }
2428
2547
 
2548
+ // src/components/NarrativePanel/NarrativePanel.tsx
2549
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2550
+ function NarrativePanel({
2551
+ snapshots,
2552
+ selectedIndex,
2553
+ narrativeEntries,
2554
+ narrative: narrativeProp,
2555
+ size = "default",
2556
+ unstyled = false,
2557
+ className,
2558
+ style
2559
+ }) {
2560
+ const fs = fontSize[size];
2561
+ const pad = padding[size];
2562
+ const narrative = (0, import_react12.useMemo)(() => {
2563
+ if (narrativeProp && narrativeProp.length > 0) return narrativeProp;
2564
+ const lines = [];
2565
+ for (const snap of snapshots) {
2566
+ const stageLines = (snap.narrative ?? "").split("\n").filter(Boolean);
2567
+ lines.push(...stageLines);
2568
+ }
2569
+ return lines;
2570
+ }, [narrativeProp, snapshots]);
2571
+ const revealedCount = (0, import_react12.useMemo)(() => {
2572
+ if (snapshots.length === 0 || narrative.length === 0) return narrative.length;
2573
+ const stageBoundaries = [];
2574
+ for (let i = 0; i < narrative.length; i++) {
2575
+ const trimmed = narrative[i].trimStart();
2576
+ if (trimmed.startsWith("Stage ") && !trimmed.match(/^Stage\s+\d+:\s*Step\s/)) {
2577
+ stageBoundaries.push(i);
2578
+ }
2579
+ }
2580
+ if (stageBoundaries.length === 0) {
2581
+ const ratio = (selectedIndex + 1) / snapshots.length;
2582
+ return Math.max(1, Math.ceil(narrative.length * ratio));
2583
+ }
2584
+ const groupsToShow = Math.min(selectedIndex + 1, stageBoundaries.length);
2585
+ const endIdx = groupsToShow < stageBoundaries.length ? stageBoundaries[groupsToShow] : narrative.length;
2586
+ return Math.max(1, endIdx);
2587
+ }, [snapshots.length, selectedIndex, narrative]);
2588
+ const hasStructured = narrativeEntries && narrativeEntries.length > 0;
2589
+ if (unstyled) {
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className, style, "data-fp": "narrative-panel", children: hasStructured ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(StoryNarrative, { entries: narrativeEntries, stageCount: selectedIndex + 1, unstyled: true }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NarrativeTrace, { narrative, revealedCount, unstyled: true }) });
2591
+ }
2592
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2593
+ "div",
2594
+ {
2595
+ className,
2596
+ style: {
2597
+ overflow: "auto",
2598
+ display: "flex",
2599
+ flexDirection: "column",
2600
+ ...style
2601
+ },
2602
+ "data-fp": "narrative-panel",
2603
+ children: [
2604
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2605
+ "div",
2606
+ {
2607
+ style: {
2608
+ padding: `${pad - 4}px ${pad}px`,
2609
+ fontSize: fs.small,
2610
+ color: theme.textMuted,
2611
+ fontStyle: "italic",
2612
+ borderBottom: `1px solid ${theme.border}`,
2613
+ flexShrink: 0
2614
+ },
2615
+ children: "What happened at each stage, what data flowed, what decisions were made, and why."
2616
+ }
2617
+ ),
2618
+ hasStructured ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2619
+ StoryNarrative,
2620
+ {
2621
+ entries: narrativeEntries,
2622
+ stageCount: selectedIndex + 1,
2623
+ size,
2624
+ style: { flex: 1 }
2625
+ }
2626
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2627
+ NarrativeTrace,
2628
+ {
2629
+ narrative,
2630
+ revealedCount,
2631
+ size,
2632
+ style: { flex: 1 }
2633
+ }
2634
+ )
2635
+ ]
2636
+ }
2637
+ );
2638
+ }
2639
+
2429
2640
  // src/components/FlowchartView/SubflowTree.tsx
2430
- var import_react12 = require("react");
2431
- var import_jsx_runtime12 = require("react/jsx-runtime");
2641
+ var import_react13 = require("react");
2642
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2432
2643
  function specToTree(node) {
2644
+ if (!node) return [];
2433
2645
  const entries = [];
2434
2646
  const seen = /* @__PURE__ */ new Set();
2435
2647
  function walk(n) {
2648
+ if (!n) return;
2436
2649
  const id = n.name || n.id || "";
2437
2650
  if (seen.has(id)) return;
2438
2651
  seen.add(id);
@@ -2448,7 +2661,7 @@ function specToTree(node) {
2448
2661
  entries.push(entry);
2449
2662
  if (n.children) {
2450
2663
  for (const child of n.children) {
2451
- walk(child);
2664
+ if (child) walk(child);
2452
2665
  }
2453
2666
  }
2454
2667
  if (n.next) {
@@ -2458,25 +2671,25 @@ function specToTree(node) {
2458
2671
  walk(node);
2459
2672
  return entries;
2460
2673
  }
2461
- var TreeNode = (0, import_react12.memo)(function TreeNode2({
2674
+ var TreeNode = (0, import_react13.memo)(function TreeNode2({
2462
2675
  entry,
2463
2676
  depth,
2464
2677
  activeStage,
2465
2678
  doneStages,
2466
2679
  onNodeSelect
2467
2680
  }) {
2468
- const [expanded, setExpanded] = (0, import_react12.useState)(true);
2681
+ const [expanded, setExpanded] = (0, import_react13.useState)(true);
2469
2682
  const hasChildren = entry.children && entry.children.length > 0;
2470
2683
  const isActive = activeStage === entry.name;
2471
2684
  const isDone = doneStages?.has(entry.name);
2472
- const handleClick = (0, import_react12.useCallback)(() => {
2685
+ const handleClick = (0, import_react13.useCallback)(() => {
2473
2686
  if (hasChildren) {
2474
2687
  setExpanded((prev) => !prev);
2475
2688
  }
2476
2689
  onNodeSelect?.(entry.name, !!entry.isSubflow);
2477
2690
  }, [hasChildren, onNodeSelect, entry.name, entry.isSubflow]);
2478
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2479
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2691
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2692
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2480
2693
  "button",
2481
2694
  {
2482
2695
  onClick: handleClick,
@@ -2507,7 +2720,7 @@ var TreeNode = (0, import_react12.memo)(function TreeNode2({
2507
2720
  }
2508
2721
  },
2509
2722
  children: [
2510
- hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2723
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2511
2724
  "span",
2512
2725
  {
2513
2726
  style: {
@@ -2522,8 +2735,8 @@ var TreeNode = (0, import_react12.memo)(function TreeNode2({
2522
2735
  },
2523
2736
  children: "\u25B6"
2524
2737
  }
2525
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { width: 12, flexShrink: 0 } }),
2526
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2738
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { width: 12, flexShrink: 0 } }),
2739
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2527
2740
  "span",
2528
2741
  {
2529
2742
  style: {
@@ -2535,8 +2748,8 @@ var TreeNode = (0, import_react12.memo)(function TreeNode2({
2535
2748
  }
2536
2749
  }
2537
2750
  ),
2538
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
2539
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2751
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
2752
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2540
2753
  "span",
2541
2754
  {
2542
2755
  style: {
@@ -2548,11 +2761,11 @@ var TreeNode = (0, import_react12.memo)(function TreeNode2({
2548
2761
  },
2549
2762
  children: [
2550
2763
  entry.name,
2551
- entry.isSubflow && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
2764
+ entry.isSubflow && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
2552
2765
  ]
2553
2766
  }
2554
2767
  ),
2555
- entry.description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2768
+ entry.description && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2556
2769
  "span",
2557
2770
  {
2558
2771
  style: {
@@ -2569,7 +2782,7 @@ var TreeNode = (0, import_react12.memo)(function TreeNode2({
2569
2782
  ]
2570
2783
  }
2571
2784
  ),
2572
- hasChildren && expanded && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { children: entry.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2785
+ hasChildren && expanded && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { children: entry.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2573
2786
  TreeNode2,
2574
2787
  {
2575
2788
  entry: child,
@@ -2578,12 +2791,12 @@ var TreeNode = (0, import_react12.memo)(function TreeNode2({
2578
2791
  doneStages,
2579
2792
  onNodeSelect
2580
2793
  },
2581
- `${child.name}-${i}`
2794
+ child.subflowId ?? `${child.name}-${i}`
2582
2795
  )) })
2583
2796
  ] });
2584
2797
  });
2585
- var SectionLabel = (0, import_react12.memo)(function SectionLabel2({ children }) {
2586
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2798
+ var SectionLabel = (0, import_react13.memo)(function SectionLabel2({ children }) {
2799
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2587
2800
  "div",
2588
2801
  {
2589
2802
  style: {
@@ -2598,7 +2811,7 @@ var SectionLabel = (0, import_react12.memo)(function SectionLabel2({ children })
2598
2811
  }
2599
2812
  );
2600
2813
  });
2601
- var SubflowTree = (0, import_react12.memo)(function SubflowTree2({
2814
+ var SubflowTree = (0, import_react13.memo)(function SubflowTree2({
2602
2815
  spec,
2603
2816
  activeStage,
2604
2817
  doneStages,
@@ -2607,10 +2820,10 @@ var SubflowTree = (0, import_react12.memo)(function SubflowTree2({
2607
2820
  className,
2608
2821
  style
2609
2822
  }) {
2610
- const tree = (0, import_react12.useMemo)(() => specToTree(spec), [spec]);
2611
- const subflowStages = (0, import_react12.useMemo)(() => tree.filter((e) => e.isSubflow), [tree]);
2823
+ const tree = (0, import_react13.useMemo)(() => specToTree(spec), [spec]);
2824
+ const subflowStages = (0, import_react13.useMemo)(() => tree.filter((e) => e.isSubflow), [tree]);
2612
2825
  if (subflowStages.length === 0) return null;
2613
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2826
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2614
2827
  "div",
2615
2828
  {
2616
2829
  className,
@@ -2628,8 +2841,8 @@ var SubflowTree = (0, import_react12.memo)(function SubflowTree2({
2628
2841
  ...style
2629
2842
  },
2630
2843
  children: [
2631
- !unstyled && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Subflows" }),
2632
- subflowStages.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2844
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SectionLabel, { children: "Subflows" }),
2845
+ subflowStages.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2633
2846
  TreeNode,
2634
2847
  {
2635
2848
  entry,
@@ -2638,98 +2851,602 @@ var SubflowTree = (0, import_react12.memo)(function SubflowTree2({
2638
2851
  doneStages,
2639
2852
  onNodeSelect
2640
2853
  },
2641
- `${entry.name}-${i}`
2854
+ entry.subflowId ?? `${entry.name}-${i}`
2642
2855
  ))
2643
2856
  ]
2644
2857
  }
2645
2858
  );
2646
2859
  });
2647
2860
 
2648
- // src/adapters/fromRuntimeSnapshot.ts
2649
- function toVisualizationSnapshots(runtime, narrativeEntries) {
2650
- const stageNarrativeMap = narrativeEntries?.length ? buildStageNarrativeMap(narrativeEntries) : /* @__PURE__ */ new Map();
2651
- const snapshots = [];
2652
- flattenTree(runtime.executionTree, snapshots, runtime.sharedState, 0, runtime.subflowResults, {}, stageNarrativeMap);
2653
- return snapshots;
2861
+ // src/components/FlowchartView/SubflowBreadcrumb.tsx
2862
+ var import_react14 = require("react");
2863
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2864
+ var SubflowBreadcrumb = (0, import_react14.memo)(function SubflowBreadcrumb2({
2865
+ breadcrumbs,
2866
+ onNavigate
2867
+ }) {
2868
+ if (breadcrumbs.length <= 1) return null;
2869
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2870
+ "div",
2871
+ {
2872
+ style: {
2873
+ display: "flex",
2874
+ alignItems: "center",
2875
+ gap: 4,
2876
+ padding: "6px 12px",
2877
+ background: theme.bgSecondary,
2878
+ borderBottom: `1px solid ${theme.border}`,
2879
+ fontSize: 12,
2880
+ fontFamily: theme.fontSans,
2881
+ flexShrink: 0,
2882
+ overflowX: "auto"
2883
+ },
2884
+ children: breadcrumbs.map((crumb, i) => {
2885
+ const isLast = i === breadcrumbs.length - 1;
2886
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
2887
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
2888
+ isLast ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
2889
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2890
+ "span",
2891
+ {
2892
+ style: {
2893
+ color: theme.primary,
2894
+ fontWeight: 600
2895
+ },
2896
+ children: crumb.label
2897
+ }
2898
+ ),
2899
+ crumb.description && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2900
+ "span",
2901
+ {
2902
+ style: {
2903
+ color: theme.textMuted,
2904
+ fontWeight: 400,
2905
+ fontSize: 11
2906
+ },
2907
+ children: [
2908
+ "\u2014 ",
2909
+ crumb.description
2910
+ ]
2911
+ }
2912
+ )
2913
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2914
+ "button",
2915
+ {
2916
+ onClick: () => onNavigate(i),
2917
+ style: {
2918
+ background: "none",
2919
+ border: "none",
2920
+ color: theme.textSecondary,
2921
+ cursor: "pointer",
2922
+ padding: "2px 4px",
2923
+ borderRadius: 4,
2924
+ fontSize: 12,
2925
+ fontFamily: "inherit",
2926
+ fontWeight: 500,
2927
+ transition: "color 0.15s"
2928
+ },
2929
+ onMouseEnter: (e) => {
2930
+ e.currentTarget.style.color = `${theme.primary}`;
2931
+ },
2932
+ onMouseLeave: (e) => {
2933
+ e.currentTarget.style.color = `${theme.textSecondary}`;
2934
+ },
2935
+ children: crumb.label
2936
+ }
2937
+ )
2938
+ ] }, `${crumb.label}-${i}`);
2939
+ })
2940
+ }
2941
+ );
2942
+ });
2943
+
2944
+ // src/components/ExplainableShell/ExplainableShell.tsx
2945
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2946
+ var HLinePill = (0, import_react15.memo)(function HLinePill2({
2947
+ label,
2948
+ detail,
2949
+ expanded,
2950
+ onClick
2951
+ }) {
2952
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: {
2953
+ display: "flex",
2954
+ alignItems: "center",
2955
+ gap: 0,
2956
+ padding: "0"
2957
+ }, children: [
2958
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, height: 1, background: theme.border } }),
2959
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2960
+ "button",
2961
+ {
2962
+ onClick,
2963
+ style: {
2964
+ display: "flex",
2965
+ alignItems: "center",
2966
+ gap: 5,
2967
+ padding: "3px 12px",
2968
+ margin: "4px 0",
2969
+ fontSize: 10,
2970
+ fontWeight: 600,
2971
+ fontFamily: "inherit",
2972
+ color: theme.textMuted,
2973
+ background: theme.bgSecondary,
2974
+ border: `1px solid ${theme.border}`,
2975
+ borderRadius: 10,
2976
+ cursor: "pointer",
2977
+ whiteSpace: "nowrap",
2978
+ letterSpacing: "0.04em",
2979
+ textTransform: "uppercase",
2980
+ transition: "color 0.15s ease"
2981
+ },
2982
+ children: [
2983
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontSize: 7 }, children: expanded ? "\u25BC" : "\u25B6" }),
2984
+ label,
2985
+ detail && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontWeight: 400, opacity: 0.5, fontSize: 9 }, children: detail })
2986
+ ]
2987
+ }
2988
+ ),
2989
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, height: 1, background: theme.border } })
2990
+ ] });
2991
+ });
2992
+ var VLinePill = (0, import_react15.memo)(function VLinePill2({
2993
+ label,
2994
+ expanded,
2995
+ side = "right",
2996
+ onClick
2997
+ }) {
2998
+ const arrow = side === "right" ? expanded ? "\u25B6" : "\u25C0" : expanded ? "\u25C0" : "\u25B6";
2999
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: {
3000
+ display: "flex",
3001
+ flexDirection: "column",
3002
+ alignItems: "center",
3003
+ gap: 0,
3004
+ padding: "0"
3005
+ }, children: [
3006
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, width: 1, background: theme.border } }),
3007
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3008
+ "button",
3009
+ {
3010
+ onClick,
3011
+ style: {
3012
+ display: "flex",
3013
+ alignItems: "center",
3014
+ gap: 4,
3015
+ padding: "10px 4px",
3016
+ margin: "0 3px",
3017
+ fontSize: 10,
3018
+ fontWeight: 600,
3019
+ fontFamily: "inherit",
3020
+ color: theme.textMuted,
3021
+ background: theme.bgSecondary,
3022
+ border: `1px solid ${theme.border}`,
3023
+ borderRadius: 10,
3024
+ cursor: "pointer",
3025
+ whiteSpace: "nowrap",
3026
+ letterSpacing: "0.04em",
3027
+ textTransform: "uppercase",
3028
+ writingMode: "vertical-lr",
3029
+ transition: "color 0.15s ease"
3030
+ },
3031
+ children: [
3032
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontSize: 7, writingMode: "horizontal-tb" }, children: arrow }),
3033
+ label
3034
+ ]
3035
+ }
3036
+ ),
3037
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, width: 1, background: theme.border } })
3038
+ ] });
3039
+ });
3040
+ var RIGHT_PANEL_LABELS = {
3041
+ memory: "Memory",
3042
+ narrative: "Narrative"
3043
+ };
3044
+ var DetailsContent = (0, import_react15.memo)(function DetailsContent2({
3045
+ snapshots,
3046
+ selectedIndex,
3047
+ narrativeEntries,
3048
+ narrative,
3049
+ size,
3050
+ fillHeight
3051
+ }) {
3052
+ const [rightPanel, setRightPanel] = (0, import_react15.useState)("memory");
3053
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
3054
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { display: "flex", borderBottom: `1px solid ${theme.border}`, flexShrink: 0 }, children: ["memory", "narrative"].map((panel) => {
3055
+ const active = rightPanel === panel;
3056
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3057
+ "button",
3058
+ {
3059
+ onClick: () => setRightPanel(panel),
3060
+ style: {
3061
+ flex: 1,
3062
+ padding: "6px 8px",
3063
+ fontSize: 11,
3064
+ fontWeight: active ? 600 : 400,
3065
+ color: active ? theme.primary : theme.textMuted,
3066
+ background: active ? `color-mix(in srgb, ${theme.primary} 8%, transparent)` : "transparent",
3067
+ border: "none",
3068
+ borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
3069
+ cursor: "pointer",
3070
+ textTransform: "uppercase",
3071
+ letterSpacing: "0.06em",
3072
+ fontFamily: "inherit"
3073
+ },
3074
+ children: RIGHT_PANEL_LABELS[panel]
3075
+ },
3076
+ panel
3077
+ );
3078
+ }) }),
3079
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, overflow: "auto" }, children: [
3080
+ rightPanel === "memory" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MemoryPanel, { snapshots, selectedIndex, size, style: fillHeight ? { height: "100%" } : void 0 }),
3081
+ rightPanel === "narrative" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NarrativePanel, { snapshots, selectedIndex, narrativeEntries, narrative, size, style: fillHeight ? { height: "100%" } : void 0 })
3082
+ ] })
3083
+ ] });
3084
+ });
3085
+ function resolveSubflowLevel(parentSpec, parentSnapshots, subflowNodeName, narrativeEntries) {
3086
+ const specNode = findSubflowSpecNode(parentSpec, subflowNodeName);
3087
+ if (!specNode?.subflowStructure) return null;
3088
+ const parentSnap = parentSnapshots.find(
3089
+ (s) => s.stageName === subflowNodeName || s.stageLabel === subflowNodeName
3090
+ );
3091
+ if (!parentSnap?.subflowResult) return null;
3092
+ const sfNarrative = narrativeEntries ? extractSubflowNarrative(narrativeEntries, subflowNodeName) : void 0;
3093
+ const sfSnapshots = subflowResultToSnapshots(parentSnap.subflowResult, sfNarrative);
3094
+ if (sfSnapshots.length === 0) return null;
3095
+ return {
3096
+ subflowId: specNode.subflowId ?? subflowNodeName,
3097
+ label: specNode.subflowName ?? specNode.name,
3098
+ spec: specNode.subflowStructure,
3099
+ snapshots: sfSnapshots
3100
+ };
2654
3101
  }
2655
- function buildStageNarrativeMap(entries) {
2656
- const map = /* @__PURE__ */ new Map();
2657
- let currentStageName;
3102
+ function extractSubflowNarrative(entries, subflowName) {
3103
+ const result = [];
3104
+ let inside = false;
2658
3105
  for (const entry of entries) {
2659
- if (entry.stageName) {
2660
- currentStageName = entry.stageName;
3106
+ if (entry.type === "subflow" && entry.text.includes(subflowName) && entry.text.startsWith("Entering")) {
3107
+ inside = true;
3108
+ continue;
2661
3109
  }
2662
- if (currentStageName) {
2663
- if (!map.has(currentStageName)) {
2664
- map.set(currentStageName, []);
2665
- }
2666
- const indent = " ".repeat(entry.depth);
2667
- map.get(currentStageName).push(`${indent}${entry.text}`);
3110
+ if (inside && entry.type === "subflow" && entry.text.includes(subflowName) && entry.text.startsWith("Exiting")) break;
3111
+ if (inside) result.push(entry);
3112
+ }
3113
+ return result;
3114
+ }
3115
+ function findSubflowSpecNode(node, name) {
3116
+ if ((node.name === name || node.id === name) && node.isSubflowRoot) return node;
3117
+ if (node.children) {
3118
+ for (const child of node.children) {
3119
+ const f = findSubflowSpecNode(child, name);
3120
+ if (f) return f;
2668
3121
  }
2669
3122
  }
2670
- return map;
3123
+ if (node.next) return findSubflowSpecNode(node.next, name);
3124
+ return null;
2671
3125
  }
2672
- function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults, cumulativeMemory = {}, stageNarrativeMap = /* @__PURE__ */ new Map()) {
2673
- const durationMs = typeof node.metrics?.durationMs === "number" ? node.metrics.durationMs : 1;
2674
- const startMs = accumulatedMs;
2675
- const stageId = node.name || node.id;
2676
- const stageLines = stageNarrativeMap.get(stageId);
2677
- const narrative = stageLines ? stageLines.join("\n") : "Narrative not part of this run.";
2678
- const memory = { ...cumulativeMemory };
2679
- if (node.stageWrites) {
2680
- for (const [key, value] of Object.entries(node.stageWrites)) {
2681
- if (value === void 0) {
2682
- delete memory[key];
3126
+ function hasSubflowNodes(node) {
3127
+ if (!node) return false;
3128
+ if (node.isSubflowRoot) return true;
3129
+ if (node.children?.some((c) => c && hasSubflowNodes(c))) return true;
3130
+ if (node.next && hasSubflowNodes(node.next)) return true;
3131
+ return false;
3132
+ }
3133
+ function ExplainableShell({
3134
+ snapshots,
3135
+ spec,
3136
+ title,
3137
+ resultData,
3138
+ logs = [],
3139
+ narrative,
3140
+ narrativeEntries,
3141
+ tabs = ["result", "explainable"],
3142
+ defaultTab,
3143
+ hideConsole = false,
3144
+ panelLabels,
3145
+ defaultExpanded,
3146
+ renderFlowchart,
3147
+ size = "default",
3148
+ unstyled = false,
3149
+ className,
3150
+ style
3151
+ }) {
3152
+ const leftLabel = panelLabels?.topology ?? "Topology";
3153
+ const rightLabel = panelLabels?.details ?? "Details";
3154
+ const bottomLabel = panelLabels?.timeline ?? "Timeline";
3155
+ const shellRef = (0, import_react15.useRef)(null);
3156
+ const [isNarrow, setIsNarrow] = (0, import_react15.useState)(false);
3157
+ (0, import_react15.useEffect)(() => {
3158
+ const el = shellRef.current;
3159
+ if (!el) return;
3160
+ const ro = new ResizeObserver(([entry]) => {
3161
+ setIsNarrow(entry.contentRect.width < 640);
3162
+ });
3163
+ ro.observe(el);
3164
+ return () => ro.disconnect();
3165
+ }, []);
3166
+ const [activeTab, setActiveTab] = (0, import_react15.useState)(defaultTab ?? tabs[0]);
3167
+ const [snapshotIdx, setSnapshotIdx] = (0, import_react15.useState)(0);
3168
+ const [drillDownStack, setDrillDownStack] = (0, import_react15.useState)([]);
3169
+ const [rightExpanded, setRightExpanded] = (0, import_react15.useState)(defaultExpanded?.details ?? true);
3170
+ const [leftExpanded, setLeftExpanded] = (0, import_react15.useState)(defaultExpanded?.topology ?? false);
3171
+ const [timelineExpanded, setTimelineExpanded] = (0, import_react15.useState)(defaultExpanded?.timeline ?? false);
3172
+ (0, import_react15.useEffect)(() => {
3173
+ if (isNarrow) {
3174
+ setLeftExpanded(false);
3175
+ setRightExpanded(false);
3176
+ setTimelineExpanded(false);
3177
+ }
3178
+ }, [isNarrow]);
3179
+ const triggerReflow = (0, import_react15.useCallback)(() => {
3180
+ requestAnimationFrame(() => window.dispatchEvent(new Event("resize")));
3181
+ }, []);
3182
+ const toggleLeft = (0, import_react15.useCallback)((v2) => {
3183
+ setLeftExpanded(v2);
3184
+ triggerReflow();
3185
+ }, [triggerReflow]);
3186
+ const toggleRight = (0, import_react15.useCallback)((v2) => {
3187
+ setRightExpanded(v2);
3188
+ triggerReflow();
3189
+ }, [triggerReflow]);
3190
+ const toggleTimeline = (0, import_react15.useCallback)(() => {
3191
+ setTimelineExpanded((p) => !p);
3192
+ triggerReflow();
3193
+ }, [triggerReflow]);
3194
+ const isInSubflow = drillDownStack.length > 0;
3195
+ const currentLevel = (0, import_react15.useMemo)(() => {
3196
+ if (drillDownStack.length > 0) {
3197
+ const top = drillDownStack[drillDownStack.length - 1];
3198
+ return { spec: top.spec, snapshots: top.snapshots };
3199
+ }
3200
+ return { spec: spec ?? null, snapshots };
3201
+ }, [drillDownStack, spec, snapshots]);
3202
+ const activeSnapshots = currentLevel.snapshots;
3203
+ const activeSpec = currentLevel.spec;
3204
+ const safeIdx = activeSnapshots.length > 0 ? Math.max(0, Math.min(snapshotIdx, activeSnapshots.length - 1)) : 0;
3205
+ const activeNarrative = (0, import_react15.useMemo)(() => {
3206
+ if (!isInSubflow) return narrative;
3207
+ const lines = [];
3208
+ for (const snap of activeSnapshots) {
3209
+ const stageLines = (snap.narrative ?? "").split("\n").filter(Boolean);
3210
+ lines.push(...stageLines);
3211
+ }
3212
+ return lines.length > 0 ? lines : void 0;
3213
+ }, [isInSubflow, narrative, activeSnapshots]);
3214
+ const activeNarrativeEntries = isInSubflow ? void 0 : narrativeEntries;
3215
+ const breadcrumbs = (0, import_react15.useMemo)(() => {
3216
+ const root = { label: title || "Flowchart", spec, description: spec?.description };
3217
+ return [root, ...drillDownStack.map((e) => ({ label: e.label, spec: e.spec, description: void 0 }))];
3218
+ }, [spec, title, drillDownStack]);
3219
+ const showTreeSidebar = (0, import_react15.useMemo)(() => !!spec && hasSubflowNodes(spec), [spec]);
3220
+ const rootOverlay = (0, import_react15.useMemo)(() => {
3221
+ if (isInSubflow || !snapshots.length) return { activeStage: void 0, doneStages: void 0 };
3222
+ const doneStages = new Set(snapshots.slice(0, safeIdx).map((s) => s.stageLabel));
3223
+ const activeStage = snapshots[safeIdx]?.stageLabel ?? null;
3224
+ return { activeStage, doneStages };
3225
+ }, [isInSubflow, snapshots, safeIdx]);
3226
+ const handleTabChange = (0, import_react15.useCallback)((tab) => {
3227
+ setActiveTab(tab);
3228
+ setDrillDownStack([]);
3229
+ setSnapshotIdx(999);
3230
+ }, []);
3231
+ const handleSnapshotChange = (0, import_react15.useCallback)((idx) => {
3232
+ if (typeof idx === "number") setSnapshotIdx(idx);
3233
+ }, []);
3234
+ const handleDrillDown = (0, import_react15.useCallback)(
3235
+ (nodeName) => {
3236
+ if (!activeSpec) return;
3237
+ const entry = resolveSubflowLevel(activeSpec, activeSnapshots, nodeName, narrativeEntries);
3238
+ if (entry) {
3239
+ setDrillDownStack((prev) => [...prev, { ...entry, parentSnapshotIdx: snapshotIdx }]);
3240
+ setSnapshotIdx(0);
3241
+ }
3242
+ },
3243
+ [activeSpec, activeSnapshots, narrativeEntries, snapshotIdx]
3244
+ );
3245
+ const handleBreadcrumbNavigate = (0, import_react15.useCallback)((level) => {
3246
+ setDrillDownStack((prev) => {
3247
+ const popped = level === 0 ? prev[0] : prev[level];
3248
+ if (popped) setSnapshotIdx(popped.parentSnapshotIdx);
3249
+ return level === 0 ? [] : prev.slice(0, level);
3250
+ });
3251
+ }, []);
3252
+ const handleNodeClick = (0, import_react15.useCallback)(
3253
+ (indexOrId) => {
3254
+ if (typeof indexOrId === "number") {
3255
+ setSnapshotIdx(indexOrId);
3256
+ return;
3257
+ }
3258
+ if (activeSpec) {
3259
+ const sfNode = findSubflowSpecNode(activeSpec, indexOrId);
3260
+ if (sfNode?.subflowStructure) {
3261
+ handleDrillDown(indexOrId);
3262
+ return;
3263
+ }
3264
+ }
3265
+ const idx = activeSnapshots.findIndex((s) => s.stageLabel === indexOrId);
3266
+ if (idx >= 0) setSnapshotIdx(idx);
3267
+ },
3268
+ [activeSpec, activeSnapshots, handleDrillDown]
3269
+ );
3270
+ const handleTreeNodeSelect = (0, import_react15.useCallback)(
3271
+ (name, isSubflow) => {
3272
+ if (isSubflow && spec) {
3273
+ setDrillDownStack([]);
3274
+ const entry = resolveSubflowLevel(spec, snapshots, name, narrativeEntries);
3275
+ if (entry) {
3276
+ setDrillDownStack([{ ...entry, parentSnapshotIdx: snapshotIdx }]);
3277
+ setSnapshotIdx(0);
3278
+ }
2683
3279
  } else {
2684
- memory[key] = value;
3280
+ setDrillDownStack([]);
3281
+ const idx = snapshots.findIndex((s) => s.stageLabel === name);
3282
+ if (idx >= 0) setSnapshotIdx(idx);
2685
3283
  }
2686
- }
3284
+ },
3285
+ [spec, snapshots, narrativeEntries, snapshotIdx]
3286
+ );
3287
+ const tabLabels = {
3288
+ result: "Result",
3289
+ explainable: "Explainable",
3290
+ "ai-compatible": "AI-Compatible"
3291
+ };
3292
+ if (unstyled) {
3293
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className, style, "data-fp": "explainable-shell", children: [
3294
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { "data-fp": "shell-tabs", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { "data-fp": "shell-tab", "data-active": tab === activeTab, onClick: () => handleTabChange(tab), children: tabLabels[tab] }, tab)) }),
3295
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-fp": "shell-content", "data-tab": activeTab, children: [
3296
+ activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ResultPanel, { data: resultData ?? null, logs, hideConsole, unstyled: true }),
3297
+ (activeTab === "explainable" || activeTab === "ai-compatible") && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3298
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TimeTravelControls, { snapshots: activeSnapshots, selectedIndex: safeIdx, onIndexChange: handleSnapshotChange, unstyled: true }),
3299
+ isInSubflow && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SubflowBreadcrumb, { breadcrumbs, onNavigate: handleBreadcrumbNavigate }),
3300
+ activeSpec && renderFlowchart?.({ spec: activeSpec, snapshots: activeSnapshots, selectedIndex: safeIdx, onNodeClick: handleNodeClick }),
3301
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MemoryPanel, { snapshots: activeSnapshots, selectedIndex: safeIdx, unstyled: true }),
3302
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NarrativePanel, { snapshots: activeSnapshots, selectedIndex: safeIdx, narrativeEntries: activeNarrativeEntries, narrative: activeNarrative, unstyled: true }),
3303
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GanttTimeline, { snapshots: activeSnapshots, selectedIndex: safeIdx, onSelect: handleSnapshotChange, unstyled: true })
3304
+ ] })
3305
+ ] })
3306
+ ] });
2687
3307
  }
2688
- const sfResult = subflowResults?.[node.subflowId ?? stageId];
2689
- out.push({
2690
- stageName: stageId,
2691
- stageLabel: stageId,
2692
- memory,
2693
- narrative,
2694
- startMs,
2695
- durationMs,
2696
- status: "done",
2697
- ...node.description ? { description: node.description } : void 0,
2698
- ...node.subflowId ? { subflowId: node.subflowId } : void 0,
2699
- ...sfResult ? { subflowResult: sfResult } : void 0
2700
- });
2701
- let nextMs = startMs + durationMs;
2702
- if (node.children && node.children.length > 0) {
2703
- let maxChildEnd = nextMs;
2704
- for (const child of node.children) {
2705
- const childEnd = flattenTree(child, out, sharedState, nextMs, subflowResults, memory, stageNarrativeMap);
2706
- maxChildEnd = Math.max(maxChildEnd, childEnd);
3308
+ const isVisualizationTab = activeTab === "explainable" || activeTab === "ai-compatible";
3309
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3310
+ "div",
3311
+ {
3312
+ ref: shellRef,
3313
+ className,
3314
+ style: {
3315
+ height: "100%",
3316
+ display: "flex",
3317
+ flexDirection: "column",
3318
+ overflow: "hidden",
3319
+ background: theme.bgPrimary,
3320
+ color: theme.textPrimary,
3321
+ fontFamily: theme.fontSans,
3322
+ fontSize: 12,
3323
+ ...style
3324
+ },
3325
+ "data-fp": "explainable-shell",
3326
+ children: [
3327
+ tabs.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: {
3328
+ display: "flex",
3329
+ borderBottom: `1px solid ${theme.border}`,
3330
+ background: theme.bgSecondary,
3331
+ flexShrink: 0
3332
+ }, children: tabs.map((tab) => {
3333
+ const active = tab === activeTab;
3334
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3335
+ "button",
3336
+ {
3337
+ onClick: () => handleTabChange(tab),
3338
+ style: {
3339
+ padding: "6px 14px",
3340
+ fontSize: 11,
3341
+ fontWeight: active ? 700 : 500,
3342
+ textTransform: "uppercase",
3343
+ letterSpacing: "0.08em",
3344
+ color: active ? theme.primary : theme.textMuted,
3345
+ background: "transparent",
3346
+ border: "none",
3347
+ borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
3348
+ cursor: "pointer",
3349
+ fontFamily: "inherit"
3350
+ },
3351
+ children: tabLabels[tab]
3352
+ },
3353
+ tab
3354
+ );
3355
+ }) }),
3356
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, overflow: isNarrow ? "auto" : "hidden", display: "flex", flexDirection: "column" }, children: [
3357
+ activeTab === "result" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ResultPanel, { data: resultData ?? null, logs, hideConsole, size }),
3358
+ isVisualizationTab && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3359
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3360
+ TimeTravelControls,
3361
+ {
3362
+ snapshots: activeSnapshots,
3363
+ selectedIndex: safeIdx,
3364
+ onIndexChange: handleSnapshotChange,
3365
+ size
3366
+ }
3367
+ ),
3368
+ isInSubflow && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SubflowBreadcrumb, { breadcrumbs, onNavigate: handleBreadcrumbNavigate }),
3369
+ isNarrow ? (
3370
+ /* ── Mobile: stacked vertical ── */
3371
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3372
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { height: 350, flexShrink: 0, overflow: "hidden" }, children: renderFlowchart && activeSpec && renderFlowchart({
3373
+ spec: activeSpec,
3374
+ snapshots: activeSnapshots,
3375
+ selectedIndex: safeIdx,
3376
+ onNodeClick: handleNodeClick
3377
+ }) }),
3378
+ showTreeSidebar && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3379
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HLinePill, { label: leftLabel, expanded: leftExpanded, onClick: () => toggleLeft(!leftExpanded) }),
3380
+ leftExpanded && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { maxHeight: 180, overflow: "auto", flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3381
+ SubflowTree,
3382
+ {
3383
+ spec,
3384
+ activeStage: rootOverlay.activeStage,
3385
+ doneStages: rootOverlay.doneStages,
3386
+ onNodeSelect: handleTreeNodeSelect
3387
+ }
3388
+ ) })
3389
+ ] }),
3390
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HLinePill, { label: rightLabel, expanded: rightExpanded, onClick: () => toggleRight(!rightExpanded) }),
3391
+ rightExpanded && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { maxHeight: 250, flexShrink: 0, display: "flex", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3392
+ DetailsContent,
3393
+ {
3394
+ snapshots: activeSnapshots,
3395
+ selectedIndex: safeIdx,
3396
+ narrativeEntries: activeNarrativeEntries,
3397
+ narrative: activeNarrative,
3398
+ size
3399
+ }
3400
+ ) }),
3401
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HLinePill, { label: bottomLabel, detail: `${activeSnapshots.length} stages`, expanded: timelineExpanded, onClick: toggleTimeline }),
3402
+ timelineExpanded && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flexShrink: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GanttTimeline, { snapshots: activeSnapshots, selectedIndex: safeIdx, onSelect: handleSnapshotChange, size }) })
3403
+ ] })
3404
+ ) : (
3405
+ /* ── Desktop: side-by-side ── */
3406
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3407
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
3408
+ showTreeSidebar && (leftExpanded ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { width: 220, flexShrink: 0, display: "flex", flexDirection: "row", overflow: "hidden" }, children: [
3409
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, overflow: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3410
+ SubflowTree,
3411
+ {
3412
+ spec,
3413
+ activeStage: rootOverlay.activeStage,
3414
+ doneStages: rootOverlay.doneStages,
3415
+ onNodeSelect: handleTreeNodeSelect
3416
+ }
3417
+ ) }),
3418
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VLinePill, { label: leftLabel, expanded: true, side: "left", onClick: () => toggleLeft(false) })
3419
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VLinePill, { label: leftLabel, expanded: false, side: "left", onClick: () => toggleLeft(true) })),
3420
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, overflow: "hidden", minWidth: 0 }, children: renderFlowchart && activeSpec && renderFlowchart({
3421
+ spec: activeSpec,
3422
+ snapshots: activeSnapshots,
3423
+ selectedIndex: safeIdx,
3424
+ onNodeClick: handleNodeClick
3425
+ }) }),
3426
+ rightExpanded ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { width: "38%", minWidth: 300, maxWidth: 500, display: "flex", flexDirection: "row", overflow: "hidden" }, children: [
3427
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VLinePill, { label: rightLabel, expanded: true, onClick: () => toggleRight(false) }),
3428
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3429
+ DetailsContent,
3430
+ {
3431
+ snapshots: activeSnapshots,
3432
+ selectedIndex: safeIdx,
3433
+ narrativeEntries: activeNarrativeEntries,
3434
+ narrative: activeNarrative,
3435
+ size,
3436
+ fillHeight: true
3437
+ }
3438
+ )
3439
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VLinePill, { label: rightLabel, expanded: false, onClick: () => toggleRight(true) })
3440
+ ] }),
3441
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HLinePill, { label: bottomLabel, detail: `${activeSnapshots.length} stages`, expanded: timelineExpanded, onClick: toggleTimeline }),
3442
+ timelineExpanded && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flexShrink: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GanttTimeline, { snapshots: activeSnapshots, selectedIndex: safeIdx, onSelect: handleSnapshotChange, size }) })
3443
+ ] })
3444
+ )
3445
+ ] })
3446
+ ] })
3447
+ ]
2707
3448
  }
2708
- nextMs = maxChildEnd;
2709
- }
2710
- if (node.next) {
2711
- nextMs = flattenTree(node.next, out, sharedState, nextMs, subflowResults, memory, stageNarrativeMap);
2712
- }
2713
- return nextMs;
2714
- }
2715
- function createSnapshots(stages) {
2716
- let accMs = 0;
2717
- return stages.map((s) => {
2718
- const duration = s.durationMs ?? 1;
2719
- const snap = {
2720
- stageName: s.name,
2721
- stageLabel: s.label ?? s.name,
2722
- memory: s.memory ?? {},
2723
- narrative: s.narrative ?? `${s.label ?? s.name} completed.`,
2724
- startMs: accMs,
2725
- durationMs: duration,
2726
- status: "done",
2727
- ...s.description ? { description: s.description } : void 0,
2728
- ...s.subflowId ? { subflowId: s.subflowId } : void 0
2729
- };
2730
- accMs += duration;
2731
- return snap;
2732
- });
3449
+ );
2733
3450
  }
2734
3451
  // Annotate the CommonJS export names for ESM import in node:
2735
3452
  0 && (module.exports = {
@@ -2737,18 +3454,23 @@ function createSnapshots(stages) {
2737
3454
  FootprintTheme,
2738
3455
  GanttTimeline,
2739
3456
  MemoryInspector,
3457
+ MemoryPanel,
2740
3458
  NarrativeLog,
3459
+ NarrativePanel,
2741
3460
  NarrativeTrace,
2742
3461
  ResultPanel,
2743
3462
  ScopeDiff,
2744
3463
  SnapshotPanel,
2745
3464
  StageDetailPanel,
3465
+ StoryNarrative,
2746
3466
  SubflowTree,
2747
3467
  TimeTravelControls,
2748
3468
  coolDark,
2749
3469
  coolLight,
2750
3470
  createSnapshots,
2751
3471
  defaultTokens,
3472
+ rawDefaults,
3473
+ subflowResultToSnapshots,
2752
3474
  themePresets,
2753
3475
  toVisualizationSnapshots,
2754
3476
  tokensToCSSVars,