footprint-explainable-ui 0.5.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,20 +24,27 @@ __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,
39
+ coolLight: () => coolLight,
36
40
  createSnapshots: () => createSnapshots,
37
41
  defaultTokens: () => defaultTokens,
42
+ rawDefaults: () => rawDefaults,
43
+ subflowResultToSnapshots: () => subflowResultToSnapshots,
38
44
  themePresets: () => themePresets,
39
45
  toVisualizationSnapshots: () => toVisualizationSnapshots,
40
46
  tokensToCSSVars: () => tokensToCSSVars,
47
+ useDarkModeTokens: () => useDarkModeTokens,
41
48
  useFootprintTheme: () => useFootprintTheme,
42
49
  warmDark: () => warmDark,
43
50
  warmLight: () => warmLight
@@ -69,7 +76,7 @@ function tokensToCSSVars(tokens) {
69
76
  if (tokens.fontFamily?.mono) vars["--fp-font-mono"] = tokens.fontFamily.mono;
70
77
  return vars;
71
78
  }
72
- var defaultTokens = {
79
+ var rawDefaults = {
73
80
  colors: {
74
81
  primary: "#6366f1",
75
82
  success: "#22c55e",
@@ -89,6 +96,26 @@ var defaultTokens = {
89
96
  mono: "'JetBrains Mono', 'Fira Code', monospace"
90
97
  }
91
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
+ };
92
119
 
93
120
  // src/theme/ThemeProvider.tsx
94
121
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -193,14 +220,57 @@ var warmLight = {
193
220
  mono: "'JetBrains Mono', 'Fira Code', monospace"
194
221
  }
195
222
  };
223
+ var coolLight = {
224
+ colors: {
225
+ primary: "#6366f1",
226
+ success: "#22c55e",
227
+ error: "#ef4444",
228
+ warning: "#f59e0b",
229
+ bgPrimary: "#ffffff",
230
+ bgSecondary: "#f9fafb",
231
+ bgTertiary: "#e5e7eb",
232
+ textPrimary: "#18181b",
233
+ textSecondary: "#52525b",
234
+ textMuted: "#a1a1aa",
235
+ border: "#e5e7eb"
236
+ },
237
+ radius: "8px",
238
+ fontFamily: {
239
+ sans: "Inter, system-ui, -apple-system, sans-serif",
240
+ mono: "'JetBrains Mono', 'Fira Code', monospace"
241
+ }
242
+ };
196
243
  var themePresets = {
197
244
  coolDark,
245
+ coolLight,
198
246
  warmDark,
199
247
  warmLight
200
248
  };
201
249
 
202
- // src/components/MemoryInspector/MemoryInspector.tsx
250
+ // src/theme/useDarkModeTokens.ts
203
251
  var import_react2 = require("react");
252
+ function useDarkModeTokens(options) {
253
+ const lightTokens = options?.light ?? coolLight;
254
+ const darkTokens = options?.dark ?? coolDark;
255
+ const [isDark, setIsDark] = (0, import_react2.useState)(
256
+ () => document.documentElement.classList.contains(options?.selector ?? "dark")
257
+ );
258
+ (0, import_react2.useEffect)(() => {
259
+ const cls = options?.selector ?? "dark";
260
+ const obs = new MutationObserver(() => {
261
+ setIsDark(document.documentElement.classList.contains(cls));
262
+ });
263
+ obs.observe(document.documentElement, {
264
+ attributes: true,
265
+ attributeFilter: ["class"]
266
+ });
267
+ return () => obs.disconnect();
268
+ }, [options?.selector]);
269
+ return isDark ? darkTokens : lightTokens;
270
+ }
271
+
272
+ // src/components/MemoryInspector/MemoryInspector.tsx
273
+ var import_react3 = require("react");
204
274
  var import_jsx_runtime2 = require("react/jsx-runtime");
205
275
  function MemoryInspector({
206
276
  data,
@@ -213,28 +283,45 @@ function MemoryInspector({
213
283
  className,
214
284
  style
215
285
  }) {
216
- const { memory, newKeys } = (0, import_react2.useMemo)(() => {
286
+ const cacheRef = (0, import_react3.useRef)(null);
287
+ const { memory, newKeys } = (0, import_react3.useMemo)(() => {
217
288
  if (data) {
218
289
  return { memory: data, newKeys: /* @__PURE__ */ new Set() };
219
290
  }
220
291
  if (!snapshots || snapshots.length === 0) {
221
292
  return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
222
293
  }
223
- const merged = {};
224
- for (let i = 0; i <= Math.min(selectedIndex, snapshots.length - 1); i++) {
225
- 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
+ }
226
307
  }
308
+ cacheRef.current = { snapshots, index: safeIdx, accumulated: merged };
227
309
  const nk = /* @__PURE__ */ new Set();
228
- if (highlightNew && selectedIndex > 0) {
229
- const prev = {};
230
- for (let i = 0; i < selectedIndex; i++) {
231
- 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
+ }
232
319
  }
233
- const current = snapshots[selectedIndex]?.memory ?? {};
320
+ const current = snapshots[safeIdx]?.memory ?? {};
234
321
  for (const k of Object.keys(current)) {
235
322
  if (!(k in prev)) nk.add(k);
236
323
  }
237
- } else if (highlightNew && selectedIndex === 0 && snapshots[0]) {
324
+ } else if (highlightNew && safeIdx === 0 && snapshots[0]) {
238
325
  for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
239
326
  }
240
327
  return { memory: merged, newKeys: nk };
@@ -243,9 +330,9 @@ function MemoryInspector({
243
330
  const fs = fontSize[size];
244
331
  const pad = padding[size];
245
332
  if (unstyled) {
246
- 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: [
247
334
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-fp": "memory-label", children: "Memory State" }),
248
- /* @__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) }) })
249
336
  ] });
250
337
  }
251
338
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -258,6 +345,8 @@ function MemoryInspector({
258
345
  ...style
259
346
  },
260
347
  "data-fp": "memory-inspector",
348
+ role: "region",
349
+ "aria-label": "Memory state",
261
350
  children: [
262
351
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
263
352
  "span",
@@ -357,7 +446,7 @@ function formatValue(value) {
357
446
  }
358
447
 
359
448
  // src/components/NarrativeLog/NarrativeLog.tsx
360
- var import_react3 = require("react");
449
+ var import_react4 = require("react");
361
450
  var import_jsx_runtime3 = require("react/jsx-runtime");
362
451
  function NarrativeLog({
363
452
  snapshots,
@@ -368,7 +457,7 @@ function NarrativeLog({
368
457
  className,
369
458
  style
370
459
  }) {
371
- const entries = (0, import_react3.useMemo)(() => {
460
+ const entries = (0, import_react4.useMemo)(() => {
372
461
  if (narrative) {
373
462
  return [{ label: "Output", text: narrative, isCurrent: true }];
374
463
  }
@@ -490,7 +579,7 @@ function NarrativeLog({
490
579
  }
491
580
 
492
581
  // src/components/NarrativeTrace/NarrativeTrace.tsx
493
- var import_react4 = require("react");
582
+ var import_react5 = require("react");
494
583
  var import_jsx_runtime4 = require("react/jsx-runtime");
495
584
  function parseGroups(lines) {
496
585
  const groups = [];
@@ -520,17 +609,17 @@ function NarrativeTrace({
520
609
  }) {
521
610
  const revealed = revealedCount != null ? narrative.slice(0, revealedCount) : narrative;
522
611
  const future = revealedCount != null ? narrative.slice(revealedCount) : [];
523
- const revealedGroups = (0, import_react4.useMemo)(() => parseGroups(revealed), [revealed]);
524
- const futureGroups = (0, import_react4.useMemo)(() => parseGroups(future), [future]);
525
- const [collapsedSet, setCollapsedSet] = (0, import_react4.useState)(() => {
612
+ const revealedGroups = (0, import_react5.useMemo)(() => parseGroups(revealed), [revealed]);
613
+ const futureGroups = (0, import_react5.useMemo)(() => parseGroups(future), [future]);
614
+ const [collapsedSet, setCollapsedSet] = (0, import_react5.useState)(() => {
526
615
  if (!defaultCollapsed) return /* @__PURE__ */ new Set();
527
616
  return new Set(parseGroups(narrative).map((g) => g.headerIdx));
528
617
  });
529
- const latestRef = (0, import_react4.useRef)(null);
530
- (0, import_react4.useEffect)(() => {
618
+ const latestRef = (0, import_react5.useRef)(null);
619
+ (0, import_react5.useEffect)(() => {
531
620
  latestRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
532
621
  }, [revealedGroups.length]);
533
- const toggle = (0, import_react4.useCallback)((idx) => {
622
+ const toggle = (0, import_react5.useCallback)((idx) => {
534
623
  setCollapsedSet((prev) => {
535
624
  const next = new Set(prev);
536
625
  if (next.has(idx)) next.delete(idx);
@@ -550,19 +639,32 @@ function NarrativeTrace({
550
639
  "data-fp": "narrative-header",
551
640
  "data-collapsible": group.steps.length > 0,
552
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" : ""}`,
553
646
  onClick: () => {
554
647
  if (group.steps.length > 0) toggle(group.headerIdx);
555
648
  onStageClick?.(group.headerIdx);
556
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
+ },
557
657
  children: group.header
558
658
  }
559
659
  ),
560
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))
561
661
  ] }, group.headerIdx)),
562
- futureGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-fp": "narrative-group", "data-future": true, children: [
563
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-fp": "narrative-header", children: group.header }),
564
- group.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-fp": "narrative-step", children: step.text }, `f-${step.idx}`))
565
- ] }, `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
+ ] })
566
668
  ] });
567
669
  }
568
670
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -592,10 +694,21 @@ function NarrativeTrace({
592
694
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
593
695
  "div",
594
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"}`,
595
701
  onClick: () => {
596
702
  if (hasSteps) toggle(group.headerIdx);
597
703
  onStageClick?.(group.headerIdx);
598
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
+ },
599
712
  style: {
600
713
  fontSize: fs.body,
601
714
  lineHeight: 1.7,
@@ -654,43 +767,25 @@ function NarrativeTrace({
654
767
  group.headerIdx
655
768
  );
656
769
  }),
657
- 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: [
658
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
659
- "div",
660
- {
661
- style: {
662
- fontSize: fs.body,
663
- lineHeight: 1.7,
664
- color: theme.textMuted,
665
- padding: `4px ${pad - 4}px`,
666
- borderLeft: `3px solid ${theme.border}`,
667
- fontWeight: 600,
668
- paddingLeft: pad + 12
669
- },
670
- children: group.header
671
- }
672
- ),
673
- group.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
674
- "div",
675
- {
676
- style: {
677
- fontSize: fs.small,
678
- lineHeight: 1.6,
679
- color: theme.textMuted,
680
- padding: `2px ${pad - 4}px 2px ${pad + 20}px`
681
- },
682
- children: step.text
683
- },
684
- `f-${step.idx}`
685
- ))
686
- ] }, `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
+ ] })
687
782
  ]
688
783
  }
689
784
  );
690
785
  }
691
786
 
692
787
  // src/components/GanttTimeline/GanttTimeline.tsx
693
- var import_react5 = require("react");
788
+ var import_react6 = require("react");
694
789
  var import_jsx_runtime5 = require("react/jsx-runtime");
695
790
  function GanttTimeline({
696
791
  snapshots,
@@ -702,10 +797,10 @@ function GanttTimeline({
702
797
  style,
703
798
  maxVisibleRows = 5
704
799
  }) {
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);
708
- const totalWallTime = (0, import_react5.useMemo)(
800
+ const [expanded, setExpanded] = (0, import_react6.useState)(false);
801
+ const activeRowRef = (0, import_react6.useRef)(null);
802
+ const scrollContainerRef = (0, import_react6.useRef)(null);
803
+ const totalWallTime = (0, import_react6.useMemo)(
709
804
  () => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
710
805
  [snapshots]
711
806
  );
@@ -716,7 +811,7 @@ function GanttTimeline({
716
811
  const rowHeight = size === "compact" ? 18 : 22;
717
812
  const collapsible = maxVisibleRows > 0 && snapshots.length > maxVisibleRows;
718
813
  const showAll = expanded || !collapsible;
719
- (0, import_react5.useEffect)(() => {
814
+ (0, import_react6.useEffect)(() => {
720
815
  if (!showAll && activeRowRef.current && scrollContainerRef.current) {
721
816
  activeRowRef.current.scrollIntoView({
722
817
  block: "nearest",
@@ -725,12 +820,15 @@ function GanttTimeline({
725
820
  }
726
821
  }, [selectedIndex, showAll]);
727
822
  if (unstyled) {
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)(
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)(
729
824
  "div",
730
825
  {
731
826
  "data-fp": "gantt-bar",
732
827
  "data-selected": idx === selectedIndex,
733
828
  "data-visible": idx <= selectedIndex,
829
+ role: "option",
830
+ "aria-selected": idx === selectedIndex,
831
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
734
832
  onClick: () => onSelect?.(idx),
735
833
  children: [
736
834
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
@@ -740,7 +838,7 @@ function GanttTimeline({
740
838
  ] })
741
839
  ]
742
840
  },
743
- snap.stageName
841
+ `${snap.stageName}-${idx}`
744
842
  )) });
745
843
  }
746
844
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
@@ -796,6 +894,8 @@ function GanttTimeline({
796
894
  "div",
797
895
  {
798
896
  ref: scrollContainerRef,
897
+ role: "listbox",
898
+ "aria-label": "Execution timeline",
799
899
  style: {
800
900
  marginTop: 8,
801
901
  display: "flex",
@@ -816,6 +916,9 @@ function GanttTimeline({
816
916
  "div",
817
917
  {
818
918
  ref: isSelected ? activeRowRef : void 0,
919
+ role: "option",
920
+ "aria-selected": isSelected,
921
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
819
922
  onClick: () => onSelect?.(idx),
820
923
  style: {
821
924
  display: "flex",
@@ -831,6 +934,7 @@ function GanttTimeline({
831
934
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
832
935
  "span",
833
936
  {
937
+ title: snap.stageLabel,
834
938
  style: {
835
939
  width: labelWidth,
836
940
  fontSize: fs.small,
@@ -890,7 +994,7 @@ function GanttTimeline({
890
994
  )
891
995
  ]
892
996
  },
893
- snap.stageName
997
+ `${snap.stageName}-${idx}`
894
998
  );
895
999
  })
896
1000
  }
@@ -927,7 +1031,7 @@ function GanttTimeline({
927
1031
  }
928
1032
 
929
1033
  // src/components/SnapshotPanel/SnapshotPanel.tsx
930
- var import_react6 = require("react");
1034
+ var import_react7 = require("react");
931
1035
  var import_jsx_runtime6 = require("react/jsx-runtime");
932
1036
  function SnapshotPanel({
933
1037
  snapshots,
@@ -939,7 +1043,7 @@ function SnapshotPanel({
939
1043
  className,
940
1044
  style
941
1045
  }) {
942
- const [selectedIndex, setSelectedIndex] = (0, import_react6.useState)(0);
1046
+ const [selectedIndex, setSelectedIndex] = (0, import_react7.useState)(0);
943
1047
  const fs = fontSize[size];
944
1048
  const pad = padding[size];
945
1049
  if (snapshots.length === 0) {
@@ -1182,7 +1286,7 @@ function ScrubButton({
1182
1286
  }
1183
1287
 
1184
1288
  // src/components/ScopeDiff/ScopeDiff.tsx
1185
- var import_react7 = require("react");
1289
+ var import_react8 = require("react");
1186
1290
  var import_jsx_runtime7 = require("react/jsx-runtime");
1187
1291
  function computeDiff(prev, curr) {
1188
1292
  const entries = [];
@@ -1212,9 +1316,9 @@ function fmt(v2) {
1212
1316
  return String(v2);
1213
1317
  }
1214
1318
  var diffColors = {
1215
- added: { bg: "rgba(34,197,94,0.10)", fg: "#22c55e", icon: "+" },
1216
- removed: { bg: "rgba(239,68,68,0.10)", fg: "#ef4444", icon: "-" },
1217
- 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: "~" },
1218
1322
  unchanged: { bg: "transparent", fg: "", icon: " " }
1219
1323
  };
1220
1324
  function ScopeDiff({
@@ -1226,7 +1330,7 @@ function ScopeDiff({
1226
1330
  className,
1227
1331
  style
1228
1332
  }) {
1229
- const entries = (0, import_react7.useMemo)(() => computeDiff(previous, current), [previous, current]);
1333
+ const entries = (0, import_react8.useMemo)(() => computeDiff(previous, current), [previous, current]);
1230
1334
  const visible = hideUnchanged ? entries.filter((e) => e.type !== "unchanged") : entries;
1231
1335
  const fs = fontSize[size];
1232
1336
  const pad = padding[size];
@@ -1430,7 +1534,7 @@ function ResultPanel({
1430
1534
  }
1431
1535
 
1432
1536
  // src/components/StageDetailPanel/StageDetailPanel.tsx
1433
- var import_react8 = require("react");
1537
+ var import_react9 = require("react");
1434
1538
  var import_jsx_runtime9 = require("react/jsx-runtime");
1435
1539
  function computeChanges(prev, curr) {
1436
1540
  const changes = [];
@@ -1584,7 +1688,7 @@ function DevView({
1584
1688
  fs,
1585
1689
  pad
1586
1690
  }) {
1587
- const rows = (0, import_react8.useMemo)(() => buildMemoryRows(currMemory, changes), [currMemory, changes]);
1691
+ const rows = (0, import_react9.useMemo)(() => buildMemoryRows(currMemory, changes), [currMemory, changes]);
1588
1692
  const totalKeys = Object.keys(currMemory).length;
1589
1693
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1590
1694
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
@@ -1786,7 +1890,7 @@ function UnstyledDevView({
1786
1890
  changes,
1787
1891
  currMemory
1788
1892
  }) {
1789
- const rows = (0, import_react8.useMemo)(() => buildMemoryRows(currMemory, changes), [currMemory, changes]);
1893
+ const rows = (0, import_react9.useMemo)(() => buildMemoryRows(currMemory, changes), [currMemory, changes]);
1790
1894
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-fp": "stage-detail-dev", children: [
1791
1895
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-fp": "stage-label", children: snapshot.stageLabel }),
1792
1896
  rows.map((row) => {
@@ -1862,9 +1966,9 @@ function StageDetailPanel({
1862
1966
  className,
1863
1967
  style
1864
1968
  }) {
1865
- const [internalMode, setInternalMode] = (0, import_react8.useState)(controlledMode ?? "simple");
1969
+ const [internalMode, setInternalMode] = (0, import_react9.useState)(controlledMode ?? "simple");
1866
1970
  const activeMode = controlledMode ?? internalMode;
1867
- const handleToggle = (0, import_react8.useCallback)(() => {
1971
+ const handleToggle = (0, import_react9.useCallback)(() => {
1868
1972
  const next = activeMode === "simple" ? "dev" : "simple";
1869
1973
  setInternalMode(next);
1870
1974
  onModeChange?.(next);
@@ -1872,7 +1976,7 @@ function StageDetailPanel({
1872
1976
  const snapshot = snapshots[selectedIndex];
1873
1977
  const prevMemory = selectedIndex > 0 ? snapshots[selectedIndex - 1]?.memory ?? null : null;
1874
1978
  const currMemory = snapshot?.memory ?? {};
1875
- const changes = (0, import_react8.useMemo)(
1979
+ const changes = (0, import_react9.useMemo)(
1876
1980
  () => computeChanges(prevMemory, currMemory),
1877
1981
  [prevMemory, currMemory]
1878
1982
  );
@@ -1908,7 +2012,7 @@ function StageDetailPanel({
1908
2012
  }
1909
2013
 
1910
2014
  // src/components/TimeTravelControls/TimeTravelControls.tsx
1911
- var import_react9 = require("react");
2015
+ var import_react10 = require("react");
1912
2016
  var import_jsx_runtime10 = require("react/jsx-runtime");
1913
2017
  function TimeTravelControls({
1914
2018
  snapshots,
@@ -1920,12 +2024,12 @@ function TimeTravelControls({
1920
2024
  className,
1921
2025
  style
1922
2026
  }) {
1923
- const [playing, setPlaying] = (0, import_react9.useState)(false);
1924
- const playRef = (0, import_react9.useRef)(null);
2027
+ const [playing, setPlaying] = (0, import_react10.useState)(false);
2028
+ const playRef = (0, import_react10.useRef)(null);
1925
2029
  const total = snapshots.length;
1926
2030
  const canPrev = selectedIndex > 0;
1927
2031
  const canNext = selectedIndex < total - 1;
1928
- (0, import_react9.useEffect)(() => {
2032
+ (0, import_react10.useEffect)(() => {
1929
2033
  if (!playing || !autoPlayable) return;
1930
2034
  if (selectedIndex >= total - 1) {
1931
2035
  setPlaying(false);
@@ -1943,7 +2047,7 @@ function TimeTravelControls({
1943
2047
  if (playRef.current) clearTimeout(playRef.current);
1944
2048
  };
1945
2049
  }, [playing, selectedIndex, snapshots, total, onIndexChange, autoPlayable]);
1946
- const togglePlay = (0, import_react9.useCallback)(() => {
2050
+ const togglePlay = (0, import_react10.useCallback)(() => {
1947
2051
  if (playing) {
1948
2052
  setPlaying(false);
1949
2053
  } else {
@@ -1951,49 +2055,80 @@ function TimeTravelControls({
1951
2055
  setPlaying(true);
1952
2056
  }
1953
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
+ );
1954
2075
  const fs = fontSize[size];
1955
2076
  if (unstyled) {
1956
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, style, "data-fp": "time-travel-controls", children: [
1957
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1958
- "button",
1959
- {
1960
- "data-fp": "tt-prev",
1961
- disabled: !canPrev || playing,
1962
- onClick: () => {
1963
- setPlaying(false);
1964
- onIndexChange(selectedIndex - 1);
1965
- },
1966
- children: "Prev"
1967
- }
1968
- ),
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)(
1971
- "button",
1972
- {
1973
- "data-fp": "tt-next",
1974
- disabled: !canNext || playing,
1975
- onClick: () => {
1976
- setPlaying(false);
1977
- onIndexChange(selectedIndex + 1);
1978
- },
1979
- children: "Next"
1980
- }
1981
- ),
1982
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1983
- "button",
1984
- {
1985
- "data-fp": "tt-tick",
1986
- "data-active": i === selectedIndex,
1987
- "data-done": i < selectedIndex,
1988
- onClick: () => {
1989
- setPlaying(false);
1990
- onIndexChange(i);
1991
- },
1992
- title: snap.stageLabel
1993
- },
1994
- i
1995
- )) })
1996
- ] });
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
+ );
1997
2132
  }
1998
2133
  const btnStyle = (disabled) => ({
1999
2134
  background: theme.bgTertiary,
@@ -2022,6 +2157,10 @@ function TimeTravelControls({
2022
2157
  ...style
2023
2158
  },
2024
2159
  "data-fp": "time-travel-controls",
2160
+ role: "toolbar",
2161
+ "aria-label": "Time travel controls",
2162
+ tabIndex: 0,
2163
+ onKeyDown: handleKeyDown,
2025
2164
  children: [
2026
2165
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2027
2166
  "button",
@@ -2032,6 +2171,7 @@ function TimeTravelControls({
2032
2171
  setPlaying(false);
2033
2172
  onIndexChange(selectedIndex - 1);
2034
2173
  },
2174
+ "aria-label": "Previous stage",
2035
2175
  children: "\u25C0"
2036
2176
  }
2037
2177
  ),
@@ -2054,6 +2194,7 @@ function TimeTravelControls({
2054
2194
  flexShrink: 0
2055
2195
  },
2056
2196
  title: playing ? "Pause" : "Play",
2197
+ "aria-label": playing ? "Pause" : "Play",
2057
2198
  children: playing ? "\u23F8" : "\u25B6"
2058
2199
  }
2059
2200
  ),
@@ -2066,6 +2207,7 @@ function TimeTravelControls({
2066
2207
  setPlaying(false);
2067
2208
  onIndexChange(selectedIndex + 1);
2068
2209
  },
2210
+ "aria-label": "Next stage",
2069
2211
  children: "\u25B6"
2070
2212
  }
2071
2213
  ),
@@ -2112,125 +2254,146 @@ function TimeTravelControls({
2112
2254
  }
2113
2255
 
2114
2256
  // src/components/ExplainableShell/ExplainableShell.tsx
2115
- var import_react10 = 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
2116
2382
  var import_jsx_runtime11 = require("react/jsx-runtime");
2117
- function ExplainableShell({
2383
+ function MemoryPanel({
2118
2384
  snapshots,
2119
- resultData,
2120
- logs = [],
2121
- narrative = [],
2122
- tabs = ["result", "explainable", "ai-compatible"],
2123
- defaultTab,
2124
- hideConsole = false,
2125
- renderFlowchart,
2385
+ selectedIndex,
2126
2386
  size = "default",
2127
2387
  unstyled = false,
2128
2388
  className,
2129
2389
  style
2130
2390
  }) {
2131
- const [activeTab, setActiveTab] = (0, import_react10.useState)(defaultTab ?? tabs[0]);
2132
- const [snapshotIdx, setSnapshotIdx] = (0, import_react10.useState)(0);
2133
- const fs = fontSize[size];
2134
- const pad = padding[size];
2135
- const handleSnapshotChange = (0, import_react10.useCallback)((idx) => {
2136
- setSnapshotIdx(Math.max(0, Math.min(idx, snapshots.length - 1)));
2137
- }, [snapshots.length]);
2138
- const revealedCount = (0, import_react10.useMemo)(() => {
2139
- if (snapshots.length === 0 || narrative.length === 0) return narrative.length;
2140
- const boundaries = [];
2141
- for (let i = 0; i < narrative.length; i++) {
2142
- const trimmed = narrative[i].trimStart();
2143
- if (trimmed.startsWith("Stage ") && !trimmed.match(/^Stage\s+\d+:\s*Step\s/) || trimmed.startsWith("[")) {
2144
- boundaries.push(i);
2145
- }
2146
- }
2147
- if (boundaries.length === 0) {
2148
- const ratio = (snapshotIdx + 1) / snapshots.length;
2149
- return Math.max(1, Math.ceil(narrative.length * ratio));
2150
- }
2151
- const groupsToShow = Math.max(
2152
- 1,
2153
- Math.min(
2154
- Math.floor((snapshotIdx + 1) / snapshots.length * boundaries.length) || 1,
2155
- boundaries.length
2156
- )
2157
- );
2158
- const endIdx = groupsToShow < boundaries.length ? boundaries[groupsToShow] : narrative.length;
2159
- return Math.max(1, endIdx);
2160
- }, [snapshots.length, snapshotIdx, narrative]);
2161
- const prevMemory = snapshotIdx > 0 ? snapshots[snapshotIdx - 1]?.memory : null;
2162
- const currMemory = snapshots[snapshotIdx]?.memory ?? {};
2163
- const tabLabels = {
2164
- result: "Result",
2165
- explainable: "Explainable",
2166
- "ai-compatible": "AI-Compatible"
2167
- };
2391
+ const prevMemory = selectedIndex > 0 ? snapshots[selectedIndex - 1]?.memory ?? null : null;
2392
+ const currMemory = snapshots[selectedIndex]?.memory ?? {};
2168
2393
  if (unstyled) {
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)(
2171
- "button",
2172
- {
2173
- "data-fp": "shell-tab",
2174
- "data-active": tab === activeTab,
2175
- onClick: () => setActiveTab(tab),
2176
- children: tabLabels[tab]
2177
- },
2178
- tab
2179
- )) }),
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)(
2182
- ResultPanel,
2183
- {
2184
- data: resultData ?? null,
2185
- logs,
2186
- hideConsole,
2187
- unstyled: true
2188
- }
2189
- ),
2190
- activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2191
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2192
- TimeTravelControls,
2193
- {
2194
- snapshots,
2195
- selectedIndex: snapshotIdx,
2196
- onIndexChange: handleSnapshotChange,
2197
- unstyled: true
2198
- }
2199
- ),
2200
- renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
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)(
2204
- GanttTimeline,
2205
- {
2206
- snapshots,
2207
- selectedIndex: snapshotIdx,
2208
- onSelect: handleSnapshotChange,
2209
- unstyled: true
2210
- }
2211
- )
2212
- ] }),
2213
- activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2214
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2215
- TimeTravelControls,
2216
- {
2217
- snapshots,
2218
- selectedIndex: snapshotIdx,
2219
- onIndexChange: handleSnapshotChange,
2220
- unstyled: true
2221
- }
2222
- ),
2223
- renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
2224
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2225
- NarrativeTrace,
2226
- {
2227
- narrative,
2228
- revealedCount,
2229
- unstyled: true
2230
- }
2231
- )
2232
- ] })
2233
- ] })
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 })
2234
2397
  ] });
2235
2398
  }
2236
2399
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -2238,156 +2401,251 @@ function ExplainableShell({
2238
2401
  {
2239
2402
  className,
2240
2403
  style: {
2241
- height: "100%",
2404
+ overflow: "auto",
2242
2405
  display: "flex",
2243
2406
  flexDirection: "column",
2244
- overflow: "hidden",
2245
- background: theme.bgPrimary,
2246
- color: theme.textPrimary,
2247
- fontFamily: theme.fontSans,
2248
2407
  ...style
2249
2408
  },
2250
- "data-fp": "explainable-shell",
2409
+ "data-fp": "memory-panel",
2251
2410
  children: [
2252
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2253
- "div",
2254
- {
2255
- style: {
2256
- display: "flex",
2257
- gap: 0,
2258
- borderBottom: `1px solid ${theme.border}`,
2259
- background: theme.bgSecondary,
2260
- flexShrink: 0
2261
- },
2262
- children: tabs.map((tab) => {
2263
- const active = tab === activeTab;
2264
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2265
- "button",
2266
- {
2267
- onClick: () => setActiveTab(tab),
2268
- style: {
2269
- padding: `${pad - 4}px ${pad}px`,
2270
- fontSize: fs.label,
2271
- fontWeight: active ? 700 : 500,
2272
- textTransform: "uppercase",
2273
- letterSpacing: "0.08em",
2274
- color: active ? theme.primary : theme.textMuted,
2275
- background: "transparent",
2276
- border: "none",
2277
- borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
2278
- cursor: "pointer",
2279
- transition: "all 0.15s ease"
2280
- },
2281
- children: tabLabels[tab]
2282
- },
2283
- tab
2284
- );
2285
- })
2286
- }
2287
- ),
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)(
2290
- ResultPanel,
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",
2291
2486
  {
2292
- data: resultData ?? null,
2293
- logs,
2294
- hideConsole,
2295
- size
2296
- }
2297
- ),
2298
- activeTab === "explainable" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2299
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2300
- TimeTravelControls,
2301
- {
2302
- snapshots,
2303
- selectedIndex: snapshotIdx,
2304
- onIndexChange: handleSnapshotChange,
2305
- size
2306
- }
2307
- ),
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)(
2311
- "div",
2312
- {
2313
- style: {
2314
- width: renderFlowchart ? "40%" : "100%",
2315
- minWidth: 280,
2316
- overflow: "auto",
2317
- display: "flex",
2318
- flexDirection: "column"
2319
- },
2320
- children: [
2321
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2322
- MemoryInspector,
2323
- {
2324
- snapshots,
2325
- selectedIndex: snapshotIdx,
2326
- size
2327
- }
2328
- ),
2329
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2330
- ScopeDiff,
2331
- {
2332
- previous: prevMemory,
2333
- current: currMemory,
2334
- hideUnchanged: true,
2335
- size
2336
- }
2337
- ) })
2338
- ]
2339
- }
2340
- )
2341
- ] }),
2342
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { borderTop: `1px solid ${theme.border}`, flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2343
- GanttTimeline,
2344
- {
2345
- snapshots,
2346
- selectedIndex: snapshotIdx,
2347
- onSelect: handleSnapshotChange,
2348
- size
2349
- }
2350
- ) })
2351
- ] }),
2352
- activeTab === "ai-compatible" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2353
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2354
- TimeTravelControls,
2355
- {
2356
- snapshots,
2357
- selectedIndex: snapshotIdx,
2358
- onIndexChange: handleSnapshotChange,
2359
- size
2360
- }
2361
- ),
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)(
2365
- NarrativeTrace,
2366
- {
2367
- narrative,
2368
- revealedCount,
2369
- size,
2370
- style: {
2371
- width: renderFlowchart ? "40%" : "100%",
2372
- minWidth: 280
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
2373
2511
  }
2374
- }
2375
- )
2376
- ] })
2377
- ] })
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..."
2378
2542
  ] })
2379
2543
  ]
2380
2544
  }
2381
2545
  );
2382
2546
  }
2383
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
+
2384
2640
  // src/components/FlowchartView/SubflowTree.tsx
2385
- var import_react11 = require("react");
2386
- var import_jsx_runtime12 = require("react/jsx-runtime");
2641
+ var import_react13 = require("react");
2642
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2387
2643
  function specToTree(node) {
2644
+ if (!node) return [];
2388
2645
  const entries = [];
2389
2646
  const seen = /* @__PURE__ */ new Set();
2390
2647
  function walk(n) {
2648
+ if (!n) return;
2391
2649
  const id = n.name || n.id || "";
2392
2650
  if (seen.has(id)) return;
2393
2651
  seen.add(id);
@@ -2403,7 +2661,7 @@ function specToTree(node) {
2403
2661
  entries.push(entry);
2404
2662
  if (n.children) {
2405
2663
  for (const child of n.children) {
2406
- walk(child);
2664
+ if (child) walk(child);
2407
2665
  }
2408
2666
  }
2409
2667
  if (n.next) {
@@ -2413,25 +2671,25 @@ function specToTree(node) {
2413
2671
  walk(node);
2414
2672
  return entries;
2415
2673
  }
2416
- var TreeNode = (0, import_react11.memo)(function TreeNode2({
2674
+ var TreeNode = (0, import_react13.memo)(function TreeNode2({
2417
2675
  entry,
2418
2676
  depth,
2419
2677
  activeStage,
2420
2678
  doneStages,
2421
2679
  onNodeSelect
2422
2680
  }) {
2423
- const [expanded, setExpanded] = (0, import_react11.useState)(true);
2681
+ const [expanded, setExpanded] = (0, import_react13.useState)(true);
2424
2682
  const hasChildren = entry.children && entry.children.length > 0;
2425
2683
  const isActive = activeStage === entry.name;
2426
2684
  const isDone = doneStages?.has(entry.name);
2427
- const handleClick = (0, import_react11.useCallback)(() => {
2685
+ const handleClick = (0, import_react13.useCallback)(() => {
2428
2686
  if (hasChildren) {
2429
2687
  setExpanded((prev) => !prev);
2430
2688
  }
2431
2689
  onNodeSelect?.(entry.name, !!entry.isSubflow);
2432
2690
  }, [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)(
2691
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2692
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2435
2693
  "button",
2436
2694
  {
2437
2695
  onClick: handleClick,
@@ -2462,7 +2720,7 @@ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2462
2720
  }
2463
2721
  },
2464
2722
  children: [
2465
- hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2723
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2466
2724
  "span",
2467
2725
  {
2468
2726
  style: {
@@ -2477,8 +2735,8 @@ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2477
2735
  },
2478
2736
  children: "\u25B6"
2479
2737
  }
2480
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { width: 12, flexShrink: 0 } }),
2481
- /* @__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)(
2482
2740
  "span",
2483
2741
  {
2484
2742
  style: {
@@ -2490,8 +2748,8 @@ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2490
2748
  }
2491
2749
  }
2492
2750
  ),
2493
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
2494
- /* @__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)(
2495
2753
  "span",
2496
2754
  {
2497
2755
  style: {
@@ -2503,11 +2761,11 @@ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2503
2761
  },
2504
2762
  children: [
2505
2763
  entry.name,
2506
- 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" })
2507
2765
  ]
2508
2766
  }
2509
2767
  ),
2510
- entry.description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2768
+ entry.description && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2511
2769
  "span",
2512
2770
  {
2513
2771
  style: {
@@ -2524,7 +2782,7 @@ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2524
2782
  ]
2525
2783
  }
2526
2784
  ),
2527
- 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)(
2528
2786
  TreeNode2,
2529
2787
  {
2530
2788
  entry: child,
@@ -2533,12 +2791,12 @@ var TreeNode = (0, import_react11.memo)(function TreeNode2({
2533
2791
  doneStages,
2534
2792
  onNodeSelect
2535
2793
  },
2536
- `${child.name}-${i}`
2794
+ child.subflowId ?? `${child.name}-${i}`
2537
2795
  )) })
2538
2796
  ] });
2539
2797
  });
2540
- var SectionLabel = (0, import_react11.memo)(function SectionLabel2({ children }) {
2541
- 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)(
2542
2800
  "div",
2543
2801
  {
2544
2802
  style: {
@@ -2553,7 +2811,7 @@ var SectionLabel = (0, import_react11.memo)(function SectionLabel2({ children })
2553
2811
  }
2554
2812
  );
2555
2813
  });
2556
- var SubflowTree = (0, import_react11.memo)(function SubflowTree2({
2814
+ var SubflowTree = (0, import_react13.memo)(function SubflowTree2({
2557
2815
  spec,
2558
2816
  activeStage,
2559
2817
  doneStages,
@@ -2562,21 +2820,10 @@ var SubflowTree = (0, import_react11.memo)(function SubflowTree2({
2562
2820
  className,
2563
2821
  style
2564
2822
  }) {
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)(
2823
+ const tree = (0, import_react13.useMemo)(() => specToTree(spec), [spec]);
2824
+ const subflowStages = (0, import_react13.useMemo)(() => tree.filter((e) => e.isSubflow), [tree]);
2825
+ if (subflowStages.length === 0) return null;
2826
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2580
2827
  "div",
2581
2828
  {
2582
2829
  className,
@@ -2594,103 +2841,612 @@ var SubflowTree = (0, import_react11.memo)(function SubflowTree2({
2594
2841
  ...style
2595
2842
  },
2596
2843
  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
- ] })
2844
+ !unstyled && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SectionLabel, { children: "Subflows" }),
2845
+ subflowStages.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2846
+ TreeNode,
2847
+ {
2848
+ entry,
2849
+ depth: 0,
2850
+ activeStage,
2851
+ doneStages,
2852
+ onNodeSelect
2853
+ },
2854
+ entry.subflowId ?? `${entry.name}-${i}`
2855
+ ))
2604
2856
  ]
2605
2857
  }
2606
2858
  );
2607
2859
  });
2608
2860
 
2609
- // src/adapters/fromRuntimeSnapshot.ts
2610
- function toVisualizationSnapshots(runtime) {
2611
- const snapshots = [];
2612
- flattenTree(runtime.executionTree, snapshots, runtime.sharedState, 0, runtime.subflowResults, {});
2613
- return snapshots;
2614
- }
2615
- function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults, cumulativeMemory = {}) {
2616
- const durationMs = typeof node.metrics?.durationMs === "number" ? node.metrics.durationMs : 1;
2617
- const startMs = accumulatedMs;
2618
- const narrative = buildNarrative(node);
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;
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
+ ]
2626
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
+ };
3101
+ }
3102
+ function extractSubflowNarrative(entries, subflowName) {
3103
+ const result = [];
3104
+ let inside = false;
3105
+ for (const entry of entries) {
3106
+ if (entry.type === "subflow" && entry.text.includes(subflowName) && entry.text.startsWith("Entering")) {
3107
+ inside = true;
3108
+ continue;
2627
3109
  }
3110
+ if (inside && entry.type === "subflow" && entry.text.includes(subflowName) && entry.text.startsWith("Exiting")) break;
3111
+ if (inside) result.push(entry);
2628
3112
  }
2629
- const stageId = node.name || node.id;
2630
- const sfResult = subflowResults?.[node.subflowId ?? stageId];
2631
- out.push({
2632
- stageName: stageId,
2633
- stageLabel: stageId,
2634
- memory,
2635
- narrative,
2636
- startMs,
2637
- durationMs,
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
2642
- });
2643
- let nextMs = startMs + durationMs;
2644
- if (node.children && node.children.length > 0) {
2645
- let maxChildEnd = nextMs;
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) {
2646
3118
  for (const child of node.children) {
2647
- const childEnd = flattenTree(child, out, sharedState, nextMs, subflowResults, memory);
2648
- maxChildEnd = Math.max(maxChildEnd, childEnd);
3119
+ const f = findSubflowSpecNode(child, name);
3120
+ if (f) return f;
2649
3121
  }
2650
- nextMs = maxChildEnd;
2651
- }
2652
- if (node.next) {
2653
- nextMs = flattenTree(node.next, out, sharedState, nextMs, subflowResults, memory);
2654
3122
  }
2655
- return nextMs;
3123
+ if (node.next) return findSubflowSpecNode(node.next, name);
3124
+ return null;
2656
3125
  }
2657
- function buildNarrative(node) {
2658
- const parts = [];
2659
- if (node.name) {
2660
- parts.push(`Stage "${node.name}" executed.`);
2661
- }
2662
- if (node.stageWrites && Object.keys(node.stageWrites).length > 0) {
2663
- const keys = Object.keys(node.stageWrites);
2664
- parts.push(`Wrote ${keys.length} key(s): ${keys.join(", ")}.`);
2665
- }
2666
- if (node.errors && Object.keys(node.errors).length > 0) {
2667
- parts.push(`Errors: ${JSON.stringify(node.errors)}`);
2668
- }
2669
- if (node.isFork) {
2670
- parts.push(
2671
- `Forked into ${node.children?.length ?? 0} parallel branch(es).`
2672
- );
2673
- }
2674
- return parts.join(" ") || `Stage ${node.id} completed.`;
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;
2675
3132
  }
2676
- function createSnapshots(stages) {
2677
- let accMs = 0;
2678
- return stages.map((s) => {
2679
- const duration = s.durationMs ?? 1;
2680
- const snap = {
2681
- stageName: s.name,
2682
- stageLabel: s.label ?? s.name,
2683
- memory: s.memory ?? {},
2684
- narrative: s.narrative ?? `${s.label ?? s.name} completed.`,
2685
- startMs: accMs,
2686
- durationMs: duration,
2687
- status: "done",
2688
- ...s.description ? { description: s.description } : void 0,
2689
- ...s.subflowId ? { subflowId: s.subflowId } : void 0
2690
- };
2691
- accMs += duration;
2692
- return snap;
2693
- });
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
+ }
3279
+ } else {
3280
+ setDrillDownStack([]);
3281
+ const idx = snapshots.findIndex((s) => s.stageLabel === name);
3282
+ if (idx >= 0) setSnapshotIdx(idx);
3283
+ }
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
+ ] });
3307
+ }
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
+ ]
3448
+ }
3449
+ );
2694
3450
  }
2695
3451
  // Annotate the CommonJS export names for ESM import in node:
2696
3452
  0 && (module.exports = {
@@ -2698,20 +3454,27 @@ function createSnapshots(stages) {
2698
3454
  FootprintTheme,
2699
3455
  GanttTimeline,
2700
3456
  MemoryInspector,
3457
+ MemoryPanel,
2701
3458
  NarrativeLog,
3459
+ NarrativePanel,
2702
3460
  NarrativeTrace,
2703
3461
  ResultPanel,
2704
3462
  ScopeDiff,
2705
3463
  SnapshotPanel,
2706
3464
  StageDetailPanel,
3465
+ StoryNarrative,
2707
3466
  SubflowTree,
2708
3467
  TimeTravelControls,
2709
3468
  coolDark,
3469
+ coolLight,
2710
3470
  createSnapshots,
2711
3471
  defaultTokens,
3472
+ rawDefaults,
3473
+ subflowResultToSnapshots,
2712
3474
  themePresets,
2713
3475
  toVisualizationSnapshots,
2714
3476
  tokensToCSSVars,
3477
+ useDarkModeTokens,
2715
3478
  useFootprintTheme,
2716
3479
  warmDark,
2717
3480
  warmLight