footprint-explainable-ui 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -7,6 +7,8 @@ interface StageSnapshot {
7
7
  stageName: string;
8
8
  /** Human-readable label */
9
9
  stageLabel: string;
10
+ /** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex. Key for recorder Map lookup. */
11
+ runtimeStageId?: string;
10
12
  /** Accumulated memory state after this stage ran */
11
13
  memory: Record<string, unknown>;
12
14
  /** Narrative text describing what happened */
@@ -26,15 +28,24 @@ interface StageSnapshot {
26
28
  }
27
29
  /** Structured narrative entry — preserves type info for semantic rendering. */
28
30
  interface NarrativeEntry {
29
- type: 'stage' | 'step' | 'condition' | 'fork' | 'selector' | 'subflow' | 'loop' | 'break' | 'error';
31
+ type: 'stage' | 'step' | 'condition' | 'fork' | 'selector' | 'subflow' | 'loop' | 'break' | 'error' | 'pause' | 'resume';
30
32
  text: string;
31
33
  depth: number;
32
34
  stageName?: string;
33
35
  /** Stable stage identifier (matches spec node id). Primary key for UI sync. */
34
36
  stageId?: string;
37
+ /** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex.
38
+ * Used for exact time-travel sync (preferred over stageId for progressive reveal). */
39
+ runtimeStageId?: string;
35
40
  /** Subflow ID when this entry was generated inside a subflow. */
36
41
  subflowId?: string;
42
+ /** Direction for subflow entries: 'entry' when entering, 'exit' when leaving. */
43
+ direction?: 'entry' | 'exit';
37
44
  stepNumber?: number;
45
+ /** Scope key that was read or written. Only present on 'step' entries. */
46
+ key?: string;
47
+ /** Raw value from the scope event. Only present on 'step' entries. */
48
+ rawValue?: unknown;
38
49
  }
39
50
  /** Component size variants */
40
51
  type Size = "compact" | "default" | "detailed";
@@ -373,6 +384,9 @@ interface RecorderView {
373
384
  id: string;
374
385
  /** Display label on the tab */
375
386
  name: string;
387
+ /** Short description shown as tooltip and header for auto-detected views.
388
+ * e.g., "Per-step timing and I/O counts (KeyedRecorder)" */
389
+ description?: string;
376
390
  /**
377
391
  * Render function — receives the current snapshot index and all snapshots.
378
392
  * Return a React node to display in the details panel.
@@ -563,4 +577,63 @@ declare function createSnapshots(stages: Array<{
563
577
  subflowId?: string;
564
578
  }>): StageSnapshot[];
565
579
 
566
- export { type NarrativeEntry as AdapterNarrativeEntry, type BaseComponentProps, type DarkModeTokensOptions, type DefaultExpanded, type DiffEntry, ExplainableShell, type ExplainableShellProps, FootprintTheme, GanttTimeline, type GanttTimelineProps, type MemoryChange, MemoryInspector, type MemoryInspectorProps, MemoryPanel, type MemoryPanelProps, type NarrativeEntry, NarrativeLog, type NarrativeLogProps, NarrativePanel, type NarrativePanelProps, NarrativeTrace, type NarrativeTraceProps, type PanelLabels, type RecorderView, ResultPanel, type ResultPanelProps, type RuntimeSnapshotInput, ScopeDiff, type ScopeDiffProps, type ShellTab, type Size, SnapshotPanel, type SnapshotPanelProps, type StageDetailMode, StageDetailPanel, type StageDetailPanelProps, type StageSnapshot, StoryNarrative, type StoryNarrativeProps, SubflowTree, type SubflowTreeEntry, type SubflowTreeProps, type ThemePresetName, type ThemeTokens, TimeTravelControls, type TimeTravelControlsProps, coolDark, coolLight, createSnapshots, defaultTokens, rawDefaults, subflowResultToSnapshots, themePresets, toVisualizationSnapshots, tokensToCSSVars, useDarkModeTokens, useFootprintTheme, warmDark, warmLight };
580
+ /**
581
+ * Narrative sync utilities — shared logic for mapping timeline position
582
+ * to narrative entries. Used by NarrativePanel and available to consumers
583
+ * building custom visualization shells.
584
+ */
585
+
586
+ /**
587
+ * Range index: runtimeStageId → half-open range [firstIdx, endIdx) in entries array.
588
+ *
589
+ * This is the same shape as `SequenceRecorder.getEntryRanges()` in footprintjs.
590
+ * When you have recorder access, pass `recorder.getEntryRanges()` directly.
591
+ * When you only have the flat array, use `buildEntryRangeIndex()` to build it.
592
+ */
593
+ type EntryRangeIndex = ReadonlyMap<string, {
594
+ readonly firstIdx: number;
595
+ readonly endIdx: number;
596
+ }>;
597
+ /**
598
+ * Build a range index from a flat entries array for O(1) per-step lookups.
599
+ * Equivalent to `SequenceRecorder.getEntryRanges()` but works on detached arrays.
600
+ *
601
+ * Call once when narrativeEntries changes, then pass to `computeRevealedEntryCount`.
602
+ *
603
+ * @param entries — structured entries (from CombinedNarrativeRecorder.getEntries() or getNarrativeEntries())
604
+ * @returns range index for fast slider sync
605
+ */
606
+ declare function buildEntryRangeIndex(entries: Pick<NarrativeEntry, "runtimeStageId">[]): EntryRangeIndex;
607
+ /**
608
+ * Compute how many narrative entries to reveal at a given slider position.
609
+ *
610
+ * **With range index (preferred):** O(selectedIndex) — one Map lookup per snapshot.
611
+ * **Without index (convenience):** O(entries) forward scan.
612
+ *
613
+ * The range index can come from:
614
+ * - `SequenceRecorder.getEntryRanges()` (when you have recorder access)
615
+ * - `buildEntryRangeIndex(entries)` (when you only have the flat array)
616
+ *
617
+ * @param narrativeEntries — structured entries from CombinedNarrativeRecorder
618
+ * @param snapshots — execution timeline (from adapter)
619
+ * @param selectedIndex — current slider position (0-based)
620
+ * @param rangeIndex — optional precomputed range index for O(1) lookups
621
+ * @returns number of entries to reveal (0 to narrativeEntries.length)
622
+ */
623
+ declare function computeRevealedEntryCount(narrativeEntries: NarrativeEntry[], snapshots: Pick<StageSnapshot, "runtimeStageId">[], selectedIndex: number, rangeIndex?: EntryRangeIndex): number;
624
+ /**
625
+ * Extract narrative entries belonging to a specific subflow.
626
+ *
627
+ * Three-tier matching (most reliable first):
628
+ * 1. `stageName` prefix match (e.g., entries with `stageName` starting with `"sf-pay/"`)
629
+ * 2. `subflowId` field match
630
+ * 3. `direction` field on subflow entry/exit markers (renderer-agnostic)
631
+ *
632
+ * @param entries — all narrative entries from the execution
633
+ * @param subflowId — subflow identifier to extract
634
+ * @param subflowName — optional display name for fallback matching
635
+ * @returns entries belonging to the subflow
636
+ */
637
+ declare function extractSubflowNarrative(entries: NarrativeEntry[], subflowId: string, subflowName?: string): NarrativeEntry[];
638
+
639
+ export { type NarrativeEntry as AdapterNarrativeEntry, type BaseComponentProps, type DarkModeTokensOptions, type DefaultExpanded, type DiffEntry, type EntryRangeIndex, ExplainableShell, type ExplainableShellProps, FootprintTheme, GanttTimeline, type GanttTimelineProps, type MemoryChange, MemoryInspector, type MemoryInspectorProps, MemoryPanel, type MemoryPanelProps, type NarrativeEntry, NarrativeLog, type NarrativeLogProps, NarrativePanel, type NarrativePanelProps, NarrativeTrace, type NarrativeTraceProps, type PanelLabels, type RecorderView, ResultPanel, type ResultPanelProps, type RuntimeSnapshotInput, ScopeDiff, type ScopeDiffProps, type ShellTab, type Size, SnapshotPanel, type SnapshotPanelProps, type StageDetailMode, StageDetailPanel, type StageDetailPanelProps, type StageSnapshot, StoryNarrative, type StoryNarrativeProps, SubflowTree, type SubflowTreeEntry, type SubflowTreeProps, type ThemePresetName, type ThemeTokens, TimeTravelControls, type TimeTravelControlsProps, buildEntryRangeIndex, computeRevealedEntryCount, coolDark, coolLight, createSnapshots, defaultTokens, extractSubflowNarrative, rawDefaults, subflowResultToSnapshots, themePresets, toVisualizationSnapshots, tokensToCSSVars, useDarkModeTokens, useFootprintTheme, warmDark, warmLight };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ interface StageSnapshot {
7
7
  stageName: string;
8
8
  /** Human-readable label */
9
9
  stageLabel: string;
10
+ /** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex. Key for recorder Map lookup. */
11
+ runtimeStageId?: string;
10
12
  /** Accumulated memory state after this stage ran */
11
13
  memory: Record<string, unknown>;
12
14
  /** Narrative text describing what happened */
@@ -26,15 +28,24 @@ interface StageSnapshot {
26
28
  }
27
29
  /** Structured narrative entry — preserves type info for semantic rendering. */
28
30
  interface NarrativeEntry {
29
- type: 'stage' | 'step' | 'condition' | 'fork' | 'selector' | 'subflow' | 'loop' | 'break' | 'error';
31
+ type: 'stage' | 'step' | 'condition' | 'fork' | 'selector' | 'subflow' | 'loop' | 'break' | 'error' | 'pause' | 'resume';
30
32
  text: string;
31
33
  depth: number;
32
34
  stageName?: string;
33
35
  /** Stable stage identifier (matches spec node id). Primary key for UI sync. */
34
36
  stageId?: string;
37
+ /** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex.
38
+ * Used for exact time-travel sync (preferred over stageId for progressive reveal). */
39
+ runtimeStageId?: string;
35
40
  /** Subflow ID when this entry was generated inside a subflow. */
36
41
  subflowId?: string;
42
+ /** Direction for subflow entries: 'entry' when entering, 'exit' when leaving. */
43
+ direction?: 'entry' | 'exit';
37
44
  stepNumber?: number;
45
+ /** Scope key that was read or written. Only present on 'step' entries. */
46
+ key?: string;
47
+ /** Raw value from the scope event. Only present on 'step' entries. */
48
+ rawValue?: unknown;
38
49
  }
39
50
  /** Component size variants */
40
51
  type Size = "compact" | "default" | "detailed";
@@ -373,6 +384,9 @@ interface RecorderView {
373
384
  id: string;
374
385
  /** Display label on the tab */
375
386
  name: string;
387
+ /** Short description shown as tooltip and header for auto-detected views.
388
+ * e.g., "Per-step timing and I/O counts (KeyedRecorder)" */
389
+ description?: string;
376
390
  /**
377
391
  * Render function — receives the current snapshot index and all snapshots.
378
392
  * Return a React node to display in the details panel.
@@ -563,4 +577,63 @@ declare function createSnapshots(stages: Array<{
563
577
  subflowId?: string;
564
578
  }>): StageSnapshot[];
565
579
 
566
- export { type NarrativeEntry as AdapterNarrativeEntry, type BaseComponentProps, type DarkModeTokensOptions, type DefaultExpanded, type DiffEntry, ExplainableShell, type ExplainableShellProps, FootprintTheme, GanttTimeline, type GanttTimelineProps, type MemoryChange, MemoryInspector, type MemoryInspectorProps, MemoryPanel, type MemoryPanelProps, type NarrativeEntry, NarrativeLog, type NarrativeLogProps, NarrativePanel, type NarrativePanelProps, NarrativeTrace, type NarrativeTraceProps, type PanelLabels, type RecorderView, ResultPanel, type ResultPanelProps, type RuntimeSnapshotInput, ScopeDiff, type ScopeDiffProps, type ShellTab, type Size, SnapshotPanel, type SnapshotPanelProps, type StageDetailMode, StageDetailPanel, type StageDetailPanelProps, type StageSnapshot, StoryNarrative, type StoryNarrativeProps, SubflowTree, type SubflowTreeEntry, type SubflowTreeProps, type ThemePresetName, type ThemeTokens, TimeTravelControls, type TimeTravelControlsProps, coolDark, coolLight, createSnapshots, defaultTokens, rawDefaults, subflowResultToSnapshots, themePresets, toVisualizationSnapshots, tokensToCSSVars, useDarkModeTokens, useFootprintTheme, warmDark, warmLight };
580
+ /**
581
+ * Narrative sync utilities — shared logic for mapping timeline position
582
+ * to narrative entries. Used by NarrativePanel and available to consumers
583
+ * building custom visualization shells.
584
+ */
585
+
586
+ /**
587
+ * Range index: runtimeStageId → half-open range [firstIdx, endIdx) in entries array.
588
+ *
589
+ * This is the same shape as `SequenceRecorder.getEntryRanges()` in footprintjs.
590
+ * When you have recorder access, pass `recorder.getEntryRanges()` directly.
591
+ * When you only have the flat array, use `buildEntryRangeIndex()` to build it.
592
+ */
593
+ type EntryRangeIndex = ReadonlyMap<string, {
594
+ readonly firstIdx: number;
595
+ readonly endIdx: number;
596
+ }>;
597
+ /**
598
+ * Build a range index from a flat entries array for O(1) per-step lookups.
599
+ * Equivalent to `SequenceRecorder.getEntryRanges()` but works on detached arrays.
600
+ *
601
+ * Call once when narrativeEntries changes, then pass to `computeRevealedEntryCount`.
602
+ *
603
+ * @param entries — structured entries (from CombinedNarrativeRecorder.getEntries() or getNarrativeEntries())
604
+ * @returns range index for fast slider sync
605
+ */
606
+ declare function buildEntryRangeIndex(entries: Pick<NarrativeEntry, "runtimeStageId">[]): EntryRangeIndex;
607
+ /**
608
+ * Compute how many narrative entries to reveal at a given slider position.
609
+ *
610
+ * **With range index (preferred):** O(selectedIndex) — one Map lookup per snapshot.
611
+ * **Without index (convenience):** O(entries) forward scan.
612
+ *
613
+ * The range index can come from:
614
+ * - `SequenceRecorder.getEntryRanges()` (when you have recorder access)
615
+ * - `buildEntryRangeIndex(entries)` (when you only have the flat array)
616
+ *
617
+ * @param narrativeEntries — structured entries from CombinedNarrativeRecorder
618
+ * @param snapshots — execution timeline (from adapter)
619
+ * @param selectedIndex — current slider position (0-based)
620
+ * @param rangeIndex — optional precomputed range index for O(1) lookups
621
+ * @returns number of entries to reveal (0 to narrativeEntries.length)
622
+ */
623
+ declare function computeRevealedEntryCount(narrativeEntries: NarrativeEntry[], snapshots: Pick<StageSnapshot, "runtimeStageId">[], selectedIndex: number, rangeIndex?: EntryRangeIndex): number;
624
+ /**
625
+ * Extract narrative entries belonging to a specific subflow.
626
+ *
627
+ * Three-tier matching (most reliable first):
628
+ * 1. `stageName` prefix match (e.g., entries with `stageName` starting with `"sf-pay/"`)
629
+ * 2. `subflowId` field match
630
+ * 3. `direction` field on subflow entry/exit markers (renderer-agnostic)
631
+ *
632
+ * @param entries — all narrative entries from the execution
633
+ * @param subflowId — subflow identifier to extract
634
+ * @param subflowName — optional display name for fallback matching
635
+ * @returns entries belonging to the subflow
636
+ */
637
+ declare function extractSubflowNarrative(entries: NarrativeEntry[], subflowId: string, subflowName?: string): NarrativeEntry[];
638
+
639
+ export { type NarrativeEntry as AdapterNarrativeEntry, type BaseComponentProps, type DarkModeTokensOptions, type DefaultExpanded, type DiffEntry, type EntryRangeIndex, ExplainableShell, type ExplainableShellProps, FootprintTheme, GanttTimeline, type GanttTimelineProps, type MemoryChange, MemoryInspector, type MemoryInspectorProps, MemoryPanel, type MemoryPanelProps, type NarrativeEntry, NarrativeLog, type NarrativeLogProps, NarrativePanel, type NarrativePanelProps, NarrativeTrace, type NarrativeTraceProps, type PanelLabels, type RecorderView, ResultPanel, type ResultPanelProps, type RuntimeSnapshotInput, ScopeDiff, type ScopeDiffProps, type ShellTab, type Size, SnapshotPanel, type SnapshotPanelProps, type StageDetailMode, StageDetailPanel, type StageDetailPanelProps, type StageSnapshot, StoryNarrative, type StoryNarrativeProps, SubflowTree, type SubflowTreeEntry, type SubflowTreeProps, type ThemePresetName, type ThemeTokens, TimeTravelControls, type TimeTravelControlsProps, buildEntryRangeIndex, computeRevealedEntryCount, coolDark, coolLight, createSnapshots, defaultTokens, extractSubflowNarrative, rawDefaults, subflowResultToSnapshots, themePresets, toVisualizationSnapshots, tokensToCSSVars, useDarkModeTokens, useFootprintTheme, warmDark, warmLight };
package/dist/index.js CHANGED
@@ -2203,6 +2203,81 @@ function TimeTravelControls({
2203
2203
  // src/components/ExplainableShell/ExplainableShell.tsx
2204
2204
  import { memo as memo4, useState as useState9, useCallback as useCallback7, useMemo as useMemo11, useRef as useRef7, useEffect as useEffect8 } from "react";
2205
2205
 
2206
+ // src/utils/narrativeSync.ts
2207
+ function buildEntryRangeIndex(entries) {
2208
+ const ranges = /* @__PURE__ */ new Map();
2209
+ let lastId;
2210
+ for (let i = 0; i < entries.length; i++) {
2211
+ const id = entries[i].runtimeStageId;
2212
+ if (id) {
2213
+ const existing = ranges.get(id);
2214
+ if (!existing) {
2215
+ ranges.set(id, { firstIdx: i, endIdx: i + 1 });
2216
+ } else {
2217
+ existing.endIdx = i + 1;
2218
+ }
2219
+ lastId = id;
2220
+ } else if (lastId) {
2221
+ ranges.get(lastId).endIdx = i + 1;
2222
+ }
2223
+ }
2224
+ return ranges;
2225
+ }
2226
+ function computeRevealedEntryCount(narrativeEntries, snapshots, selectedIndex, rangeIndex) {
2227
+ if (!narrativeEntries.length || snapshots.length === 0) return 0;
2228
+ if (rangeIndex) {
2229
+ let maxEndIdx = 0;
2230
+ for (let si = 0; si <= selectedIndex && si < snapshots.length; si++) {
2231
+ const targetId = snapshots[si].runtimeStageId;
2232
+ if (!targetId) continue;
2233
+ const range = rangeIndex.get(targetId);
2234
+ if (range && range.endIdx > maxEndIdx) {
2235
+ maxEndIdx = range.endIdx;
2236
+ }
2237
+ }
2238
+ return maxEndIdx;
2239
+ }
2240
+ let entryIdx = 0;
2241
+ for (let si = 0; si <= selectedIndex && si < snapshots.length; si++) {
2242
+ const targetId = snapshots[si].runtimeStageId;
2243
+ if (!targetId) continue;
2244
+ let found = false;
2245
+ for (let j = entryIdx; j < narrativeEntries.length; j++) {
2246
+ if (narrativeEntries[j].runtimeStageId === targetId) {
2247
+ found = true;
2248
+ entryIdx = j;
2249
+ break;
2250
+ }
2251
+ }
2252
+ if (!found) continue;
2253
+ while (entryIdx < narrativeEntries.length) {
2254
+ const eId = narrativeEntries[entryIdx].runtimeStageId;
2255
+ if (eId && eId !== targetId) break;
2256
+ entryIdx++;
2257
+ }
2258
+ }
2259
+ return entryIdx;
2260
+ }
2261
+ function extractSubflowNarrative(entries, subflowId, subflowName) {
2262
+ const prefix = subflowId + "/";
2263
+ const byPrefix = entries.filter((e) => e.stageName?.startsWith(prefix));
2264
+ if (byPrefix.length > 0) return byPrefix;
2265
+ const byId = entries.filter((e) => e.subflowId === subflowId);
2266
+ if (byId.length > 0) return byId;
2267
+ const result = [];
2268
+ const searchName = subflowName ?? subflowId;
2269
+ let inside = false;
2270
+ for (const entry of entries) {
2271
+ if (entry.type === "subflow" && entry.direction === "entry" && entry.stageName === searchName) {
2272
+ inside = true;
2273
+ continue;
2274
+ }
2275
+ if (inside && entry.type === "subflow" && entry.direction === "exit" && entry.stageName === searchName) break;
2276
+ if (inside) result.push(entry);
2277
+ }
2278
+ return result;
2279
+ }
2280
+
2206
2281
  // src/adapters/fromRuntimeSnapshot.ts
2207
2282
  function toVisualizationSnapshots(runtime, narrativeEntries) {
2208
2283
  const stageNarrativeMap = narrativeEntries?.length ? buildStageNarrativeMap(narrativeEntries) : /* @__PURE__ */ new Map();
@@ -2278,6 +2353,7 @@ function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults,
2278
2353
  out.push({
2279
2354
  stageName: displayName,
2280
2355
  stageLabel: stageId,
2356
+ runtimeStageId: node.runtimeStageId ?? void 0,
2281
2357
  memory,
2282
2358
  narrative,
2283
2359
  startMs,
@@ -2630,37 +2706,14 @@ function NarrativePanel({
2630
2706
  const endIdx = groupsToShow < stageBoundaries.length ? stageBoundaries[groupsToShow] : narrative.length;
2631
2707
  return Math.max(1, endIdx);
2632
2708
  }, [snapshots.length, selectedIndex, narrative]);
2633
- const revealedEntryCount = useMemo8(() => {
2634
- if (!narrativeEntries?.length || snapshots.length === 0) return 0;
2635
- let entryIdx = 0;
2636
- for (let si = 0; si <= selectedIndex && si < snapshots.length; si++) {
2637
- const snap = snapshots[si];
2638
- const keys = /* @__PURE__ */ new Set();
2639
- if (snap.stageLabel) keys.add(snap.stageLabel);
2640
- if (snap.stageName) keys.add(snap.stageName);
2641
- if (snap.subflowId) keys.add(snap.subflowId);
2642
- let found = false;
2643
- for (let j = entryIdx; j < narrativeEntries.length; j++) {
2644
- const e = narrativeEntries[j];
2645
- const eKey = e.stageId ?? e.subflowId ?? e.stageName;
2646
- if (eKey && keys.has(eKey)) {
2647
- found = true;
2648
- entryIdx = j;
2649
- break;
2650
- }
2651
- if (!eKey && !found) {
2652
- }
2653
- }
2654
- if (!found) continue;
2655
- while (entryIdx < narrativeEntries.length) {
2656
- const e = narrativeEntries[entryIdx];
2657
- const eKey = e.stageId ?? e.subflowId ?? e.stageName;
2658
- if (eKey && !keys.has(eKey)) break;
2659
- entryIdx++;
2660
- }
2661
- }
2662
- return entryIdx;
2663
- }, [narrativeEntries, snapshots, selectedIndex]);
2709
+ const rangeIndex = useMemo8(
2710
+ () => narrativeEntries?.length ? buildEntryRangeIndex(narrativeEntries) : void 0,
2711
+ [narrativeEntries]
2712
+ );
2713
+ const revealedEntryCount = useMemo8(
2714
+ () => narrativeEntries?.length ? computeRevealedEntryCount(narrativeEntries, snapshots, selectedIndex, rangeIndex) : 0,
2715
+ [narrativeEntries, snapshots, selectedIndex, rangeIndex]
2716
+ );
2664
2717
  const hasStructured = narrativeEntries && narrativeEntries.length > 0;
2665
2718
  const [copied, setCopied] = useState7(false);
2666
2719
  const buildLLMNarrative = useCallback4(() => {
@@ -2676,7 +2729,7 @@ function NarrativePanel({
2676
2729
  root.push(entry);
2677
2730
  } else {
2678
2731
  if (entry.type === "subflow") {
2679
- const isExit = entry.text.startsWith("Done:") || entry.text.startsWith("Exiting");
2732
+ const isExit = entry.direction === "exit";
2680
2733
  if (!isExit) {
2681
2734
  root.push(entry);
2682
2735
  }
@@ -2696,7 +2749,11 @@ function NarrativePanel({
2696
2749
  if (opts?.inSubflow && e.type === "subflow") continue;
2697
2750
  let text = e.text;
2698
2751
  if (opts?.inSubflow) {
2699
- text = text.replace(new RegExp(`\\[${opts.inSubflow}/`), "[");
2752
+ const prefix = `[${opts.inSubflow}/`;
2753
+ const idx = text.indexOf(prefix);
2754
+ if (idx !== -1) {
2755
+ text = text.slice(0, idx) + "[" + text.slice(idx + prefix.length);
2756
+ }
2700
2757
  }
2701
2758
  const isHeading = e.type === "stage" || e.type === "subflow" || e.type === "fork" || e.type === "selector";
2702
2759
  if (isHeading) {
@@ -4003,6 +4060,12 @@ var DetailsContent = memo4(function DetailsContent2({
4003
4060
  ];
4004
4061
  const allViews = [...builtInViews, ...extraViews ?? []];
4005
4062
  const [activeViewId, setActiveViewId] = useState9(allViews[0]?.id ?? "memory");
4063
+ const viewIds = allViews.map((v2) => v2.id).join(",");
4064
+ useEffect8(() => {
4065
+ if (!allViews.find((v2) => v2.id === activeViewId)) {
4066
+ setActiveViewId(allViews[0]?.id ?? "memory");
4067
+ }
4068
+ }, [viewIds]);
4006
4069
  const activeView = allViews.find((v2) => v2.id === activeViewId) ?? allViews[0];
4007
4070
  return /* @__PURE__ */ jsxs17("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
4008
4071
  /* @__PURE__ */ jsx18("div", { style: { display: "flex", borderBottom: `1px solid ${theme.border}`, flexShrink: 0, overflowX: "auto" }, children: allViews.map((view) => {
@@ -4053,25 +4116,6 @@ function resolveSubflowLevel(parentSpec, parentSnapshots, subflowNodeName, narra
4053
4116
  snapshots: sfSnapshots
4054
4117
  };
4055
4118
  }
4056
- function extractSubflowNarrative(entries, subflowId, subflowName) {
4057
- const prefix = subflowId + "/";
4058
- const byPrefix = entries.filter((e) => e.stageName?.startsWith(prefix));
4059
- if (byPrefix.length > 0) return byPrefix;
4060
- const byId = entries.filter((e) => e.subflowId === subflowId);
4061
- if (byId.length > 0) return byId;
4062
- const result = [];
4063
- const searchName = subflowName ?? subflowId;
4064
- let inside = false;
4065
- for (const entry of entries) {
4066
- if (entry.type === "subflow" && entry.text.includes(searchName) && entry.text.startsWith("Entering")) {
4067
- inside = true;
4068
- continue;
4069
- }
4070
- if (inside && entry.type === "subflow" && entry.text.includes(searchName) && entry.text.startsWith("Exiting")) break;
4071
- if (inside) result.push(entry);
4072
- }
4073
- return result;
4074
- }
4075
4119
  function findSubflowSpecNode(node, name) {
4076
4120
  if ((node.name === name || node.id === name) && node.isSubflowRoot) return node;
4077
4121
  if (node.children) {
@@ -4160,22 +4204,22 @@ function ExplainableShell({
4160
4204
  const recorders = runtimeSnapshot?.recorders;
4161
4205
  if (!recorders?.length) return [];
4162
4206
  const explicitIds = new Set((recorderViews ?? []).map((v2) => v2.id));
4163
- return recorders.filter((r) => !explicitIds.has(r.id)).map((r) => ({ id: r.id, name: r.name, data: r.data }));
4207
+ return recorders.filter((r) => !explicitIds.has(r.id)).map((r) => ({ id: r.id, name: r.name, description: r.description, data: r.data }));
4164
4208
  }, [runtimeSnapshot, recorderViews]);
4165
4209
  const hasNarrative = !!(narrative?.length || narrativeEntries?.length);
4166
4210
  const allTabs = useMemo11(() => {
4167
4211
  const tabs2 = [
4168
- { id: "result", name: "Result" },
4169
- { id: "memory", name: "Memory" }
4212
+ { id: "result", name: "Result", description: "Final output and console logs" },
4213
+ { id: "memory", name: "Memory", description: "Accumulated shared state at each stage" }
4170
4214
  ];
4171
4215
  if (hasNarrative) {
4172
- tabs2.push({ id: "narrative", name: "Narrative" });
4216
+ tabs2.push({ id: "narrative", name: "Narrative", description: "What happened, what data flowed, what decisions were made" });
4173
4217
  }
4174
4218
  for (const v2 of recorderViews ?? []) {
4175
- tabs2.push({ id: v2.id, name: v2.name });
4219
+ tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
4176
4220
  }
4177
4221
  for (const v2 of autoRecorderViews) {
4178
- tabs2.push({ id: v2.id, name: v2.name });
4222
+ tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
4179
4223
  }
4180
4224
  const hideSet = new Set(hideTabsProp ?? []);
4181
4225
  return hideSet.size > 0 ? tabs2.filter((t) => !hideSet.has(t.id)) : tabs2;
@@ -4337,7 +4381,17 @@ function ExplainableShell({
4337
4381
  }
4338
4382
  const autoView = autoRecorderViews.find((v2) => v2.id === activeTab);
4339
4383
  if (autoView) {
4340
- return /* @__PURE__ */ jsx18("div", { style: { padding: 12, fontFamily: theme.fontMono, fontSize: 11, whiteSpace: "pre-wrap", overflow: "auto", height: "100%" }, children: typeof autoView.data === "string" ? autoView.data : JSON.stringify(autoView.data, null, 2) });
4384
+ return /* @__PURE__ */ jsxs17("div", { style: { overflow: "auto", height: "100%", display: "flex", flexDirection: "column" }, children: [
4385
+ autoView.description && /* @__PURE__ */ jsx18("div", { style: {
4386
+ padding: "6px 12px",
4387
+ fontSize: 11,
4388
+ color: theme.textMuted,
4389
+ fontStyle: "italic",
4390
+ borderBottom: `1px solid ${theme.border}`,
4391
+ flexShrink: 0
4392
+ }, children: autoView.description }),
4393
+ /* @__PURE__ */ jsx18("div", { style: { padding: 12, fontFamily: theme.fontMono, fontSize: 11, whiteSpace: "pre-wrap", overflow: "auto", flex: 1 }, children: typeof autoView.data === "string" ? autoView.data : JSON.stringify(autoView.data, null, 2) })
4394
+ ] });
4341
4395
  }
4342
4396
  return null;
4343
4397
  }, [activeTab, resultData, logs, hideConsole, size, activeSnapshots, safeIdx, activeNarrativeEntries, activeNarrative, recorderViews, autoRecorderViews]);
@@ -4354,6 +4408,7 @@ function ExplainableShell({
4354
4408
  "button",
4355
4409
  {
4356
4410
  onClick: () => handleTabChange(tab.id),
4411
+ title: tab.description,
4357
4412
  style: {
4358
4413
  padding: "6px 14px",
4359
4414
  fontSize: 11,
@@ -4487,10 +4542,13 @@ export {
4487
4542
  StoryNarrative,
4488
4543
  SubflowTree,
4489
4544
  TimeTravelControls,
4545
+ buildEntryRangeIndex,
4546
+ computeRevealedEntryCount,
4490
4547
  coolDark,
4491
4548
  coolLight,
4492
4549
  createSnapshots,
4493
4550
  defaultTokens,
4551
+ extractSubflowNarrative,
4494
4552
  rawDefaults,
4495
4553
  subflowResultToSnapshots,
4496
4554
  themePresets,