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.js CHANGED
@@ -23,7 +23,7 @@ function tokensToCSSVars(tokens) {
23
23
  if (tokens.fontFamily?.mono) vars["--fp-font-mono"] = tokens.fontFamily.mono;
24
24
  return vars;
25
25
  }
26
- var defaultTokens = {
26
+ var rawDefaults = {
27
27
  colors: {
28
28
  primary: "#6366f1",
29
29
  success: "#22c55e",
@@ -43,6 +43,26 @@ var defaultTokens = {
43
43
  mono: "'JetBrains Mono', 'Fira Code', monospace"
44
44
  }
45
45
  };
46
+ var defaultTokens = {
47
+ colors: {
48
+ primary: `var(--fp-color-primary, ${rawDefaults.colors.primary})`,
49
+ success: `var(--fp-color-success, ${rawDefaults.colors.success})`,
50
+ error: `var(--fp-color-error, ${rawDefaults.colors.error})`,
51
+ warning: `var(--fp-color-warning, ${rawDefaults.colors.warning})`,
52
+ bgPrimary: `var(--fp-bg-primary, ${rawDefaults.colors.bgPrimary})`,
53
+ bgSecondary: `var(--fp-bg-secondary, ${rawDefaults.colors.bgSecondary})`,
54
+ bgTertiary: `var(--fp-bg-tertiary, ${rawDefaults.colors.bgTertiary})`,
55
+ textPrimary: `var(--fp-text-primary, ${rawDefaults.colors.textPrimary})`,
56
+ textSecondary: `var(--fp-text-secondary, ${rawDefaults.colors.textSecondary})`,
57
+ textMuted: `var(--fp-text-muted, ${rawDefaults.colors.textMuted})`,
58
+ border: `var(--fp-border, ${rawDefaults.colors.border})`
59
+ },
60
+ radius: `var(--fp-radius, ${rawDefaults.radius})`,
61
+ fontFamily: {
62
+ sans: `var(--fp-font-sans, ${rawDefaults.fontFamily.sans})`,
63
+ mono: `var(--fp-font-mono, ${rawDefaults.fontFamily.mono})`
64
+ }
65
+ };
46
66
 
47
67
  // src/theme/ThemeProvider.tsx
48
68
  import { jsx } from "react/jsx-runtime";
@@ -147,14 +167,57 @@ var warmLight = {
147
167
  mono: "'JetBrains Mono', 'Fira Code', monospace"
148
168
  }
149
169
  };
170
+ var coolLight = {
171
+ colors: {
172
+ primary: "#6366f1",
173
+ success: "#22c55e",
174
+ error: "#ef4444",
175
+ warning: "#f59e0b",
176
+ bgPrimary: "#ffffff",
177
+ bgSecondary: "#f9fafb",
178
+ bgTertiary: "#e5e7eb",
179
+ textPrimary: "#18181b",
180
+ textSecondary: "#52525b",
181
+ textMuted: "#a1a1aa",
182
+ border: "#e5e7eb"
183
+ },
184
+ radius: "8px",
185
+ fontFamily: {
186
+ sans: "Inter, system-ui, -apple-system, sans-serif",
187
+ mono: "'JetBrains Mono', 'Fira Code', monospace"
188
+ }
189
+ };
150
190
  var themePresets = {
151
191
  coolDark,
192
+ coolLight,
152
193
  warmDark,
153
194
  warmLight
154
195
  };
155
196
 
197
+ // src/theme/useDarkModeTokens.ts
198
+ import { useState, useEffect } from "react";
199
+ function useDarkModeTokens(options) {
200
+ const lightTokens = options?.light ?? coolLight;
201
+ const darkTokens = options?.dark ?? coolDark;
202
+ const [isDark, setIsDark] = useState(
203
+ () => document.documentElement.classList.contains(options?.selector ?? "dark")
204
+ );
205
+ useEffect(() => {
206
+ const cls = options?.selector ?? "dark";
207
+ const obs = new MutationObserver(() => {
208
+ setIsDark(document.documentElement.classList.contains(cls));
209
+ });
210
+ obs.observe(document.documentElement, {
211
+ attributes: true,
212
+ attributeFilter: ["class"]
213
+ });
214
+ return () => obs.disconnect();
215
+ }, [options?.selector]);
216
+ return isDark ? darkTokens : lightTokens;
217
+ }
218
+
156
219
  // src/components/MemoryInspector/MemoryInspector.tsx
157
- import { useMemo } from "react";
220
+ import { useMemo, useRef } from "react";
158
221
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
159
222
  function MemoryInspector({
160
223
  data,
@@ -167,6 +230,7 @@ function MemoryInspector({
167
230
  className,
168
231
  style
169
232
  }) {
233
+ const cacheRef = useRef(null);
170
234
  const { memory, newKeys } = useMemo(() => {
171
235
  if (data) {
172
236
  return { memory: data, newKeys: /* @__PURE__ */ new Set() };
@@ -174,21 +238,37 @@ function MemoryInspector({
174
238
  if (!snapshots || snapshots.length === 0) {
175
239
  return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
176
240
  }
177
- const merged = {};
178
- for (let i = 0; i <= Math.min(selectedIndex, snapshots.length - 1); i++) {
179
- Object.assign(merged, snapshots[i]?.memory);
241
+ const safeIdx = Math.min(selectedIndex, snapshots.length - 1);
242
+ let merged;
243
+ const cache = cacheRef.current;
244
+ if (cache && cache.snapshots === snapshots && cache.index <= safeIdx) {
245
+ merged = { ...cache.accumulated };
246
+ for (let i = cache.index + 1; i <= safeIdx; i++) {
247
+ Object.assign(merged, snapshots[i]?.memory);
248
+ }
249
+ } else {
250
+ merged = {};
251
+ for (let i = 0; i <= safeIdx; i++) {
252
+ Object.assign(merged, snapshots[i]?.memory);
253
+ }
180
254
  }
255
+ cacheRef.current = { snapshots, index: safeIdx, accumulated: merged };
181
256
  const nk = /* @__PURE__ */ new Set();
182
- if (highlightNew && selectedIndex > 0) {
183
- const prev = {};
184
- for (let i = 0; i < selectedIndex; i++) {
185
- Object.assign(prev, snapshots[i]?.memory);
257
+ if (highlightNew && safeIdx > 0) {
258
+ let prev;
259
+ if (cache && cache.snapshots === snapshots && cache.index === safeIdx - 1) {
260
+ prev = cache.accumulated;
261
+ } else {
262
+ prev = {};
263
+ for (let i = 0; i < safeIdx; i++) {
264
+ Object.assign(prev, snapshots[i]?.memory);
265
+ }
186
266
  }
187
- const current = snapshots[selectedIndex]?.memory ?? {};
267
+ const current = snapshots[safeIdx]?.memory ?? {};
188
268
  for (const k of Object.keys(current)) {
189
269
  if (!(k in prev)) nk.add(k);
190
270
  }
191
- } else if (highlightNew && selectedIndex === 0 && snapshots[0]) {
271
+ } else if (highlightNew && safeIdx === 0 && snapshots[0]) {
192
272
  for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
193
273
  }
194
274
  return { memory: merged, newKeys: nk };
@@ -197,9 +277,9 @@ function MemoryInspector({
197
277
  const fs = fontSize[size];
198
278
  const pad = padding[size];
199
279
  if (unstyled) {
200
- return /* @__PURE__ */ jsxs("div", { className, style, "data-fp": "memory-inspector", children: [
280
+ return /* @__PURE__ */ jsxs("div", { className, style, "data-fp": "memory-inspector", role: "region", "aria-label": "Memory state", children: [
201
281
  /* @__PURE__ */ jsx2("div", { "data-fp": "memory-label", children: "Memory State" }),
202
- /* @__PURE__ */ jsx2("pre", { "data-fp": "memory-json", children: JSON.stringify(memory, null, 2) })
282
+ /* @__PURE__ */ jsx2("pre", { "data-fp": "memory-json", children: /* @__PURE__ */ jsx2("code", { children: JSON.stringify(memory, null, 2) }) })
203
283
  ] });
204
284
  }
205
285
  return /* @__PURE__ */ jsxs(
@@ -212,6 +292,8 @@ function MemoryInspector({
212
292
  ...style
213
293
  },
214
294
  "data-fp": "memory-inspector",
295
+ role: "region",
296
+ "aria-label": "Memory state",
215
297
  children: [
216
298
  /* @__PURE__ */ jsx2(
217
299
  "span",
@@ -444,7 +526,7 @@ function NarrativeLog({
444
526
  }
445
527
 
446
528
  // src/components/NarrativeTrace/NarrativeTrace.tsx
447
- import { useState, useCallback, useMemo as useMemo3, useEffect, useRef } from "react";
529
+ import { useState as useState2, useCallback, useMemo as useMemo3, useEffect as useEffect2, useRef as useRef2 } from "react";
448
530
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
449
531
  function parseGroups(lines) {
450
532
  const groups = [];
@@ -476,12 +558,12 @@ function NarrativeTrace({
476
558
  const future = revealedCount != null ? narrative.slice(revealedCount) : [];
477
559
  const revealedGroups = useMemo3(() => parseGroups(revealed), [revealed]);
478
560
  const futureGroups = useMemo3(() => parseGroups(future), [future]);
479
- const [collapsedSet, setCollapsedSet] = useState(() => {
561
+ const [collapsedSet, setCollapsedSet] = useState2(() => {
480
562
  if (!defaultCollapsed) return /* @__PURE__ */ new Set();
481
563
  return new Set(parseGroups(narrative).map((g) => g.headerIdx));
482
564
  });
483
- const latestRef = useRef(null);
484
- useEffect(() => {
565
+ const latestRef = useRef2(null);
566
+ useEffect2(() => {
485
567
  latestRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
486
568
  }, [revealedGroups.length]);
487
569
  const toggle = useCallback((idx) => {
@@ -504,19 +586,32 @@ function NarrativeTrace({
504
586
  "data-fp": "narrative-header",
505
587
  "data-collapsible": group.steps.length > 0,
506
588
  "data-collapsed": collapsedSet.has(group.headerIdx),
589
+ role: group.steps.length > 0 ? "button" : void 0,
590
+ tabIndex: group.steps.length > 0 ? 0 : void 0,
591
+ "aria-expanded": group.steps.length > 0 ? !collapsedSet.has(group.headerIdx) : void 0,
592
+ "aria-label": `Stage ${gi + 1}, ${group.steps.length} steps${gi === lastIdx ? ", current" : ""}`,
507
593
  onClick: () => {
508
594
  if (group.steps.length > 0) toggle(group.headerIdx);
509
595
  onStageClick?.(group.headerIdx);
510
596
  },
597
+ onKeyDown: (e) => {
598
+ if (e.key === "Enter" || e.key === " ") {
599
+ e.preventDefault();
600
+ if (group.steps.length > 0) toggle(group.headerIdx);
601
+ onStageClick?.(group.headerIdx);
602
+ }
603
+ },
511
604
  children: group.header
512
605
  }
513
606
  ),
514
607
  !collapsedSet.has(group.headerIdx) && group.steps.map((step) => /* @__PURE__ */ jsx4("div", { "data-fp": "narrative-step", children: step.text }, step.idx))
515
608
  ] }, group.headerIdx)),
516
- futureGroups.map((group) => /* @__PURE__ */ jsxs3("div", { "data-fp": "narrative-group", "data-future": true, children: [
517
- /* @__PURE__ */ jsx4("div", { "data-fp": "narrative-header", children: group.header }),
518
- group.steps.map((step) => /* @__PURE__ */ jsx4("div", { "data-fp": "narrative-step", children: step.text }, `f-${step.idx}`))
519
- ] }, `f-${group.headerIdx}`))
609
+ futureGroups.length > 0 && /* @__PURE__ */ jsxs3("div", { "data-fp": "narrative-future-hint", children: [
610
+ futureGroups.length,
611
+ " more ",
612
+ futureGroups.length === 1 ? "stage" : "stages",
613
+ " ahead..."
614
+ ] })
520
615
  ] });
521
616
  }
522
617
  return /* @__PURE__ */ jsxs3(
@@ -546,10 +641,21 @@ function NarrativeTrace({
546
641
  /* @__PURE__ */ jsxs3(
547
642
  "div",
548
643
  {
644
+ role: hasSteps ? "button" : void 0,
645
+ tabIndex: hasSteps ? 0 : void 0,
646
+ "aria-expanded": hasSteps ? !isCollapsed : void 0,
647
+ "aria-label": `Stage ${gi + 1}, ${group.steps.length} steps${isLatest ? ", current" : ", completed"}`,
549
648
  onClick: () => {
550
649
  if (hasSteps) toggle(group.headerIdx);
551
650
  onStageClick?.(group.headerIdx);
552
651
  },
652
+ onKeyDown: (e) => {
653
+ if (e.key === "Enter" || e.key === " ") {
654
+ e.preventDefault();
655
+ if (hasSteps) toggle(group.headerIdx);
656
+ onStageClick?.(group.headerIdx);
657
+ }
658
+ },
553
659
  style: {
554
660
  fontSize: fs.body,
555
661
  lineHeight: 1.7,
@@ -608,43 +714,25 @@ function NarrativeTrace({
608
714
  group.headerIdx
609
715
  );
610
716
  }),
611
- futureGroups.length > 0 && /* @__PURE__ */ jsx4("div", { style: { opacity: 0.2 }, children: futureGroups.map((group) => /* @__PURE__ */ jsxs3("div", { style: { marginBottom: 2 }, children: [
612
- /* @__PURE__ */ jsx4(
613
- "div",
614
- {
615
- style: {
616
- fontSize: fs.body,
617
- lineHeight: 1.7,
618
- color: theme.textMuted,
619
- padding: `4px ${pad - 4}px`,
620
- borderLeft: `3px solid ${theme.border}`,
621
- fontWeight: 600,
622
- paddingLeft: pad + 12
623
- },
624
- children: group.header
625
- }
626
- ),
627
- group.steps.map((step) => /* @__PURE__ */ jsx4(
628
- "div",
629
- {
630
- style: {
631
- fontSize: fs.small,
632
- lineHeight: 1.6,
633
- color: theme.textMuted,
634
- padding: `2px ${pad - 4}px 2px ${pad + 20}px`
635
- },
636
- children: step.text
637
- },
638
- `f-${step.idx}`
639
- ))
640
- ] }, `f-${group.headerIdx}`)) })
717
+ futureGroups.length > 0 && /* @__PURE__ */ jsxs3("div", { style: {
718
+ opacity: 0.3,
719
+ fontSize: fs.small,
720
+ color: theme.textMuted,
721
+ padding: `8px ${pad}px`,
722
+ fontStyle: "italic"
723
+ }, children: [
724
+ futureGroups.length,
725
+ " more ",
726
+ futureGroups.length === 1 ? "stage" : "stages",
727
+ " ahead..."
728
+ ] })
641
729
  ]
642
730
  }
643
731
  );
644
732
  }
645
733
 
646
734
  // src/components/GanttTimeline/GanttTimeline.tsx
647
- import { useState as useState2, useMemo as useMemo4, useRef as useRef2, useEffect as useEffect2 } from "react";
735
+ import { useState as useState3, useMemo as useMemo4, useRef as useRef3, useEffect as useEffect3 } from "react";
648
736
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
649
737
  function GanttTimeline({
650
738
  snapshots,
@@ -656,9 +744,9 @@ function GanttTimeline({
656
744
  style,
657
745
  maxVisibleRows = 5
658
746
  }) {
659
- const [expanded, setExpanded] = useState2(false);
660
- const activeRowRef = useRef2(null);
661
- const scrollContainerRef = useRef2(null);
747
+ const [expanded, setExpanded] = useState3(false);
748
+ const activeRowRef = useRef3(null);
749
+ const scrollContainerRef = useRef3(null);
662
750
  const totalWallTime = useMemo4(
663
751
  () => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
664
752
  [snapshots]
@@ -670,7 +758,7 @@ function GanttTimeline({
670
758
  const rowHeight = size === "compact" ? 18 : 22;
671
759
  const collapsible = maxVisibleRows > 0 && snapshots.length > maxVisibleRows;
672
760
  const showAll = expanded || !collapsible;
673
- useEffect2(() => {
761
+ useEffect3(() => {
674
762
  if (!showAll && activeRowRef.current && scrollContainerRef.current) {
675
763
  activeRowRef.current.scrollIntoView({
676
764
  block: "nearest",
@@ -679,12 +767,15 @@ function GanttTimeline({
679
767
  }
680
768
  }, [selectedIndex, showAll]);
681
769
  if (unstyled) {
682
- return /* @__PURE__ */ jsx5("div", { className, style, "data-fp": "gantt-timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs4(
770
+ return /* @__PURE__ */ jsx5("div", { className, style, "data-fp": "gantt-timeline", role: "listbox", "aria-label": "Execution timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs4(
683
771
  "div",
684
772
  {
685
773
  "data-fp": "gantt-bar",
686
774
  "data-selected": idx === selectedIndex,
687
775
  "data-visible": idx <= selectedIndex,
776
+ role: "option",
777
+ "aria-selected": idx === selectedIndex,
778
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
688
779
  onClick: () => onSelect?.(idx),
689
780
  children: [
690
781
  /* @__PURE__ */ jsx5("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
@@ -694,7 +785,7 @@ function GanttTimeline({
694
785
  ] })
695
786
  ]
696
787
  },
697
- snap.stageName
788
+ `${snap.stageName}-${idx}`
698
789
  )) });
699
790
  }
700
791
  return /* @__PURE__ */ jsxs4(
@@ -750,6 +841,8 @@ function GanttTimeline({
750
841
  "div",
751
842
  {
752
843
  ref: scrollContainerRef,
844
+ role: "listbox",
845
+ "aria-label": "Execution timeline",
753
846
  style: {
754
847
  marginTop: 8,
755
848
  display: "flex",
@@ -770,6 +863,9 @@ function GanttTimeline({
770
863
  "div",
771
864
  {
772
865
  ref: isSelected ? activeRowRef : void 0,
866
+ role: "option",
867
+ "aria-selected": isSelected,
868
+ "aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
773
869
  onClick: () => onSelect?.(idx),
774
870
  style: {
775
871
  display: "flex",
@@ -785,6 +881,7 @@ function GanttTimeline({
785
881
  /* @__PURE__ */ jsx5(
786
882
  "span",
787
883
  {
884
+ title: snap.stageLabel,
788
885
  style: {
789
886
  width: labelWidth,
790
887
  fontSize: fs.small,
@@ -844,7 +941,7 @@ function GanttTimeline({
844
941
  )
845
942
  ]
846
943
  },
847
- snap.stageName
944
+ `${snap.stageName}-${idx}`
848
945
  );
849
946
  })
850
947
  }
@@ -881,7 +978,7 @@ function GanttTimeline({
881
978
  }
882
979
 
883
980
  // src/components/SnapshotPanel/SnapshotPanel.tsx
884
- import { useState as useState3 } from "react";
981
+ import { useState as useState4 } from "react";
885
982
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
886
983
  function SnapshotPanel({
887
984
  snapshots,
@@ -893,7 +990,7 @@ function SnapshotPanel({
893
990
  className,
894
991
  style
895
992
  }) {
896
- const [selectedIndex, setSelectedIndex] = useState3(0);
993
+ const [selectedIndex, setSelectedIndex] = useState4(0);
897
994
  const fs = fontSize[size];
898
995
  const pad = padding[size];
899
996
  if (snapshots.length === 0) {
@@ -1166,9 +1263,9 @@ function fmt(v2) {
1166
1263
  return String(v2);
1167
1264
  }
1168
1265
  var diffColors = {
1169
- added: { bg: "rgba(34,197,94,0.10)", fg: "#22c55e", icon: "+" },
1170
- removed: { bg: "rgba(239,68,68,0.10)", fg: "#ef4444", icon: "-" },
1171
- changed: { bg: "rgba(245,158,11,0.10)", fg: "#f59e0b", icon: "~" },
1266
+ added: { bg: `color-mix(in srgb, ${theme.success} 10%, transparent)`, fg: theme.success, icon: "+" },
1267
+ removed: { bg: `color-mix(in srgb, ${theme.error} 10%, transparent)`, fg: theme.error, icon: "-" },
1268
+ changed: { bg: `color-mix(in srgb, ${theme.warning} 10%, transparent)`, fg: theme.warning, icon: "~" },
1172
1269
  unchanged: { bg: "transparent", fg: "", icon: " " }
1173
1270
  };
1174
1271
  function ScopeDiff({
@@ -1384,7 +1481,7 @@ function ResultPanel({
1384
1481
  }
1385
1482
 
1386
1483
  // src/components/StageDetailPanel/StageDetailPanel.tsx
1387
- import { useState as useState4, useMemo as useMemo6, useCallback as useCallback2 } from "react";
1484
+ import { useState as useState5, useMemo as useMemo6, useCallback as useCallback2 } from "react";
1388
1485
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1389
1486
  function computeChanges(prev, curr) {
1390
1487
  const changes = [];
@@ -1816,7 +1913,7 @@ function StageDetailPanel({
1816
1913
  className,
1817
1914
  style
1818
1915
  }) {
1819
- const [internalMode, setInternalMode] = useState4(controlledMode ?? "simple");
1916
+ const [internalMode, setInternalMode] = useState5(controlledMode ?? "simple");
1820
1917
  const activeMode = controlledMode ?? internalMode;
1821
1918
  const handleToggle = useCallback2(() => {
1822
1919
  const next = activeMode === "simple" ? "dev" : "simple";
@@ -1862,7 +1959,7 @@ function StageDetailPanel({
1862
1959
  }
1863
1960
 
1864
1961
  // src/components/TimeTravelControls/TimeTravelControls.tsx
1865
- import { useState as useState5, useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3 } from "react";
1962
+ import { useState as useState6, useEffect as useEffect4, useRef as useRef4, useCallback as useCallback3 } from "react";
1866
1963
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1867
1964
  function TimeTravelControls({
1868
1965
  snapshots,
@@ -1874,12 +1971,12 @@ function TimeTravelControls({
1874
1971
  className,
1875
1972
  style
1876
1973
  }) {
1877
- const [playing, setPlaying] = useState5(false);
1878
- const playRef = useRef3(null);
1974
+ const [playing, setPlaying] = useState6(false);
1975
+ const playRef = useRef4(null);
1879
1976
  const total = snapshots.length;
1880
1977
  const canPrev = selectedIndex > 0;
1881
1978
  const canNext = selectedIndex < total - 1;
1882
- useEffect3(() => {
1979
+ useEffect4(() => {
1883
1980
  if (!playing || !autoPlayable) return;
1884
1981
  if (selectedIndex >= total - 1) {
1885
1982
  setPlaying(false);
@@ -1905,49 +2002,80 @@ function TimeTravelControls({
1905
2002
  setPlaying(true);
1906
2003
  }
1907
2004
  }, [playing, selectedIndex, total, onIndexChange]);
2005
+ const handleKeyDown = useCallback3(
2006
+ (e) => {
2007
+ if (e.key === "ArrowLeft" && canPrev && !playing) {
2008
+ e.preventDefault();
2009
+ setPlaying(false);
2010
+ onIndexChange(selectedIndex - 1);
2011
+ } else if (e.key === "ArrowRight" && canNext && !playing) {
2012
+ e.preventDefault();
2013
+ setPlaying(false);
2014
+ onIndexChange(selectedIndex + 1);
2015
+ } else if (e.key === " " && autoPlayable) {
2016
+ e.preventDefault();
2017
+ togglePlay();
2018
+ }
2019
+ },
2020
+ [canPrev, canNext, playing, selectedIndex, onIndexChange, autoPlayable, togglePlay]
2021
+ );
1908
2022
  const fs = fontSize[size];
1909
2023
  if (unstyled) {
1910
- return /* @__PURE__ */ jsxs9("div", { className, style, "data-fp": "time-travel-controls", children: [
1911
- /* @__PURE__ */ jsx10(
1912
- "button",
1913
- {
1914
- "data-fp": "tt-prev",
1915
- disabled: !canPrev || playing,
1916
- onClick: () => {
1917
- setPlaying(false);
1918
- onIndexChange(selectedIndex - 1);
1919
- },
1920
- children: "Prev"
1921
- }
1922
- ),
1923
- autoPlayable && /* @__PURE__ */ jsx10("button", { "data-fp": "tt-play", onClick: togglePlay, children: playing ? "Pause" : "Play" }),
1924
- /* @__PURE__ */ jsx10(
1925
- "button",
1926
- {
1927
- "data-fp": "tt-next",
1928
- disabled: !canNext || playing,
1929
- onClick: () => {
1930
- setPlaying(false);
1931
- onIndexChange(selectedIndex + 1);
1932
- },
1933
- children: "Next"
1934
- }
1935
- ),
1936
- /* @__PURE__ */ jsx10("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ jsx10(
1937
- "button",
1938
- {
1939
- "data-fp": "tt-tick",
1940
- "data-active": i === selectedIndex,
1941
- "data-done": i < selectedIndex,
1942
- onClick: () => {
1943
- setPlaying(false);
1944
- onIndexChange(i);
1945
- },
1946
- title: snap.stageLabel
1947
- },
1948
- i
1949
- )) })
1950
- ] });
2024
+ return /* @__PURE__ */ jsxs9(
2025
+ "div",
2026
+ {
2027
+ className,
2028
+ style,
2029
+ "data-fp": "time-travel-controls",
2030
+ role: "toolbar",
2031
+ "aria-label": "Time travel controls",
2032
+ tabIndex: 0,
2033
+ onKeyDown: handleKeyDown,
2034
+ children: [
2035
+ /* @__PURE__ */ jsx10(
2036
+ "button",
2037
+ {
2038
+ "data-fp": "tt-prev",
2039
+ disabled: !canPrev || playing,
2040
+ onClick: () => {
2041
+ setPlaying(false);
2042
+ onIndexChange(selectedIndex - 1);
2043
+ },
2044
+ "aria-label": "Previous stage",
2045
+ children: "Prev"
2046
+ }
2047
+ ),
2048
+ autoPlayable && /* @__PURE__ */ jsx10("button", { "data-fp": "tt-play", onClick: togglePlay, "aria-label": playing ? "Pause" : "Play", children: playing ? "Pause" : "Play" }),
2049
+ /* @__PURE__ */ jsx10(
2050
+ "button",
2051
+ {
2052
+ "data-fp": "tt-next",
2053
+ disabled: !canNext || playing,
2054
+ onClick: () => {
2055
+ setPlaying(false);
2056
+ onIndexChange(selectedIndex + 1);
2057
+ },
2058
+ "aria-label": "Next stage",
2059
+ children: "Next"
2060
+ }
2061
+ ),
2062
+ /* @__PURE__ */ jsx10("div", { "data-fp": "tt-ticks", children: snapshots.map((snap, i) => /* @__PURE__ */ jsx10(
2063
+ "button",
2064
+ {
2065
+ "data-fp": "tt-tick",
2066
+ "data-active": i === selectedIndex,
2067
+ "data-done": i < selectedIndex,
2068
+ onClick: () => {
2069
+ setPlaying(false);
2070
+ onIndexChange(i);
2071
+ },
2072
+ title: snap.stageLabel
2073
+ },
2074
+ i
2075
+ )) })
2076
+ ]
2077
+ }
2078
+ );
1951
2079
  }
1952
2080
  const btnStyle = (disabled) => ({
1953
2081
  background: theme.bgTertiary,
@@ -1976,6 +2104,10 @@ function TimeTravelControls({
1976
2104
  ...style
1977
2105
  },
1978
2106
  "data-fp": "time-travel-controls",
2107
+ role: "toolbar",
2108
+ "aria-label": "Time travel controls",
2109
+ tabIndex: 0,
2110
+ onKeyDown: handleKeyDown,
1979
2111
  children: [
1980
2112
  /* @__PURE__ */ jsx10(
1981
2113
  "button",
@@ -1986,6 +2118,7 @@ function TimeTravelControls({
1986
2118
  setPlaying(false);
1987
2119
  onIndexChange(selectedIndex - 1);
1988
2120
  },
2121
+ "aria-label": "Previous stage",
1989
2122
  children: "\u25C0"
1990
2123
  }
1991
2124
  ),
@@ -2008,6 +2141,7 @@ function TimeTravelControls({
2008
2141
  flexShrink: 0
2009
2142
  },
2010
2143
  title: playing ? "Pause" : "Play",
2144
+ "aria-label": playing ? "Pause" : "Play",
2011
2145
  children: playing ? "\u23F8" : "\u25B6"
2012
2146
  }
2013
2147
  ),
@@ -2020,6 +2154,7 @@ function TimeTravelControls({
2020
2154
  setPlaying(false);
2021
2155
  onIndexChange(selectedIndex + 1);
2022
2156
  },
2157
+ "aria-label": "Next stage",
2023
2158
  children: "\u25B6"
2024
2159
  }
2025
2160
  ),
@@ -2066,125 +2201,146 @@ function TimeTravelControls({
2066
2201
  }
2067
2202
 
2068
2203
  // src/components/ExplainableShell/ExplainableShell.tsx
2069
- import { useState as useState6, useCallback as useCallback4, useMemo as useMemo7 } from "react";
2070
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2071
- function ExplainableShell({
2204
+ import { memo as memo3, useState as useState8, useCallback as useCallback5, useMemo as useMemo10, useRef as useRef6, useEffect as useEffect6 } from "react";
2205
+
2206
+ // src/adapters/fromRuntimeSnapshot.ts
2207
+ function toVisualizationSnapshots(runtime, narrativeEntries) {
2208
+ const stageNarrativeMap = narrativeEntries?.length ? buildStageNarrativeMap(narrativeEntries) : /* @__PURE__ */ new Map();
2209
+ const snapshots = [];
2210
+ flattenTree(runtime.executionTree, snapshots, runtime.sharedState, 0, runtime.subflowResults, {}, stageNarrativeMap);
2211
+ return snapshots;
2212
+ }
2213
+ function buildStageNarrativeMap(entries) {
2214
+ const map = /* @__PURE__ */ new Map();
2215
+ let currentStageName;
2216
+ for (const entry of entries) {
2217
+ if (entry.stageName) {
2218
+ currentStageName = entry.stageName;
2219
+ }
2220
+ if (currentStageName) {
2221
+ if (!map.has(currentStageName)) {
2222
+ map.set(currentStageName, []);
2223
+ }
2224
+ const indent = " ".repeat(entry.depth);
2225
+ map.get(currentStageName).push(`${indent}${entry.text}`);
2226
+ }
2227
+ }
2228
+ return map;
2229
+ }
2230
+ function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults, cumulativeMemory = {}, stageNarrativeMap = /* @__PURE__ */ new Map()) {
2231
+ const durationMs = typeof node.metrics?.durationMs === "number" ? node.metrics.durationMs : 1;
2232
+ const startMs = accumulatedMs;
2233
+ const stageId = node.id || node.name || "unknown";
2234
+ const displayName = node.name || node.id || "unknown";
2235
+ const stageLines = stageNarrativeMap.get(stageId);
2236
+ let narrative;
2237
+ if (stageLines) {
2238
+ narrative = stageLines.join("\n");
2239
+ } else {
2240
+ const parts = [`${displayName} executed.`];
2241
+ if (node.description) parts.push(node.description);
2242
+ if (node.stageWrites) {
2243
+ const keys = Object.keys(node.stageWrites);
2244
+ if (keys.length > 0) parts.push(`Wrote: ${keys.join(", ")}`);
2245
+ }
2246
+ narrative = parts.join("\n");
2247
+ }
2248
+ const memory = { ...cumulativeMemory };
2249
+ if (node.stageWrites) {
2250
+ for (const [key, value] of Object.entries(node.stageWrites)) {
2251
+ if (value === void 0) {
2252
+ delete memory[key];
2253
+ } else {
2254
+ memory[key] = value;
2255
+ }
2256
+ }
2257
+ }
2258
+ const sfResult = subflowResults?.[node.subflowId ?? stageId];
2259
+ out.push({
2260
+ stageName: displayName,
2261
+ stageLabel: stageId,
2262
+ memory,
2263
+ narrative,
2264
+ startMs,
2265
+ durationMs,
2266
+ status: "done",
2267
+ ...node.description ? { description: node.description } : void 0,
2268
+ ...node.subflowId ? { subflowId: node.subflowId } : void 0,
2269
+ ...sfResult ? { subflowResult: sfResult } : void 0
2270
+ });
2271
+ let nextMs = startMs + durationMs;
2272
+ if (node.children && node.children.length > 0) {
2273
+ let maxChildEnd = nextMs;
2274
+ for (const child of node.children) {
2275
+ const childEnd = flattenTree(child, out, sharedState, nextMs, subflowResults, memory, stageNarrativeMap);
2276
+ maxChildEnd = Math.max(maxChildEnd, childEnd);
2277
+ }
2278
+ nextMs = maxChildEnd;
2279
+ }
2280
+ if (node.next) {
2281
+ nextMs = flattenTree(node.next, out, sharedState, nextMs, subflowResults, memory, stageNarrativeMap);
2282
+ }
2283
+ return nextMs;
2284
+ }
2285
+ function subflowResultToSnapshots(subflowResult, narrativeEntries) {
2286
+ if (!subflowResult || typeof subflowResult !== "object") return [];
2287
+ const sf = subflowResult;
2288
+ if (!sf.treeContext?.stageContexts) return [];
2289
+ const runtime = {
2290
+ sharedState: sf.treeContext.globalContext ?? {},
2291
+ executionTree: sf.treeContext.stageContexts,
2292
+ commitLog: sf.treeContext.history ?? []
2293
+ };
2294
+ const snapshots = toVisualizationSnapshots(runtime, narrativeEntries);
2295
+ const prefix = sf.subflowId ? `${sf.subflowId}/` : "";
2296
+ if (prefix) {
2297
+ for (const snap of snapshots) {
2298
+ if (snap.stageName.startsWith(prefix)) {
2299
+ snap.stageName = snap.stageName.slice(prefix.length);
2300
+ }
2301
+ if (snap.stageLabel.startsWith(prefix)) {
2302
+ snap.stageLabel = snap.stageLabel.slice(prefix.length);
2303
+ }
2304
+ }
2305
+ }
2306
+ return snapshots;
2307
+ }
2308
+ function createSnapshots(stages) {
2309
+ let accMs = 0;
2310
+ return stages.map((s) => {
2311
+ const duration = s.durationMs ?? 1;
2312
+ const snap = {
2313
+ stageName: s.name,
2314
+ stageLabel: s.label ?? s.name,
2315
+ memory: s.memory ?? {},
2316
+ narrative: s.narrative ?? `${s.label ?? s.name} completed.`,
2317
+ startMs: accMs,
2318
+ durationMs: duration,
2319
+ status: "done",
2320
+ ...s.description ? { description: s.description } : void 0,
2321
+ ...s.subflowId ? { subflowId: s.subflowId } : void 0
2322
+ };
2323
+ accMs += duration;
2324
+ return snap;
2325
+ });
2326
+ }
2327
+
2328
+ // src/components/MemoryPanel/MemoryPanel.tsx
2329
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2330
+ function MemoryPanel({
2072
2331
  snapshots,
2073
- resultData,
2074
- logs = [],
2075
- narrative = [],
2076
- tabs = ["result", "explainable", "ai-compatible"],
2077
- defaultTab,
2078
- hideConsole = false,
2079
- renderFlowchart,
2332
+ selectedIndex,
2080
2333
  size = "default",
2081
2334
  unstyled = false,
2082
2335
  className,
2083
2336
  style
2084
2337
  }) {
2085
- const [activeTab, setActiveTab] = useState6(defaultTab ?? tabs[0]);
2086
- const [snapshotIdx, setSnapshotIdx] = useState6(0);
2087
- const fs = fontSize[size];
2088
- const pad = padding[size];
2089
- const handleSnapshotChange = useCallback4((idx) => {
2090
- setSnapshotIdx(Math.max(0, Math.min(idx, snapshots.length - 1)));
2091
- }, [snapshots.length]);
2092
- const revealedCount = useMemo7(() => {
2093
- if (snapshots.length === 0 || narrative.length === 0) return narrative.length;
2094
- const boundaries = [];
2095
- for (let i = 0; i < narrative.length; i++) {
2096
- const trimmed = narrative[i].trimStart();
2097
- if (trimmed.startsWith("Stage ") && !trimmed.match(/^Stage\s+\d+:\s*Step\s/) || trimmed.startsWith("[")) {
2098
- boundaries.push(i);
2099
- }
2100
- }
2101
- if (boundaries.length === 0) {
2102
- const ratio = (snapshotIdx + 1) / snapshots.length;
2103
- return Math.max(1, Math.ceil(narrative.length * ratio));
2104
- }
2105
- const groupsToShow = Math.max(
2106
- 1,
2107
- Math.min(
2108
- Math.floor((snapshotIdx + 1) / snapshots.length * boundaries.length) || 1,
2109
- boundaries.length
2110
- )
2111
- );
2112
- const endIdx = groupsToShow < boundaries.length ? boundaries[groupsToShow] : narrative.length;
2113
- return Math.max(1, endIdx);
2114
- }, [snapshots.length, snapshotIdx, narrative]);
2115
- const prevMemory = snapshotIdx > 0 ? snapshots[snapshotIdx - 1]?.memory : null;
2116
- const currMemory = snapshots[snapshotIdx]?.memory ?? {};
2117
- const tabLabels = {
2118
- result: "Result",
2119
- explainable: "Explainable",
2120
- "ai-compatible": "AI-Compatible"
2121
- };
2338
+ const prevMemory = selectedIndex > 0 ? snapshots[selectedIndex - 1]?.memory ?? null : null;
2339
+ const currMemory = snapshots[selectedIndex]?.memory ?? {};
2122
2340
  if (unstyled) {
2123
- return /* @__PURE__ */ jsxs10("div", { className, style, "data-fp": "explainable-shell", children: [
2124
- /* @__PURE__ */ jsx11("div", { "data-fp": "shell-tabs", children: tabs.map((tab) => /* @__PURE__ */ jsx11(
2125
- "button",
2126
- {
2127
- "data-fp": "shell-tab",
2128
- "data-active": tab === activeTab,
2129
- onClick: () => setActiveTab(tab),
2130
- children: tabLabels[tab]
2131
- },
2132
- tab
2133
- )) }),
2134
- /* @__PURE__ */ jsxs10("div", { "data-fp": "shell-content", "data-tab": activeTab, children: [
2135
- activeTab === "result" && /* @__PURE__ */ jsx11(
2136
- ResultPanel,
2137
- {
2138
- data: resultData ?? null,
2139
- logs,
2140
- hideConsole,
2141
- unstyled: true
2142
- }
2143
- ),
2144
- activeTab === "explainable" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
2145
- /* @__PURE__ */ jsx11(
2146
- TimeTravelControls,
2147
- {
2148
- snapshots,
2149
- selectedIndex: snapshotIdx,
2150
- onIndexChange: handleSnapshotChange,
2151
- unstyled: true
2152
- }
2153
- ),
2154
- renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
2155
- /* @__PURE__ */ jsx11(MemoryInspector, { snapshots, selectedIndex: snapshotIdx, unstyled: true }),
2156
- /* @__PURE__ */ jsx11(ScopeDiff, { previous: prevMemory, current: currMemory, unstyled: true }),
2157
- /* @__PURE__ */ jsx11(
2158
- GanttTimeline,
2159
- {
2160
- snapshots,
2161
- selectedIndex: snapshotIdx,
2162
- onSelect: handleSnapshotChange,
2163
- unstyled: true
2164
- }
2165
- )
2166
- ] }),
2167
- activeTab === "ai-compatible" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
2168
- /* @__PURE__ */ jsx11(
2169
- TimeTravelControls,
2170
- {
2171
- snapshots,
2172
- selectedIndex: snapshotIdx,
2173
- onIndexChange: handleSnapshotChange,
2174
- unstyled: true
2175
- }
2176
- ),
2177
- renderFlowchart?.({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }),
2178
- /* @__PURE__ */ jsx11(
2179
- NarrativeTrace,
2180
- {
2181
- narrative,
2182
- revealedCount,
2183
- unstyled: true
2184
- }
2185
- )
2186
- ] })
2187
- ] })
2341
+ return /* @__PURE__ */ jsxs10("div", { className, style, "data-fp": "memory-panel", children: [
2342
+ /* @__PURE__ */ jsx11(MemoryInspector, { snapshots, selectedIndex, unstyled: true }),
2343
+ /* @__PURE__ */ jsx11(ScopeDiff, { previous: prevMemory, current: currMemory, unstyled: true })
2188
2344
  ] });
2189
2345
  }
2190
2346
  return /* @__PURE__ */ jsxs10(
@@ -2192,156 +2348,251 @@ function ExplainableShell({
2192
2348
  {
2193
2349
  className,
2194
2350
  style: {
2195
- height: "100%",
2351
+ overflow: "auto",
2196
2352
  display: "flex",
2197
2353
  flexDirection: "column",
2198
- overflow: "hidden",
2199
- background: theme.bgPrimary,
2200
- color: theme.textPrimary,
2201
- fontFamily: theme.fontSans,
2202
2354
  ...style
2203
2355
  },
2204
- "data-fp": "explainable-shell",
2356
+ "data-fp": "memory-panel",
2205
2357
  children: [
2206
- /* @__PURE__ */ jsx11(
2207
- "div",
2208
- {
2209
- style: {
2210
- display: "flex",
2211
- gap: 0,
2212
- borderBottom: `1px solid ${theme.border}`,
2213
- background: theme.bgSecondary,
2214
- flexShrink: 0
2215
- },
2216
- children: tabs.map((tab) => {
2217
- const active = tab === activeTab;
2218
- return /* @__PURE__ */ jsx11(
2219
- "button",
2220
- {
2221
- onClick: () => setActiveTab(tab),
2222
- style: {
2223
- padding: `${pad - 4}px ${pad}px`,
2224
- fontSize: fs.label,
2225
- fontWeight: active ? 700 : 500,
2226
- textTransform: "uppercase",
2227
- letterSpacing: "0.08em",
2228
- color: active ? theme.primary : theme.textMuted,
2229
- background: "transparent",
2230
- border: "none",
2231
- borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
2232
- cursor: "pointer",
2233
- transition: "all 0.15s ease"
2234
- },
2235
- children: tabLabels[tab]
2236
- },
2237
- tab
2238
- );
2239
- })
2240
- }
2241
- ),
2242
- /* @__PURE__ */ jsxs10("div", { style: { flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }, children: [
2243
- activeTab === "result" && /* @__PURE__ */ jsx11(
2244
- ResultPanel,
2245
- {
2246
- data: resultData ?? null,
2247
- logs,
2248
- hideConsole,
2249
- size
2250
- }
2251
- ),
2252
- activeTab === "explainable" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
2253
- /* @__PURE__ */ jsx11(
2254
- TimeTravelControls,
2255
- {
2256
- snapshots,
2257
- selectedIndex: snapshotIdx,
2258
- onIndexChange: handleSnapshotChange,
2259
- size
2260
- }
2261
- ),
2262
- /* @__PURE__ */ jsxs10("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2263
- renderFlowchart && /* @__PURE__ */ jsx11("div", { style: { flex: 1, overflow: "hidden", borderRight: `1px solid ${theme.border}` }, children: renderFlowchart({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }) }),
2264
- /* @__PURE__ */ jsxs10(
2265
- "div",
2266
- {
2267
- style: {
2268
- width: renderFlowchart ? "40%" : "100%",
2269
- minWidth: 280,
2270
- overflow: "auto",
2271
- display: "flex",
2272
- flexDirection: "column"
2273
- },
2274
- children: [
2275
- /* @__PURE__ */ jsx11(
2276
- MemoryInspector,
2277
- {
2278
- snapshots,
2279
- selectedIndex: snapshotIdx,
2280
- size
2281
- }
2282
- ),
2283
- /* @__PURE__ */ jsx11("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ jsx11(
2284
- ScopeDiff,
2285
- {
2286
- previous: prevMemory,
2287
- current: currMemory,
2288
- hideUnchanged: true,
2289
- size
2290
- }
2291
- ) })
2292
- ]
2293
- }
2294
- )
2295
- ] }),
2296
- /* @__PURE__ */ jsx11("div", { style: { borderTop: `1px solid ${theme.border}`, flexShrink: 0 }, children: /* @__PURE__ */ jsx11(
2297
- GanttTimeline,
2298
- {
2299
- snapshots,
2300
- selectedIndex: snapshotIdx,
2301
- onSelect: handleSnapshotChange,
2302
- size
2303
- }
2304
- ) })
2305
- ] }),
2306
- activeTab === "ai-compatible" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
2307
- /* @__PURE__ */ jsx11(
2308
- TimeTravelControls,
2309
- {
2310
- snapshots,
2311
- selectedIndex: snapshotIdx,
2312
- onIndexChange: handleSnapshotChange,
2313
- size
2314
- }
2315
- ),
2316
- /* @__PURE__ */ jsxs10("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2317
- renderFlowchart && /* @__PURE__ */ jsx11("div", { style: { flex: 1, overflow: "hidden", borderRight: `1px solid ${theme.border}` }, children: renderFlowchart({ snapshots, selectedIndex: snapshotIdx, onNodeClick: handleSnapshotChange }) }),
2318
- /* @__PURE__ */ jsx11(
2319
- NarrativeTrace,
2320
- {
2321
- narrative,
2322
- revealedCount,
2323
- size,
2324
- style: {
2325
- width: renderFlowchart ? "40%" : "100%",
2326
- minWidth: 280
2358
+ /* @__PURE__ */ jsx11(MemoryInspector, { snapshots, selectedIndex, size }),
2359
+ /* @__PURE__ */ jsx11("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ jsx11(ScopeDiff, { previous: prevMemory, current: currMemory, hideUnchanged: true, size }) })
2360
+ ]
2361
+ }
2362
+ );
2363
+ }
2364
+
2365
+ // src/components/NarrativePanel/NarrativePanel.tsx
2366
+ import { useMemo as useMemo8 } from "react";
2367
+
2368
+ // src/components/StoryNarrative/StoryNarrative.tsx
2369
+ import { useMemo as useMemo7, useRef as useRef5, useEffect as useEffect5 } from "react";
2370
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2371
+ var ENTRY_ICONS = {
2372
+ stage: { icon: "\u25B8", color: theme.primary, label: "Stage" },
2373
+ step: { icon: "\xB7", color: theme.textMuted, label: "Data operation" },
2374
+ condition: { icon: "\u25C7", color: theme.warning, label: "Decision" },
2375
+ fork: { icon: "\u2443", color: theme.primary, label: "Parallel" },
2376
+ subflow: { icon: "\u21B3", color: theme.textSecondary, label: "Subflow" },
2377
+ loop: { icon: "\u21BB", color: theme.warning, label: "Loop" },
2378
+ break: { icon: "\u25A0", color: theme.error, label: "Break" },
2379
+ error: { icon: "\u2717", color: theme.error, label: "Error" }
2380
+ };
2381
+ function StoryNarrative({
2382
+ entries,
2383
+ stageCount,
2384
+ size = "default",
2385
+ unstyled = false,
2386
+ className,
2387
+ style: outerStyle
2388
+ }) {
2389
+ const fs = fontSize[size];
2390
+ const pad = padding[size];
2391
+ const revealedCount = useMemo7(() => {
2392
+ let stagesSeen = 0;
2393
+ for (let i = 0; i < entries.length; i++) {
2394
+ const e = entries[i];
2395
+ const isBoundary = e.type === "stage" || e.type === "subflow" && e.text.startsWith("Entering");
2396
+ if (isBoundary) stagesSeen++;
2397
+ if (stagesSeen > stageCount) return i;
2398
+ }
2399
+ return entries.length;
2400
+ }, [entries, stageCount]);
2401
+ const revealed = entries.slice(0, revealedCount);
2402
+ const future = entries.slice(revealedCount);
2403
+ const latestRef = useRef5(null);
2404
+ useEffect5(() => {
2405
+ latestRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
2406
+ }, [revealed.length]);
2407
+ if (unstyled) {
2408
+ return /* @__PURE__ */ jsx12("div", { className, style: outerStyle, "data-fp": "story-narrative", role: "log", children: revealed.map((entry, i) => /* @__PURE__ */ jsx12("div", { "data-fp": "narrative-entry", "data-type": entry.type, children: entry.text }, i)) });
2409
+ }
2410
+ return /* @__PURE__ */ jsxs11(
2411
+ "div",
2412
+ {
2413
+ className,
2414
+ style: {
2415
+ flex: 1,
2416
+ overflow: "auto",
2417
+ padding: pad,
2418
+ fontFamily: theme.fontSans,
2419
+ ...outerStyle
2420
+ },
2421
+ "data-fp": "story-narrative",
2422
+ role: "log",
2423
+ "aria-label": "Execution narrative",
2424
+ children: [
2425
+ revealed.map((entry, i) => {
2426
+ const meta = ENTRY_ICONS[entry.type] ?? ENTRY_ICONS.step;
2427
+ const isStage = entry.type === "stage";
2428
+ const isDecision = entry.type === "condition";
2429
+ const isError = entry.type === "error";
2430
+ const isLast = i === revealed.length - 1;
2431
+ return /* @__PURE__ */ jsxs11(
2432
+ "div",
2433
+ {
2434
+ ref: isLast ? latestRef : void 0,
2435
+ style: {
2436
+ display: "flex",
2437
+ gap: 8,
2438
+ padding: isStage ? `${pad - 4}px 0` : `2px 0`,
2439
+ marginLeft: entry.depth * 16,
2440
+ borderBottom: isStage ? `1px solid ${theme.border}` : void 0,
2441
+ marginTop: isStage && i > 0 ? 8 : 0
2442
+ },
2443
+ children: [
2444
+ /* @__PURE__ */ jsx12(
2445
+ "span",
2446
+ {
2447
+ style: {
2448
+ color: meta.color,
2449
+ fontWeight: 700,
2450
+ fontSize: isStage ? fs.body : fs.small,
2451
+ width: 16,
2452
+ textAlign: "center",
2453
+ flexShrink: 0
2454
+ },
2455
+ title: meta.label,
2456
+ "aria-label": meta.label,
2457
+ children: meta.icon
2327
2458
  }
2328
- }
2329
- )
2330
- ] })
2331
- ] })
2459
+ ),
2460
+ /* @__PURE__ */ jsx12(
2461
+ "span",
2462
+ {
2463
+ style: {
2464
+ fontSize: isStage ? fs.body : fs.small,
2465
+ fontWeight: isStage ? 600 : 400,
2466
+ color: isError ? theme.error : isDecision ? theme.warning : isStage ? theme.textPrimary : theme.textSecondary,
2467
+ lineHeight: 1.6,
2468
+ fontFamily: entry.type === "step" ? theme.fontMono : theme.fontSans
2469
+ },
2470
+ children: entry.text
2471
+ }
2472
+ )
2473
+ ]
2474
+ },
2475
+ i
2476
+ );
2477
+ }),
2478
+ future.length > 0 && /* @__PURE__ */ jsxs11("div", { style: {
2479
+ opacity: 0.3,
2480
+ fontSize: fs.small,
2481
+ color: theme.textMuted,
2482
+ padding: `8px 0`,
2483
+ fontStyle: "italic"
2484
+ }, children: [
2485
+ future.length,
2486
+ " more ",
2487
+ future.length === 1 ? "entry" : "entries",
2488
+ " ahead..."
2332
2489
  ] })
2333
2490
  ]
2334
2491
  }
2335
2492
  );
2336
2493
  }
2337
2494
 
2495
+ // src/components/NarrativePanel/NarrativePanel.tsx
2496
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2497
+ function NarrativePanel({
2498
+ snapshots,
2499
+ selectedIndex,
2500
+ narrativeEntries,
2501
+ narrative: narrativeProp,
2502
+ size = "default",
2503
+ unstyled = false,
2504
+ className,
2505
+ style
2506
+ }) {
2507
+ const fs = fontSize[size];
2508
+ const pad = padding[size];
2509
+ const narrative = useMemo8(() => {
2510
+ if (narrativeProp && narrativeProp.length > 0) return narrativeProp;
2511
+ const lines = [];
2512
+ for (const snap of snapshots) {
2513
+ const stageLines = (snap.narrative ?? "").split("\n").filter(Boolean);
2514
+ lines.push(...stageLines);
2515
+ }
2516
+ return lines;
2517
+ }, [narrativeProp, snapshots]);
2518
+ const revealedCount = useMemo8(() => {
2519
+ if (snapshots.length === 0 || narrative.length === 0) return narrative.length;
2520
+ const stageBoundaries = [];
2521
+ for (let i = 0; i < narrative.length; i++) {
2522
+ const trimmed = narrative[i].trimStart();
2523
+ if (trimmed.startsWith("Stage ") && !trimmed.match(/^Stage\s+\d+:\s*Step\s/)) {
2524
+ stageBoundaries.push(i);
2525
+ }
2526
+ }
2527
+ if (stageBoundaries.length === 0) {
2528
+ const ratio = (selectedIndex + 1) / snapshots.length;
2529
+ return Math.max(1, Math.ceil(narrative.length * ratio));
2530
+ }
2531
+ const groupsToShow = Math.min(selectedIndex + 1, stageBoundaries.length);
2532
+ const endIdx = groupsToShow < stageBoundaries.length ? stageBoundaries[groupsToShow] : narrative.length;
2533
+ return Math.max(1, endIdx);
2534
+ }, [snapshots.length, selectedIndex, narrative]);
2535
+ const hasStructured = narrativeEntries && narrativeEntries.length > 0;
2536
+ if (unstyled) {
2537
+ return /* @__PURE__ */ jsx13("div", { className, style, "data-fp": "narrative-panel", children: hasStructured ? /* @__PURE__ */ jsx13(StoryNarrative, { entries: narrativeEntries, stageCount: selectedIndex + 1, unstyled: true }) : /* @__PURE__ */ jsx13(NarrativeTrace, { narrative, revealedCount, unstyled: true }) });
2538
+ }
2539
+ return /* @__PURE__ */ jsxs12(
2540
+ "div",
2541
+ {
2542
+ className,
2543
+ style: {
2544
+ overflow: "auto",
2545
+ display: "flex",
2546
+ flexDirection: "column",
2547
+ ...style
2548
+ },
2549
+ "data-fp": "narrative-panel",
2550
+ children: [
2551
+ /* @__PURE__ */ jsx13(
2552
+ "div",
2553
+ {
2554
+ style: {
2555
+ padding: `${pad - 4}px ${pad}px`,
2556
+ fontSize: fs.small,
2557
+ color: theme.textMuted,
2558
+ fontStyle: "italic",
2559
+ borderBottom: `1px solid ${theme.border}`,
2560
+ flexShrink: 0
2561
+ },
2562
+ children: "What happened at each stage, what data flowed, what decisions were made, and why."
2563
+ }
2564
+ ),
2565
+ hasStructured ? /* @__PURE__ */ jsx13(
2566
+ StoryNarrative,
2567
+ {
2568
+ entries: narrativeEntries,
2569
+ stageCount: selectedIndex + 1,
2570
+ size,
2571
+ style: { flex: 1 }
2572
+ }
2573
+ ) : /* @__PURE__ */ jsx13(
2574
+ NarrativeTrace,
2575
+ {
2576
+ narrative,
2577
+ revealedCount,
2578
+ size,
2579
+ style: { flex: 1 }
2580
+ }
2581
+ )
2582
+ ]
2583
+ }
2584
+ );
2585
+ }
2586
+
2338
2587
  // src/components/FlowchartView/SubflowTree.tsx
2339
- import { memo, useState as useState7, useCallback as useCallback5, useMemo as useMemo8 } from "react";
2340
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2588
+ import { memo, useState as useState7, useCallback as useCallback4, useMemo as useMemo9 } from "react";
2589
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2341
2590
  function specToTree(node) {
2591
+ if (!node) return [];
2342
2592
  const entries = [];
2343
2593
  const seen = /* @__PURE__ */ new Set();
2344
2594
  function walk(n) {
2595
+ if (!n) return;
2345
2596
  const id = n.name || n.id || "";
2346
2597
  if (seen.has(id)) return;
2347
2598
  seen.add(id);
@@ -2357,7 +2608,7 @@ function specToTree(node) {
2357
2608
  entries.push(entry);
2358
2609
  if (n.children) {
2359
2610
  for (const child of n.children) {
2360
- walk(child);
2611
+ if (child) walk(child);
2361
2612
  }
2362
2613
  }
2363
2614
  if (n.next) {
@@ -2378,14 +2629,14 @@ var TreeNode = memo(function TreeNode2({
2378
2629
  const hasChildren = entry.children && entry.children.length > 0;
2379
2630
  const isActive = activeStage === entry.name;
2380
2631
  const isDone = doneStages?.has(entry.name);
2381
- const handleClick = useCallback5(() => {
2632
+ const handleClick = useCallback4(() => {
2382
2633
  if (hasChildren) {
2383
2634
  setExpanded((prev) => !prev);
2384
2635
  }
2385
2636
  onNodeSelect?.(entry.name, !!entry.isSubflow);
2386
2637
  }, [hasChildren, onNodeSelect, entry.name, entry.isSubflow]);
2387
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
2388
- /* @__PURE__ */ jsxs11(
2638
+ return /* @__PURE__ */ jsxs13(Fragment3, { children: [
2639
+ /* @__PURE__ */ jsxs13(
2389
2640
  "button",
2390
2641
  {
2391
2642
  onClick: handleClick,
@@ -2416,7 +2667,7 @@ var TreeNode = memo(function TreeNode2({
2416
2667
  }
2417
2668
  },
2418
2669
  children: [
2419
- hasChildren ? /* @__PURE__ */ jsx12(
2670
+ hasChildren ? /* @__PURE__ */ jsx14(
2420
2671
  "span",
2421
2672
  {
2422
2673
  style: {
@@ -2431,8 +2682,8 @@ var TreeNode = memo(function TreeNode2({
2431
2682
  },
2432
2683
  children: "\u25B6"
2433
2684
  }
2434
- ) : /* @__PURE__ */ jsx12("span", { style: { width: 12, flexShrink: 0 } }),
2435
- /* @__PURE__ */ jsx12(
2685
+ ) : /* @__PURE__ */ jsx14("span", { style: { width: 12, flexShrink: 0 } }),
2686
+ /* @__PURE__ */ jsx14(
2436
2687
  "span",
2437
2688
  {
2438
2689
  style: {
@@ -2444,8 +2695,8 @@ var TreeNode = memo(function TreeNode2({
2444
2695
  }
2445
2696
  }
2446
2697
  ),
2447
- /* @__PURE__ */ jsxs11("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
2448
- /* @__PURE__ */ jsxs11(
2698
+ /* @__PURE__ */ jsxs13("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
2699
+ /* @__PURE__ */ jsxs13(
2449
2700
  "span",
2450
2701
  {
2451
2702
  style: {
@@ -2457,11 +2708,11 @@ var TreeNode = memo(function TreeNode2({
2457
2708
  },
2458
2709
  children: [
2459
2710
  entry.name,
2460
- entry.isSubflow && /* @__PURE__ */ jsx12("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
2711
+ entry.isSubflow && /* @__PURE__ */ jsx14("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
2461
2712
  ]
2462
2713
  }
2463
2714
  ),
2464
- entry.description && /* @__PURE__ */ jsx12(
2715
+ entry.description && /* @__PURE__ */ jsx14(
2465
2716
  "span",
2466
2717
  {
2467
2718
  style: {
@@ -2478,7 +2729,7 @@ var TreeNode = memo(function TreeNode2({
2478
2729
  ]
2479
2730
  }
2480
2731
  ),
2481
- hasChildren && expanded && /* @__PURE__ */ jsx12("div", { children: entry.children.map((child, i) => /* @__PURE__ */ jsx12(
2732
+ hasChildren && expanded && /* @__PURE__ */ jsx14("div", { children: entry.children.map((child, i) => /* @__PURE__ */ jsx14(
2482
2733
  TreeNode2,
2483
2734
  {
2484
2735
  entry: child,
@@ -2487,12 +2738,12 @@ var TreeNode = memo(function TreeNode2({
2487
2738
  doneStages,
2488
2739
  onNodeSelect
2489
2740
  },
2490
- `${child.name}-${i}`
2741
+ child.subflowId ?? `${child.name}-${i}`
2491
2742
  )) })
2492
2743
  ] });
2493
2744
  });
2494
2745
  var SectionLabel = memo(function SectionLabel2({ children }) {
2495
- return /* @__PURE__ */ jsx12(
2746
+ return /* @__PURE__ */ jsx14(
2496
2747
  "div",
2497
2748
  {
2498
2749
  style: {
@@ -2516,21 +2767,10 @@ var SubflowTree = memo(function SubflowTree2({
2516
2767
  className,
2517
2768
  style
2518
2769
  }) {
2519
- const tree = useMemo8(() => specToTree(spec), [spec]);
2520
- const mainStages = useMemo8(() => tree.filter((e) => !e.isSubflow), [tree]);
2521
- const subflowStages = useMemo8(() => tree.filter((e) => e.isSubflow), [tree]);
2522
- const renderEntries = (entries) => entries.map((entry, i) => /* @__PURE__ */ jsx12(
2523
- TreeNode,
2524
- {
2525
- entry,
2526
- depth: 0,
2527
- activeStage,
2528
- doneStages,
2529
- onNodeSelect
2530
- },
2531
- `${entry.name}-${i}`
2532
- ));
2533
- return /* @__PURE__ */ jsxs11(
2770
+ const tree = useMemo9(() => specToTree(spec), [spec]);
2771
+ const subflowStages = useMemo9(() => tree.filter((e) => e.isSubflow), [tree]);
2772
+ if (subflowStages.length === 0) return null;
2773
+ return /* @__PURE__ */ jsxs13(
2534
2774
  "div",
2535
2775
  {
2536
2776
  className,
@@ -2548,123 +2788,639 @@ var SubflowTree = memo(function SubflowTree2({
2548
2788
  ...style
2549
2789
  },
2550
2790
  children: [
2551
- !unstyled && /* @__PURE__ */ jsx12(SectionLabel, { children: "Flowchart" }),
2552
- renderEntries(mainStages),
2553
- subflowStages.length > 0 && /* @__PURE__ */ jsxs11(Fragment4, { children: [
2554
- !unstyled && /* @__PURE__ */ jsx12("div", { style: { height: 1, background: theme.border, margin: "8px 12px" } }),
2555
- !unstyled && /* @__PURE__ */ jsx12(SectionLabel, { children: "Subflows" }),
2556
- renderEntries(subflowStages)
2557
- ] })
2791
+ !unstyled && /* @__PURE__ */ jsx14(SectionLabel, { children: "Subflows" }),
2792
+ subflowStages.map((entry, i) => /* @__PURE__ */ jsx14(
2793
+ TreeNode,
2794
+ {
2795
+ entry,
2796
+ depth: 0,
2797
+ activeStage,
2798
+ doneStages,
2799
+ onNodeSelect
2800
+ },
2801
+ entry.subflowId ?? `${entry.name}-${i}`
2802
+ ))
2558
2803
  ]
2559
2804
  }
2560
2805
  );
2561
2806
  });
2562
2807
 
2563
- // src/adapters/fromRuntimeSnapshot.ts
2564
- function toVisualizationSnapshots(runtime) {
2565
- const snapshots = [];
2566
- flattenTree(runtime.executionTree, snapshots, runtime.sharedState, 0, runtime.subflowResults, {});
2567
- return snapshots;
2568
- }
2569
- function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults, cumulativeMemory = {}) {
2570
- const durationMs = typeof node.metrics?.durationMs === "number" ? node.metrics.durationMs : 1;
2571
- const startMs = accumulatedMs;
2572
- const narrative = buildNarrative(node);
2573
- const memory = { ...cumulativeMemory };
2574
- if (node.stageWrites) {
2575
- for (const [key, value] of Object.entries(node.stageWrites)) {
2576
- if (value === void 0) {
2577
- delete memory[key];
2578
- } else {
2579
- memory[key] = value;
2808
+ // src/components/FlowchartView/SubflowBreadcrumb.tsx
2809
+ import { memo as memo2 } from "react";
2810
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2811
+ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
2812
+ breadcrumbs,
2813
+ onNavigate
2814
+ }) {
2815
+ if (breadcrumbs.length <= 1) return null;
2816
+ return /* @__PURE__ */ jsx15(
2817
+ "div",
2818
+ {
2819
+ style: {
2820
+ display: "flex",
2821
+ alignItems: "center",
2822
+ gap: 4,
2823
+ padding: "6px 12px",
2824
+ background: theme.bgSecondary,
2825
+ borderBottom: `1px solid ${theme.border}`,
2826
+ fontSize: 12,
2827
+ fontFamily: theme.fontSans,
2828
+ flexShrink: 0,
2829
+ overflowX: "auto"
2830
+ },
2831
+ children: breadcrumbs.map((crumb, i) => {
2832
+ const isLast = i === breadcrumbs.length - 1;
2833
+ return /* @__PURE__ */ jsxs14("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
2834
+ i > 0 && /* @__PURE__ */ jsx15("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
2835
+ isLast ? /* @__PURE__ */ jsxs14("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
2836
+ /* @__PURE__ */ jsx15(
2837
+ "span",
2838
+ {
2839
+ style: {
2840
+ color: theme.primary,
2841
+ fontWeight: 600
2842
+ },
2843
+ children: crumb.label
2844
+ }
2845
+ ),
2846
+ crumb.description && /* @__PURE__ */ jsxs14(
2847
+ "span",
2848
+ {
2849
+ style: {
2850
+ color: theme.textMuted,
2851
+ fontWeight: 400,
2852
+ fontSize: 11
2853
+ },
2854
+ children: [
2855
+ "\u2014 ",
2856
+ crumb.description
2857
+ ]
2858
+ }
2859
+ )
2860
+ ] }) : /* @__PURE__ */ jsx15(
2861
+ "button",
2862
+ {
2863
+ onClick: () => onNavigate(i),
2864
+ style: {
2865
+ background: "none",
2866
+ border: "none",
2867
+ color: theme.textSecondary,
2868
+ cursor: "pointer",
2869
+ padding: "2px 4px",
2870
+ borderRadius: 4,
2871
+ fontSize: 12,
2872
+ fontFamily: "inherit",
2873
+ fontWeight: 500,
2874
+ transition: "color 0.15s"
2875
+ },
2876
+ onMouseEnter: (e) => {
2877
+ e.currentTarget.style.color = `${theme.primary}`;
2878
+ },
2879
+ onMouseLeave: (e) => {
2880
+ e.currentTarget.style.color = `${theme.textSecondary}`;
2881
+ },
2882
+ children: crumb.label
2883
+ }
2884
+ )
2885
+ ] }, `${crumb.label}-${i}`);
2886
+ })
2887
+ }
2888
+ );
2889
+ });
2890
+
2891
+ // src/components/ExplainableShell/ExplainableShell.tsx
2892
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2893
+ var HLinePill = memo3(function HLinePill2({
2894
+ label,
2895
+ detail,
2896
+ expanded,
2897
+ onClick
2898
+ }) {
2899
+ return /* @__PURE__ */ jsxs15("div", { style: {
2900
+ display: "flex",
2901
+ alignItems: "center",
2902
+ gap: 0,
2903
+ padding: "0"
2904
+ }, children: [
2905
+ /* @__PURE__ */ jsx16("div", { style: { flex: 1, height: 1, background: theme.border } }),
2906
+ /* @__PURE__ */ jsxs15(
2907
+ "button",
2908
+ {
2909
+ onClick,
2910
+ style: {
2911
+ display: "flex",
2912
+ alignItems: "center",
2913
+ gap: 5,
2914
+ padding: "3px 12px",
2915
+ margin: "4px 0",
2916
+ fontSize: 10,
2917
+ fontWeight: 600,
2918
+ fontFamily: "inherit",
2919
+ color: theme.textMuted,
2920
+ background: theme.bgSecondary,
2921
+ border: `1px solid ${theme.border}`,
2922
+ borderRadius: 10,
2923
+ cursor: "pointer",
2924
+ whiteSpace: "nowrap",
2925
+ letterSpacing: "0.04em",
2926
+ textTransform: "uppercase",
2927
+ transition: "color 0.15s ease"
2928
+ },
2929
+ children: [
2930
+ /* @__PURE__ */ jsx16("span", { style: { fontSize: 7 }, children: expanded ? "\u25BC" : "\u25B6" }),
2931
+ label,
2932
+ detail && /* @__PURE__ */ jsx16("span", { style: { fontWeight: 400, opacity: 0.5, fontSize: 9 }, children: detail })
2933
+ ]
2934
+ }
2935
+ ),
2936
+ /* @__PURE__ */ jsx16("div", { style: { flex: 1, height: 1, background: theme.border } })
2937
+ ] });
2938
+ });
2939
+ var VLinePill = memo3(function VLinePill2({
2940
+ label,
2941
+ expanded,
2942
+ side = "right",
2943
+ onClick
2944
+ }) {
2945
+ const arrow = side === "right" ? expanded ? "\u25B6" : "\u25C0" : expanded ? "\u25C0" : "\u25B6";
2946
+ return /* @__PURE__ */ jsxs15("div", { style: {
2947
+ display: "flex",
2948
+ flexDirection: "column",
2949
+ alignItems: "center",
2950
+ gap: 0,
2951
+ padding: "0"
2952
+ }, children: [
2953
+ /* @__PURE__ */ jsx16("div", { style: { flex: 1, width: 1, background: theme.border } }),
2954
+ /* @__PURE__ */ jsxs15(
2955
+ "button",
2956
+ {
2957
+ onClick,
2958
+ style: {
2959
+ display: "flex",
2960
+ alignItems: "center",
2961
+ gap: 4,
2962
+ padding: "10px 4px",
2963
+ margin: "0 3px",
2964
+ fontSize: 10,
2965
+ fontWeight: 600,
2966
+ fontFamily: "inherit",
2967
+ color: theme.textMuted,
2968
+ background: theme.bgSecondary,
2969
+ border: `1px solid ${theme.border}`,
2970
+ borderRadius: 10,
2971
+ cursor: "pointer",
2972
+ whiteSpace: "nowrap",
2973
+ letterSpacing: "0.04em",
2974
+ textTransform: "uppercase",
2975
+ writingMode: "vertical-lr",
2976
+ transition: "color 0.15s ease"
2977
+ },
2978
+ children: [
2979
+ /* @__PURE__ */ jsx16("span", { style: { fontSize: 7, writingMode: "horizontal-tb" }, children: arrow }),
2980
+ label
2981
+ ]
2580
2982
  }
2983
+ ),
2984
+ /* @__PURE__ */ jsx16("div", { style: { flex: 1, width: 1, background: theme.border } })
2985
+ ] });
2986
+ });
2987
+ var RIGHT_PANEL_LABELS = {
2988
+ memory: "Memory",
2989
+ narrative: "Narrative"
2990
+ };
2991
+ var DetailsContent = memo3(function DetailsContent2({
2992
+ snapshots,
2993
+ selectedIndex,
2994
+ narrativeEntries,
2995
+ narrative,
2996
+ size,
2997
+ fillHeight
2998
+ }) {
2999
+ const [rightPanel, setRightPanel] = useState8("memory");
3000
+ return /* @__PURE__ */ jsxs15("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
3001
+ /* @__PURE__ */ jsx16("div", { style: { display: "flex", borderBottom: `1px solid ${theme.border}`, flexShrink: 0 }, children: ["memory", "narrative"].map((panel) => {
3002
+ const active = rightPanel === panel;
3003
+ return /* @__PURE__ */ jsx16(
3004
+ "button",
3005
+ {
3006
+ onClick: () => setRightPanel(panel),
3007
+ style: {
3008
+ flex: 1,
3009
+ padding: "6px 8px",
3010
+ fontSize: 11,
3011
+ fontWeight: active ? 600 : 400,
3012
+ color: active ? theme.primary : theme.textMuted,
3013
+ background: active ? `color-mix(in srgb, ${theme.primary} 8%, transparent)` : "transparent",
3014
+ border: "none",
3015
+ borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
3016
+ cursor: "pointer",
3017
+ textTransform: "uppercase",
3018
+ letterSpacing: "0.06em",
3019
+ fontFamily: "inherit"
3020
+ },
3021
+ children: RIGHT_PANEL_LABELS[panel]
3022
+ },
3023
+ panel
3024
+ );
3025
+ }) }),
3026
+ /* @__PURE__ */ jsxs15("div", { style: { flex: 1, overflow: "auto" }, children: [
3027
+ rightPanel === "memory" && /* @__PURE__ */ jsx16(MemoryPanel, { snapshots, selectedIndex, size, style: fillHeight ? { height: "100%" } : void 0 }),
3028
+ rightPanel === "narrative" && /* @__PURE__ */ jsx16(NarrativePanel, { snapshots, selectedIndex, narrativeEntries, narrative, size, style: fillHeight ? { height: "100%" } : void 0 })
3029
+ ] })
3030
+ ] });
3031
+ });
3032
+ function resolveSubflowLevel(parentSpec, parentSnapshots, subflowNodeName, narrativeEntries) {
3033
+ const specNode = findSubflowSpecNode(parentSpec, subflowNodeName);
3034
+ if (!specNode?.subflowStructure) return null;
3035
+ const parentSnap = parentSnapshots.find(
3036
+ (s) => s.stageName === subflowNodeName || s.stageLabel === subflowNodeName
3037
+ );
3038
+ if (!parentSnap?.subflowResult) return null;
3039
+ const sfNarrative = narrativeEntries ? extractSubflowNarrative(narrativeEntries, subflowNodeName) : void 0;
3040
+ const sfSnapshots = subflowResultToSnapshots(parentSnap.subflowResult, sfNarrative);
3041
+ if (sfSnapshots.length === 0) return null;
3042
+ return {
3043
+ subflowId: specNode.subflowId ?? subflowNodeName,
3044
+ label: specNode.subflowName ?? specNode.name,
3045
+ spec: specNode.subflowStructure,
3046
+ snapshots: sfSnapshots
3047
+ };
3048
+ }
3049
+ function extractSubflowNarrative(entries, subflowName) {
3050
+ const result = [];
3051
+ let inside = false;
3052
+ for (const entry of entries) {
3053
+ if (entry.type === "subflow" && entry.text.includes(subflowName) && entry.text.startsWith("Entering")) {
3054
+ inside = true;
3055
+ continue;
2581
3056
  }
3057
+ if (inside && entry.type === "subflow" && entry.text.includes(subflowName) && entry.text.startsWith("Exiting")) break;
3058
+ if (inside) result.push(entry);
2582
3059
  }
2583
- const stageId = node.name || node.id;
2584
- const sfResult = subflowResults?.[node.subflowId ?? stageId];
2585
- out.push({
2586
- stageName: stageId,
2587
- stageLabel: stageId,
2588
- memory,
2589
- narrative,
2590
- startMs,
2591
- durationMs,
2592
- status: "done",
2593
- ...node.description ? { description: node.description } : void 0,
2594
- ...node.subflowId ? { subflowId: node.subflowId } : void 0,
2595
- ...sfResult ? { subflowResult: sfResult } : void 0
2596
- });
2597
- let nextMs = startMs + durationMs;
2598
- if (node.children && node.children.length > 0) {
2599
- let maxChildEnd = nextMs;
3060
+ return result;
3061
+ }
3062
+ function findSubflowSpecNode(node, name) {
3063
+ if ((node.name === name || node.id === name) && node.isSubflowRoot) return node;
3064
+ if (node.children) {
2600
3065
  for (const child of node.children) {
2601
- const childEnd = flattenTree(child, out, sharedState, nextMs, subflowResults, memory);
2602
- maxChildEnd = Math.max(maxChildEnd, childEnd);
3066
+ const f = findSubflowSpecNode(child, name);
3067
+ if (f) return f;
2603
3068
  }
2604
- nextMs = maxChildEnd;
2605
3069
  }
2606
- if (node.next) {
2607
- nextMs = flattenTree(node.next, out, sharedState, nextMs, subflowResults, memory);
2608
- }
2609
- return nextMs;
3070
+ if (node.next) return findSubflowSpecNode(node.next, name);
3071
+ return null;
2610
3072
  }
2611
- function buildNarrative(node) {
2612
- const parts = [];
2613
- if (node.name) {
2614
- parts.push(`Stage "${node.name}" executed.`);
2615
- }
2616
- if (node.stageWrites && Object.keys(node.stageWrites).length > 0) {
2617
- const keys = Object.keys(node.stageWrites);
2618
- parts.push(`Wrote ${keys.length} key(s): ${keys.join(", ")}.`);
2619
- }
2620
- if (node.errors && Object.keys(node.errors).length > 0) {
2621
- parts.push(`Errors: ${JSON.stringify(node.errors)}`);
2622
- }
2623
- if (node.isFork) {
2624
- parts.push(
2625
- `Forked into ${node.children?.length ?? 0} parallel branch(es).`
2626
- );
2627
- }
2628
- return parts.join(" ") || `Stage ${node.id} completed.`;
3073
+ function hasSubflowNodes(node) {
3074
+ if (!node) return false;
3075
+ if (node.isSubflowRoot) return true;
3076
+ if (node.children?.some((c) => c && hasSubflowNodes(c))) return true;
3077
+ if (node.next && hasSubflowNodes(node.next)) return true;
3078
+ return false;
2629
3079
  }
2630
- function createSnapshots(stages) {
2631
- let accMs = 0;
2632
- return stages.map((s) => {
2633
- const duration = s.durationMs ?? 1;
2634
- const snap = {
2635
- stageName: s.name,
2636
- stageLabel: s.label ?? s.name,
2637
- memory: s.memory ?? {},
2638
- narrative: s.narrative ?? `${s.label ?? s.name} completed.`,
2639
- startMs: accMs,
2640
- durationMs: duration,
2641
- status: "done",
2642
- ...s.description ? { description: s.description } : void 0,
2643
- ...s.subflowId ? { subflowId: s.subflowId } : void 0
2644
- };
2645
- accMs += duration;
2646
- return snap;
2647
- });
3080
+ function ExplainableShell({
3081
+ snapshots,
3082
+ spec,
3083
+ title,
3084
+ resultData,
3085
+ logs = [],
3086
+ narrative,
3087
+ narrativeEntries,
3088
+ tabs = ["result", "explainable"],
3089
+ defaultTab,
3090
+ hideConsole = false,
3091
+ panelLabels,
3092
+ defaultExpanded,
3093
+ renderFlowchart,
3094
+ size = "default",
3095
+ unstyled = false,
3096
+ className,
3097
+ style
3098
+ }) {
3099
+ const leftLabel = panelLabels?.topology ?? "Topology";
3100
+ const rightLabel = panelLabels?.details ?? "Details";
3101
+ const bottomLabel = panelLabels?.timeline ?? "Timeline";
3102
+ const shellRef = useRef6(null);
3103
+ const [isNarrow, setIsNarrow] = useState8(false);
3104
+ useEffect6(() => {
3105
+ const el = shellRef.current;
3106
+ if (!el) return;
3107
+ const ro = new ResizeObserver(([entry]) => {
3108
+ setIsNarrow(entry.contentRect.width < 640);
3109
+ });
3110
+ ro.observe(el);
3111
+ return () => ro.disconnect();
3112
+ }, []);
3113
+ const [activeTab, setActiveTab] = useState8(defaultTab ?? tabs[0]);
3114
+ const [snapshotIdx, setSnapshotIdx] = useState8(0);
3115
+ const [drillDownStack, setDrillDownStack] = useState8([]);
3116
+ const [rightExpanded, setRightExpanded] = useState8(defaultExpanded?.details ?? true);
3117
+ const [leftExpanded, setLeftExpanded] = useState8(defaultExpanded?.topology ?? false);
3118
+ const [timelineExpanded, setTimelineExpanded] = useState8(defaultExpanded?.timeline ?? false);
3119
+ useEffect6(() => {
3120
+ if (isNarrow) {
3121
+ setLeftExpanded(false);
3122
+ setRightExpanded(false);
3123
+ setTimelineExpanded(false);
3124
+ }
3125
+ }, [isNarrow]);
3126
+ const triggerReflow = useCallback5(() => {
3127
+ requestAnimationFrame(() => window.dispatchEvent(new Event("resize")));
3128
+ }, []);
3129
+ const toggleLeft = useCallback5((v2) => {
3130
+ setLeftExpanded(v2);
3131
+ triggerReflow();
3132
+ }, [triggerReflow]);
3133
+ const toggleRight = useCallback5((v2) => {
3134
+ setRightExpanded(v2);
3135
+ triggerReflow();
3136
+ }, [triggerReflow]);
3137
+ const toggleTimeline = useCallback5(() => {
3138
+ setTimelineExpanded((p) => !p);
3139
+ triggerReflow();
3140
+ }, [triggerReflow]);
3141
+ const isInSubflow = drillDownStack.length > 0;
3142
+ const currentLevel = useMemo10(() => {
3143
+ if (drillDownStack.length > 0) {
3144
+ const top = drillDownStack[drillDownStack.length - 1];
3145
+ return { spec: top.spec, snapshots: top.snapshots };
3146
+ }
3147
+ return { spec: spec ?? null, snapshots };
3148
+ }, [drillDownStack, spec, snapshots]);
3149
+ const activeSnapshots = currentLevel.snapshots;
3150
+ const activeSpec = currentLevel.spec;
3151
+ const safeIdx = activeSnapshots.length > 0 ? Math.max(0, Math.min(snapshotIdx, activeSnapshots.length - 1)) : 0;
3152
+ const activeNarrative = useMemo10(() => {
3153
+ if (!isInSubflow) return narrative;
3154
+ const lines = [];
3155
+ for (const snap of activeSnapshots) {
3156
+ const stageLines = (snap.narrative ?? "").split("\n").filter(Boolean);
3157
+ lines.push(...stageLines);
3158
+ }
3159
+ return lines.length > 0 ? lines : void 0;
3160
+ }, [isInSubflow, narrative, activeSnapshots]);
3161
+ const activeNarrativeEntries = isInSubflow ? void 0 : narrativeEntries;
3162
+ const breadcrumbs = useMemo10(() => {
3163
+ const root = { label: title || "Flowchart", spec, description: spec?.description };
3164
+ return [root, ...drillDownStack.map((e) => ({ label: e.label, spec: e.spec, description: void 0 }))];
3165
+ }, [spec, title, drillDownStack]);
3166
+ const showTreeSidebar = useMemo10(() => !!spec && hasSubflowNodes(spec), [spec]);
3167
+ const rootOverlay = useMemo10(() => {
3168
+ if (isInSubflow || !snapshots.length) return { activeStage: void 0, doneStages: void 0 };
3169
+ const doneStages = new Set(snapshots.slice(0, safeIdx).map((s) => s.stageLabel));
3170
+ const activeStage = snapshots[safeIdx]?.stageLabel ?? null;
3171
+ return { activeStage, doneStages };
3172
+ }, [isInSubflow, snapshots, safeIdx]);
3173
+ const handleTabChange = useCallback5((tab) => {
3174
+ setActiveTab(tab);
3175
+ setDrillDownStack([]);
3176
+ setSnapshotIdx(999);
3177
+ }, []);
3178
+ const handleSnapshotChange = useCallback5((idx) => {
3179
+ if (typeof idx === "number") setSnapshotIdx(idx);
3180
+ }, []);
3181
+ const handleDrillDown = useCallback5(
3182
+ (nodeName) => {
3183
+ if (!activeSpec) return;
3184
+ const entry = resolveSubflowLevel(activeSpec, activeSnapshots, nodeName, narrativeEntries);
3185
+ if (entry) {
3186
+ setDrillDownStack((prev) => [...prev, { ...entry, parentSnapshotIdx: snapshotIdx }]);
3187
+ setSnapshotIdx(0);
3188
+ }
3189
+ },
3190
+ [activeSpec, activeSnapshots, narrativeEntries, snapshotIdx]
3191
+ );
3192
+ const handleBreadcrumbNavigate = useCallback5((level) => {
3193
+ setDrillDownStack((prev) => {
3194
+ const popped = level === 0 ? prev[0] : prev[level];
3195
+ if (popped) setSnapshotIdx(popped.parentSnapshotIdx);
3196
+ return level === 0 ? [] : prev.slice(0, level);
3197
+ });
3198
+ }, []);
3199
+ const handleNodeClick = useCallback5(
3200
+ (indexOrId) => {
3201
+ if (typeof indexOrId === "number") {
3202
+ setSnapshotIdx(indexOrId);
3203
+ return;
3204
+ }
3205
+ if (activeSpec) {
3206
+ const sfNode = findSubflowSpecNode(activeSpec, indexOrId);
3207
+ if (sfNode?.subflowStructure) {
3208
+ handleDrillDown(indexOrId);
3209
+ return;
3210
+ }
3211
+ }
3212
+ const idx = activeSnapshots.findIndex((s) => s.stageLabel === indexOrId);
3213
+ if (idx >= 0) setSnapshotIdx(idx);
3214
+ },
3215
+ [activeSpec, activeSnapshots, handleDrillDown]
3216
+ );
3217
+ const handleTreeNodeSelect = useCallback5(
3218
+ (name, isSubflow) => {
3219
+ if (isSubflow && spec) {
3220
+ setDrillDownStack([]);
3221
+ const entry = resolveSubflowLevel(spec, snapshots, name, narrativeEntries);
3222
+ if (entry) {
3223
+ setDrillDownStack([{ ...entry, parentSnapshotIdx: snapshotIdx }]);
3224
+ setSnapshotIdx(0);
3225
+ }
3226
+ } else {
3227
+ setDrillDownStack([]);
3228
+ const idx = snapshots.findIndex((s) => s.stageLabel === name);
3229
+ if (idx >= 0) setSnapshotIdx(idx);
3230
+ }
3231
+ },
3232
+ [spec, snapshots, narrativeEntries, snapshotIdx]
3233
+ );
3234
+ const tabLabels = {
3235
+ result: "Result",
3236
+ explainable: "Explainable",
3237
+ "ai-compatible": "AI-Compatible"
3238
+ };
3239
+ if (unstyled) {
3240
+ return /* @__PURE__ */ jsxs15("div", { className, style, "data-fp": "explainable-shell", children: [
3241
+ /* @__PURE__ */ jsx16("div", { "data-fp": "shell-tabs", children: tabs.map((tab) => /* @__PURE__ */ jsx16("button", { "data-fp": "shell-tab", "data-active": tab === activeTab, onClick: () => handleTabChange(tab), children: tabLabels[tab] }, tab)) }),
3242
+ /* @__PURE__ */ jsxs15("div", { "data-fp": "shell-content", "data-tab": activeTab, children: [
3243
+ activeTab === "result" && /* @__PURE__ */ jsx16(ResultPanel, { data: resultData ?? null, logs, hideConsole, unstyled: true }),
3244
+ (activeTab === "explainable" || activeTab === "ai-compatible") && /* @__PURE__ */ jsxs15(Fragment4, { children: [
3245
+ /* @__PURE__ */ jsx16(TimeTravelControls, { snapshots: activeSnapshots, selectedIndex: safeIdx, onIndexChange: handleSnapshotChange, unstyled: true }),
3246
+ isInSubflow && /* @__PURE__ */ jsx16(SubflowBreadcrumb, { breadcrumbs, onNavigate: handleBreadcrumbNavigate }),
3247
+ activeSpec && renderFlowchart?.({ spec: activeSpec, snapshots: activeSnapshots, selectedIndex: safeIdx, onNodeClick: handleNodeClick }),
3248
+ /* @__PURE__ */ jsx16(MemoryPanel, { snapshots: activeSnapshots, selectedIndex: safeIdx, unstyled: true }),
3249
+ /* @__PURE__ */ jsx16(NarrativePanel, { snapshots: activeSnapshots, selectedIndex: safeIdx, narrativeEntries: activeNarrativeEntries, narrative: activeNarrative, unstyled: true }),
3250
+ /* @__PURE__ */ jsx16(GanttTimeline, { snapshots: activeSnapshots, selectedIndex: safeIdx, onSelect: handleSnapshotChange, unstyled: true })
3251
+ ] })
3252
+ ] })
3253
+ ] });
3254
+ }
3255
+ const isVisualizationTab = activeTab === "explainable" || activeTab === "ai-compatible";
3256
+ return /* @__PURE__ */ jsxs15(
3257
+ "div",
3258
+ {
3259
+ ref: shellRef,
3260
+ className,
3261
+ style: {
3262
+ height: "100%",
3263
+ display: "flex",
3264
+ flexDirection: "column",
3265
+ overflow: "hidden",
3266
+ background: theme.bgPrimary,
3267
+ color: theme.textPrimary,
3268
+ fontFamily: theme.fontSans,
3269
+ fontSize: 12,
3270
+ ...style
3271
+ },
3272
+ "data-fp": "explainable-shell",
3273
+ children: [
3274
+ tabs.length > 1 && /* @__PURE__ */ jsx16("div", { style: {
3275
+ display: "flex",
3276
+ borderBottom: `1px solid ${theme.border}`,
3277
+ background: theme.bgSecondary,
3278
+ flexShrink: 0
3279
+ }, children: tabs.map((tab) => {
3280
+ const active = tab === activeTab;
3281
+ return /* @__PURE__ */ jsx16(
3282
+ "button",
3283
+ {
3284
+ onClick: () => handleTabChange(tab),
3285
+ style: {
3286
+ padding: "6px 14px",
3287
+ fontSize: 11,
3288
+ fontWeight: active ? 700 : 500,
3289
+ textTransform: "uppercase",
3290
+ letterSpacing: "0.08em",
3291
+ color: active ? theme.primary : theme.textMuted,
3292
+ background: "transparent",
3293
+ border: "none",
3294
+ borderBottom: active ? `2px solid ${theme.primary}` : "2px solid transparent",
3295
+ cursor: "pointer",
3296
+ fontFamily: "inherit"
3297
+ },
3298
+ children: tabLabels[tab]
3299
+ },
3300
+ tab
3301
+ );
3302
+ }) }),
3303
+ /* @__PURE__ */ jsxs15("div", { style: { flex: 1, overflow: isNarrow ? "auto" : "hidden", display: "flex", flexDirection: "column" }, children: [
3304
+ activeTab === "result" && /* @__PURE__ */ jsx16(ResultPanel, { data: resultData ?? null, logs, hideConsole, size }),
3305
+ isVisualizationTab && /* @__PURE__ */ jsxs15(Fragment4, { children: [
3306
+ /* @__PURE__ */ jsx16(
3307
+ TimeTravelControls,
3308
+ {
3309
+ snapshots: activeSnapshots,
3310
+ selectedIndex: safeIdx,
3311
+ onIndexChange: handleSnapshotChange,
3312
+ size
3313
+ }
3314
+ ),
3315
+ isInSubflow && /* @__PURE__ */ jsx16(SubflowBreadcrumb, { breadcrumbs, onNavigate: handleBreadcrumbNavigate }),
3316
+ isNarrow ? (
3317
+ /* ── Mobile: stacked vertical ── */
3318
+ /* @__PURE__ */ jsxs15(Fragment4, { children: [
3319
+ /* @__PURE__ */ jsx16("div", { style: { height: 350, flexShrink: 0, overflow: "hidden" }, children: renderFlowchart && activeSpec && renderFlowchart({
3320
+ spec: activeSpec,
3321
+ snapshots: activeSnapshots,
3322
+ selectedIndex: safeIdx,
3323
+ onNodeClick: handleNodeClick
3324
+ }) }),
3325
+ showTreeSidebar && /* @__PURE__ */ jsxs15(Fragment4, { children: [
3326
+ /* @__PURE__ */ jsx16(HLinePill, { label: leftLabel, expanded: leftExpanded, onClick: () => toggleLeft(!leftExpanded) }),
3327
+ leftExpanded && /* @__PURE__ */ jsx16("div", { style: { maxHeight: 180, overflow: "auto", flexShrink: 0 }, children: /* @__PURE__ */ jsx16(
3328
+ SubflowTree,
3329
+ {
3330
+ spec,
3331
+ activeStage: rootOverlay.activeStage,
3332
+ doneStages: rootOverlay.doneStages,
3333
+ onNodeSelect: handleTreeNodeSelect
3334
+ }
3335
+ ) })
3336
+ ] }),
3337
+ /* @__PURE__ */ jsx16(HLinePill, { label: rightLabel, expanded: rightExpanded, onClick: () => toggleRight(!rightExpanded) }),
3338
+ rightExpanded && /* @__PURE__ */ jsx16("div", { style: { maxHeight: 250, flexShrink: 0, display: "flex", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ jsx16(
3339
+ DetailsContent,
3340
+ {
3341
+ snapshots: activeSnapshots,
3342
+ selectedIndex: safeIdx,
3343
+ narrativeEntries: activeNarrativeEntries,
3344
+ narrative: activeNarrative,
3345
+ size
3346
+ }
3347
+ ) }),
3348
+ /* @__PURE__ */ jsx16(HLinePill, { label: bottomLabel, detail: `${activeSnapshots.length} stages`, expanded: timelineExpanded, onClick: toggleTimeline }),
3349
+ timelineExpanded && /* @__PURE__ */ jsx16("div", { style: { flexShrink: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx16(GanttTimeline, { snapshots: activeSnapshots, selectedIndex: safeIdx, onSelect: handleSnapshotChange, size }) })
3350
+ ] })
3351
+ ) : (
3352
+ /* ── Desktop: side-by-side ── */
3353
+ /* @__PURE__ */ jsxs15(Fragment4, { children: [
3354
+ /* @__PURE__ */ jsxs15("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
3355
+ showTreeSidebar && (leftExpanded ? /* @__PURE__ */ jsxs15("div", { style: { width: 220, flexShrink: 0, display: "flex", flexDirection: "row", overflow: "hidden" }, children: [
3356
+ /* @__PURE__ */ jsx16("div", { style: { flex: 1, overflow: "auto" }, children: /* @__PURE__ */ jsx16(
3357
+ SubflowTree,
3358
+ {
3359
+ spec,
3360
+ activeStage: rootOverlay.activeStage,
3361
+ doneStages: rootOverlay.doneStages,
3362
+ onNodeSelect: handleTreeNodeSelect
3363
+ }
3364
+ ) }),
3365
+ /* @__PURE__ */ jsx16(VLinePill, { label: leftLabel, expanded: true, side: "left", onClick: () => toggleLeft(false) })
3366
+ ] }) : /* @__PURE__ */ jsx16(VLinePill, { label: leftLabel, expanded: false, side: "left", onClick: () => toggleLeft(true) })),
3367
+ /* @__PURE__ */ jsx16("div", { style: { flex: 1, overflow: "hidden", minWidth: 0 }, children: renderFlowchart && activeSpec && renderFlowchart({
3368
+ spec: activeSpec,
3369
+ snapshots: activeSnapshots,
3370
+ selectedIndex: safeIdx,
3371
+ onNodeClick: handleNodeClick
3372
+ }) }),
3373
+ rightExpanded ? /* @__PURE__ */ jsxs15("div", { style: { width: "38%", minWidth: 300, maxWidth: 500, display: "flex", flexDirection: "row", overflow: "hidden" }, children: [
3374
+ /* @__PURE__ */ jsx16(VLinePill, { label: rightLabel, expanded: true, onClick: () => toggleRight(false) }),
3375
+ /* @__PURE__ */ jsx16(
3376
+ DetailsContent,
3377
+ {
3378
+ snapshots: activeSnapshots,
3379
+ selectedIndex: safeIdx,
3380
+ narrativeEntries: activeNarrativeEntries,
3381
+ narrative: activeNarrative,
3382
+ size,
3383
+ fillHeight: true
3384
+ }
3385
+ )
3386
+ ] }) : /* @__PURE__ */ jsx16(VLinePill, { label: rightLabel, expanded: false, onClick: () => toggleRight(true) })
3387
+ ] }),
3388
+ /* @__PURE__ */ jsx16(HLinePill, { label: bottomLabel, detail: `${activeSnapshots.length} stages`, expanded: timelineExpanded, onClick: toggleTimeline }),
3389
+ timelineExpanded && /* @__PURE__ */ jsx16("div", { style: { flexShrink: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx16(GanttTimeline, { snapshots: activeSnapshots, selectedIndex: safeIdx, onSelect: handleSnapshotChange, size }) })
3390
+ ] })
3391
+ )
3392
+ ] })
3393
+ ] })
3394
+ ]
3395
+ }
3396
+ );
2648
3397
  }
2649
3398
  export {
2650
3399
  ExplainableShell,
2651
3400
  FootprintTheme,
2652
3401
  GanttTimeline,
2653
3402
  MemoryInspector,
3403
+ MemoryPanel,
2654
3404
  NarrativeLog,
3405
+ NarrativePanel,
2655
3406
  NarrativeTrace,
2656
3407
  ResultPanel,
2657
3408
  ScopeDiff,
2658
3409
  SnapshotPanel,
2659
3410
  StageDetailPanel,
3411
+ StoryNarrative,
2660
3412
  SubflowTree,
2661
3413
  TimeTravelControls,
2662
3414
  coolDark,
3415
+ coolLight,
2663
3416
  createSnapshots,
2664
3417
  defaultTokens,
3418
+ rawDefaults,
3419
+ subflowResultToSnapshots,
2665
3420
  themePresets,
2666
3421
  toVisualizationSnapshots,
2667
3422
  tokensToCSSVars,
3423
+ useDarkModeTokens,
2668
3424
  useFootprintTheme,
2669
3425
  warmDark,
2670
3426
  warmLight