footprint-explainable-ui 0.25.0 → 0.25.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.d.cts CHANGED
@@ -1,6 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { Node, Edge } from '@xyflow/react';
3
1
  import * as react from 'react';
2
+ import { Node, Edge } from '@xyflow/react';
4
3
 
5
4
  /** Snapshot of a single pipeline stage — the core data shape for all components. */
6
5
  interface StageSnapshot {
@@ -134,7 +133,7 @@ interface FootprintThemeProps {
134
133
  * accessibility tree in some older browser versions. Our wrapper has
135
134
  * no semantic role, so this is fine.
136
135
  */
137
- declare function FootprintTheme({ tokens, children }: FootprintThemeProps): react_jsx_runtime.JSX.Element;
136
+ declare function FootprintTheme({ tokens, children }: FootprintThemeProps): react.JSX.Element;
138
137
 
139
138
  /** Cool dark theme (the library default) */
140
139
  declare const coolDark: ThemeTokens;
@@ -201,7 +200,7 @@ interface MemoryInspectorProps extends BaseComponentProps {
201
200
  * Displays pipeline memory state as formatted JSON.
202
201
  * Supports both static (data prop) and time-travel (snapshots + selectedIndex) modes.
203
202
  */
204
- declare function MemoryInspector({ data, snapshots, selectedIndex, showTypes, highlightNew, size, unstyled, className, style, }: MemoryInspectorProps): react_jsx_runtime.JSX.Element;
203
+ declare function MemoryInspector({ data, snapshots, selectedIndex, showTypes, highlightNew, size, unstyled, className, style, }: MemoryInspectorProps): react.JSX.Element;
205
204
 
206
205
  interface NarrativeLogProps extends BaseComponentProps {
207
206
  /** Snapshots to display narratives from */
@@ -215,7 +214,7 @@ interface NarrativeLogProps extends BaseComponentProps {
215
214
  * Timeline-style execution log showing what happened at each stage.
216
215
  * Supports both full snapshots mode and single-narrative mode.
217
216
  */
218
- declare function NarrativeLog({ snapshots, selectedIndex, narrative, size, unstyled, className, style, }: NarrativeLogProps): react_jsx_runtime.JSX.Element;
217
+ declare function NarrativeLog({ snapshots, selectedIndex, narrative, size, unstyled, className, style, }: NarrativeLogProps): react.JSX.Element;
219
218
 
220
219
  interface NarrativeTraceProps extends BaseComponentProps {
221
220
  /** All narrative lines (full trace) */
@@ -227,7 +226,7 @@ interface NarrativeTraceProps extends BaseComponentProps {
227
226
  /** Called when user clicks a stage header */
228
227
  onStageClick?: (headerIndex: number) => void;
229
228
  }
230
- declare function NarrativeTrace({ narrative, revealedCount, defaultCollapsed, onStageClick, size, unstyled, className, style, }: NarrativeTraceProps): react_jsx_runtime.JSX.Element;
229
+ declare function NarrativeTrace({ narrative, revealedCount, defaultCollapsed, onStageClick, size, unstyled, className, style, }: NarrativeTraceProps): react.JSX.Element;
231
230
 
232
231
  interface GanttTimelineProps extends BaseComponentProps {
233
232
  /** Stage snapshots with timing info */
@@ -244,7 +243,7 @@ interface GanttTimelineProps extends BaseComponentProps {
244
243
  * Collapses to `maxVisibleRows` with expand/collapse toggle.
245
244
  * Auto-scrolls to keep the active stage visible when collapsed.
246
245
  */
247
- declare function GanttTimeline({ snapshots, selectedIndex, onSelect, size, unstyled, className, style, maxVisibleRows, }: GanttTimelineProps): react_jsx_runtime.JSX.Element;
246
+ declare function GanttTimeline({ snapshots, selectedIndex, onSelect, size, unstyled, className, style, maxVisibleRows, }: GanttTimelineProps): react.JSX.Element;
248
247
 
249
248
  interface SnapshotPanelProps extends BaseComponentProps {
250
249
  /** Stage snapshots from pipeline execution */
@@ -260,7 +259,7 @@ interface SnapshotPanelProps extends BaseComponentProps {
260
259
  * All-in-one panel: time-travel scrubber + memory inspector + narrative log + gantt.
261
260
  * Drop this into any page to make a pipeline run inspectable.
262
261
  */
263
- declare function SnapshotPanel({ snapshots, showGantt, showScrubber, title, size, unstyled, className, style, }: SnapshotPanelProps): react_jsx_runtime.JSX.Element;
262
+ declare function SnapshotPanel({ snapshots, showGantt, showScrubber, title, size, unstyled, className, style, }: SnapshotPanelProps): react.JSX.Element;
264
263
 
265
264
  interface DiffEntry {
266
265
  key: string;
@@ -276,7 +275,7 @@ interface ScopeDiffProps extends BaseComponentProps {
276
275
  /** Hide unchanged keys (default: false) */
277
276
  hideUnchanged?: boolean;
278
277
  }
279
- declare function ScopeDiff({ previous, current, hideUnchanged, size, unstyled, className, style, }: ScopeDiffProps): react_jsx_runtime.JSX.Element;
278
+ declare function ScopeDiff({ previous, current, hideUnchanged, size, unstyled, className, style, }: ScopeDiffProps): react.JSX.Element;
280
279
 
281
280
  interface ResultPanelProps extends BaseComponentProps {
282
281
  /** Final pipeline output / shared state */
@@ -286,7 +285,7 @@ interface ResultPanelProps extends BaseComponentProps {
286
285
  /** Hide console section (default: false) */
287
286
  hideConsole?: boolean;
288
287
  }
289
- declare function ResultPanel({ data, logs, hideConsole, size, unstyled, className, style, }: ResultPanelProps): react_jsx_runtime.JSX.Element;
288
+ declare function ResultPanel({ data, logs, hideConsole, size, unstyled, className, style, }: ResultPanelProps): react.JSX.Element;
290
289
 
291
290
  type StageDetailMode = "simple" | "dev";
292
291
  interface MemoryChange {
@@ -309,7 +308,7 @@ interface StageDetailPanelProps extends BaseComponentProps {
309
308
  /** Keys to exclude from memory display (default: engine internals). Pass empty set to show all. */
310
309
  excludeKeys?: Set<string>;
311
310
  }
312
- declare function StageDetailPanel({ snapshots, selectedIndex, mode: controlledMode, showToggle, onModeChange, size, unstyled, className, style, }: StageDetailPanelProps): react_jsx_runtime.JSX.Element;
311
+ declare function StageDetailPanel({ snapshots, selectedIndex, mode: controlledMode, showToggle, onModeChange, size, unstyled, className, style, }: StageDetailPanelProps): react.JSX.Element;
313
312
 
314
313
  interface TimeTravelControlsProps extends BaseComponentProps {
315
314
  /** Stage snapshots */
@@ -321,7 +320,7 @@ interface TimeTravelControlsProps extends BaseComponentProps {
321
320
  /** Enable auto-play with Gantt-proportional timing */
322
321
  autoPlayable?: boolean;
323
322
  }
324
- declare function TimeTravelControls({ snapshots, selectedIndex, onIndexChange, autoPlayable, size, unstyled, className, style, }: TimeTravelControlsProps): react_jsx_runtime.JSX.Element;
323
+ declare function TimeTravelControls({ snapshots, selectedIndex, onIndexChange, autoPlayable, size, unstyled, className, style, }: TimeTravelControlsProps): react.JSX.Element;
325
324
 
326
325
  /**
327
326
  * One entry in the execution timeline. `<TracedFlow>` keys time-travel
@@ -707,7 +706,7 @@ interface ExplainableShellProps extends BaseComponentProps {
707
706
  */
708
707
  showStageId?: boolean;
709
708
  }
710
- declare function ExplainableShell({ snapshots: snapshotsProp, runtimeSnapshot, spec, title, resultData: resultDataProp, logs, narrativeEntries, tabs, defaultTab, hideConsole, hideTabs: hideTabsProp, panelLabels, defaultExpanded, recorderViews, renderFlowchart, showStageId, traceGraph, runtimeOverlay, size, unstyled, className, style, }: ExplainableShellProps): react_jsx_runtime.JSX.Element;
709
+ declare function ExplainableShell({ snapshots: snapshotsProp, runtimeSnapshot, spec, title, resultData: resultDataProp, logs, narrativeEntries, tabs, defaultTab, hideConsole, hideTabs: hideTabsProp, panelLabels, defaultExpanded, recorderViews, renderFlowchart, showStageId, traceGraph, runtimeOverlay, size, unstyled, className, style, }: ExplainableShellProps): react.JSX.Element;
711
710
 
712
711
  /**
713
712
  * TraceViewer — drop-in component that renders an `agentfootprint.exportTrace()`
@@ -803,7 +802,7 @@ interface MemoryPanelProps extends BaseComponentProps {
803
802
  snapshots: StageSnapshot[];
804
803
  selectedIndex: number;
805
804
  }
806
- declare function MemoryPanel({ snapshots, selectedIndex, size, unstyled, className, style, }: MemoryPanelProps): react_jsx_runtime.JSX.Element;
805
+ declare function MemoryPanel({ snapshots, selectedIndex, size, unstyled, className, style, }: MemoryPanelProps): react.JSX.Element;
807
806
 
808
807
  interface NarrativePanelProps extends BaseComponentProps {
809
808
  snapshots: StageSnapshot[];
@@ -827,7 +826,7 @@ interface NarrativePanelProps extends BaseComponentProps {
827
826
  */
828
827
  spec?: any;
829
828
  }
830
- declare function NarrativePanel({ snapshots, selectedIndex, narrativeEntries, runtimeSnapshot, spec, size, unstyled, className, style, }: NarrativePanelProps): react_jsx_runtime.JSX.Element;
829
+ declare function NarrativePanel({ snapshots, selectedIndex, narrativeEntries, runtimeSnapshot, spec, size, unstyled, className, style, }: NarrativePanelProps): react.JSX.Element;
831
830
 
832
831
  interface StoryNarrativeProps extends BaseComponentProps {
833
832
  /** Structured narrative entries from CombinedNarrativeRecorder */
@@ -835,7 +834,7 @@ interface StoryNarrativeProps extends BaseComponentProps {
835
834
  /** Number of entries to reveal (position-based sync from NarrativePanel) */
836
835
  revealedEntryCount: number;
837
836
  }
838
- declare function StoryNarrative({ entries, revealedEntryCount, size, unstyled, className, style: outerStyle, }: StoryNarrativeProps): react_jsx_runtime.JSX.Element;
837
+ declare function StoryNarrative({ entries, revealedEntryCount, size, unstyled, className, style: outerStyle, }: StoryNarrativeProps): react.JSX.Element;
839
838
 
840
839
  interface SubflowTreeEntry {
841
840
  /** Node name / identifier */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { Node, Edge } from '@xyflow/react';
3
1
  import * as react from 'react';
2
+ import { Node, Edge } from '@xyflow/react';
4
3
 
5
4
  /** Snapshot of a single pipeline stage — the core data shape for all components. */
6
5
  interface StageSnapshot {
@@ -134,7 +133,7 @@ interface FootprintThemeProps {
134
133
  * accessibility tree in some older browser versions. Our wrapper has
135
134
  * no semantic role, so this is fine.
136
135
  */
137
- declare function FootprintTheme({ tokens, children }: FootprintThemeProps): react_jsx_runtime.JSX.Element;
136
+ declare function FootprintTheme({ tokens, children }: FootprintThemeProps): react.JSX.Element;
138
137
 
139
138
  /** Cool dark theme (the library default) */
140
139
  declare const coolDark: ThemeTokens;
@@ -201,7 +200,7 @@ interface MemoryInspectorProps extends BaseComponentProps {
201
200
  * Displays pipeline memory state as formatted JSON.
202
201
  * Supports both static (data prop) and time-travel (snapshots + selectedIndex) modes.
203
202
  */
204
- declare function MemoryInspector({ data, snapshots, selectedIndex, showTypes, highlightNew, size, unstyled, className, style, }: MemoryInspectorProps): react_jsx_runtime.JSX.Element;
203
+ declare function MemoryInspector({ data, snapshots, selectedIndex, showTypes, highlightNew, size, unstyled, className, style, }: MemoryInspectorProps): react.JSX.Element;
205
204
 
206
205
  interface NarrativeLogProps extends BaseComponentProps {
207
206
  /** Snapshots to display narratives from */
@@ -215,7 +214,7 @@ interface NarrativeLogProps extends BaseComponentProps {
215
214
  * Timeline-style execution log showing what happened at each stage.
216
215
  * Supports both full snapshots mode and single-narrative mode.
217
216
  */
218
- declare function NarrativeLog({ snapshots, selectedIndex, narrative, size, unstyled, className, style, }: NarrativeLogProps): react_jsx_runtime.JSX.Element;
217
+ declare function NarrativeLog({ snapshots, selectedIndex, narrative, size, unstyled, className, style, }: NarrativeLogProps): react.JSX.Element;
219
218
 
220
219
  interface NarrativeTraceProps extends BaseComponentProps {
221
220
  /** All narrative lines (full trace) */
@@ -227,7 +226,7 @@ interface NarrativeTraceProps extends BaseComponentProps {
227
226
  /** Called when user clicks a stage header */
228
227
  onStageClick?: (headerIndex: number) => void;
229
228
  }
230
- declare function NarrativeTrace({ narrative, revealedCount, defaultCollapsed, onStageClick, size, unstyled, className, style, }: NarrativeTraceProps): react_jsx_runtime.JSX.Element;
229
+ declare function NarrativeTrace({ narrative, revealedCount, defaultCollapsed, onStageClick, size, unstyled, className, style, }: NarrativeTraceProps): react.JSX.Element;
231
230
 
232
231
  interface GanttTimelineProps extends BaseComponentProps {
233
232
  /** Stage snapshots with timing info */
@@ -244,7 +243,7 @@ interface GanttTimelineProps extends BaseComponentProps {
244
243
  * Collapses to `maxVisibleRows` with expand/collapse toggle.
245
244
  * Auto-scrolls to keep the active stage visible when collapsed.
246
245
  */
247
- declare function GanttTimeline({ snapshots, selectedIndex, onSelect, size, unstyled, className, style, maxVisibleRows, }: GanttTimelineProps): react_jsx_runtime.JSX.Element;
246
+ declare function GanttTimeline({ snapshots, selectedIndex, onSelect, size, unstyled, className, style, maxVisibleRows, }: GanttTimelineProps): react.JSX.Element;
248
247
 
249
248
  interface SnapshotPanelProps extends BaseComponentProps {
250
249
  /** Stage snapshots from pipeline execution */
@@ -260,7 +259,7 @@ interface SnapshotPanelProps extends BaseComponentProps {
260
259
  * All-in-one panel: time-travel scrubber + memory inspector + narrative log + gantt.
261
260
  * Drop this into any page to make a pipeline run inspectable.
262
261
  */
263
- declare function SnapshotPanel({ snapshots, showGantt, showScrubber, title, size, unstyled, className, style, }: SnapshotPanelProps): react_jsx_runtime.JSX.Element;
262
+ declare function SnapshotPanel({ snapshots, showGantt, showScrubber, title, size, unstyled, className, style, }: SnapshotPanelProps): react.JSX.Element;
264
263
 
265
264
  interface DiffEntry {
266
265
  key: string;
@@ -276,7 +275,7 @@ interface ScopeDiffProps extends BaseComponentProps {
276
275
  /** Hide unchanged keys (default: false) */
277
276
  hideUnchanged?: boolean;
278
277
  }
279
- declare function ScopeDiff({ previous, current, hideUnchanged, size, unstyled, className, style, }: ScopeDiffProps): react_jsx_runtime.JSX.Element;
278
+ declare function ScopeDiff({ previous, current, hideUnchanged, size, unstyled, className, style, }: ScopeDiffProps): react.JSX.Element;
280
279
 
281
280
  interface ResultPanelProps extends BaseComponentProps {
282
281
  /** Final pipeline output / shared state */
@@ -286,7 +285,7 @@ interface ResultPanelProps extends BaseComponentProps {
286
285
  /** Hide console section (default: false) */
287
286
  hideConsole?: boolean;
288
287
  }
289
- declare function ResultPanel({ data, logs, hideConsole, size, unstyled, className, style, }: ResultPanelProps): react_jsx_runtime.JSX.Element;
288
+ declare function ResultPanel({ data, logs, hideConsole, size, unstyled, className, style, }: ResultPanelProps): react.JSX.Element;
290
289
 
291
290
  type StageDetailMode = "simple" | "dev";
292
291
  interface MemoryChange {
@@ -309,7 +308,7 @@ interface StageDetailPanelProps extends BaseComponentProps {
309
308
  /** Keys to exclude from memory display (default: engine internals). Pass empty set to show all. */
310
309
  excludeKeys?: Set<string>;
311
310
  }
312
- declare function StageDetailPanel({ snapshots, selectedIndex, mode: controlledMode, showToggle, onModeChange, size, unstyled, className, style, }: StageDetailPanelProps): react_jsx_runtime.JSX.Element;
311
+ declare function StageDetailPanel({ snapshots, selectedIndex, mode: controlledMode, showToggle, onModeChange, size, unstyled, className, style, }: StageDetailPanelProps): react.JSX.Element;
313
312
 
314
313
  interface TimeTravelControlsProps extends BaseComponentProps {
315
314
  /** Stage snapshots */
@@ -321,7 +320,7 @@ interface TimeTravelControlsProps extends BaseComponentProps {
321
320
  /** Enable auto-play with Gantt-proportional timing */
322
321
  autoPlayable?: boolean;
323
322
  }
324
- declare function TimeTravelControls({ snapshots, selectedIndex, onIndexChange, autoPlayable, size, unstyled, className, style, }: TimeTravelControlsProps): react_jsx_runtime.JSX.Element;
323
+ declare function TimeTravelControls({ snapshots, selectedIndex, onIndexChange, autoPlayable, size, unstyled, className, style, }: TimeTravelControlsProps): react.JSX.Element;
325
324
 
326
325
  /**
327
326
  * One entry in the execution timeline. `<TracedFlow>` keys time-travel
@@ -707,7 +706,7 @@ interface ExplainableShellProps extends BaseComponentProps {
707
706
  */
708
707
  showStageId?: boolean;
709
708
  }
710
- declare function ExplainableShell({ snapshots: snapshotsProp, runtimeSnapshot, spec, title, resultData: resultDataProp, logs, narrativeEntries, tabs, defaultTab, hideConsole, hideTabs: hideTabsProp, panelLabels, defaultExpanded, recorderViews, renderFlowchart, showStageId, traceGraph, runtimeOverlay, size, unstyled, className, style, }: ExplainableShellProps): react_jsx_runtime.JSX.Element;
709
+ declare function ExplainableShell({ snapshots: snapshotsProp, runtimeSnapshot, spec, title, resultData: resultDataProp, logs, narrativeEntries, tabs, defaultTab, hideConsole, hideTabs: hideTabsProp, panelLabels, defaultExpanded, recorderViews, renderFlowchart, showStageId, traceGraph, runtimeOverlay, size, unstyled, className, style, }: ExplainableShellProps): react.JSX.Element;
711
710
 
712
711
  /**
713
712
  * TraceViewer — drop-in component that renders an `agentfootprint.exportTrace()`
@@ -803,7 +802,7 @@ interface MemoryPanelProps extends BaseComponentProps {
803
802
  snapshots: StageSnapshot[];
804
803
  selectedIndex: number;
805
804
  }
806
- declare function MemoryPanel({ snapshots, selectedIndex, size, unstyled, className, style, }: MemoryPanelProps): react_jsx_runtime.JSX.Element;
805
+ declare function MemoryPanel({ snapshots, selectedIndex, size, unstyled, className, style, }: MemoryPanelProps): react.JSX.Element;
807
806
 
808
807
  interface NarrativePanelProps extends BaseComponentProps {
809
808
  snapshots: StageSnapshot[];
@@ -827,7 +826,7 @@ interface NarrativePanelProps extends BaseComponentProps {
827
826
  */
828
827
  spec?: any;
829
828
  }
830
- declare function NarrativePanel({ snapshots, selectedIndex, narrativeEntries, runtimeSnapshot, spec, size, unstyled, className, style, }: NarrativePanelProps): react_jsx_runtime.JSX.Element;
829
+ declare function NarrativePanel({ snapshots, selectedIndex, narrativeEntries, runtimeSnapshot, spec, size, unstyled, className, style, }: NarrativePanelProps): react.JSX.Element;
831
830
 
832
831
  interface StoryNarrativeProps extends BaseComponentProps {
833
832
  /** Structured narrative entries from CombinedNarrativeRecorder */
@@ -835,7 +834,7 @@ interface StoryNarrativeProps extends BaseComponentProps {
835
834
  /** Number of entries to reveal (position-based sync from NarrativePanel) */
836
835
  revealedEntryCount: number;
837
836
  }
838
- declare function StoryNarrative({ entries, revealedEntryCount, size, unstyled, className, style: outerStyle, }: StoryNarrativeProps): react_jsx_runtime.JSX.Element;
837
+ declare function StoryNarrative({ entries, revealedEntryCount, size, unstyled, className, style: outerStyle, }: StoryNarrativeProps): react.JSX.Element;
839
838
 
840
839
  interface SubflowTreeEntry {
841
840
  /** Node name / identifier */
package/dist/index.js CHANGED
@@ -2208,7 +2208,7 @@ function TimeTravelControls({
2208
2208
  }
2209
2209
 
2210
2210
  // src/components/ExplainableShell/ExplainableShell.tsx
2211
- import { memo as memo8, useState as useState14, useCallback as useCallback8, useMemo as useMemo12, useRef as useRef9, useEffect as useEffect9 } from "react";
2211
+ import { memo as memo8, useState as useState14, useCallback as useCallback8, useMemo as useMemo12, useRef as useRef9, useEffect as useEffect11 } from "react";
2212
2212
 
2213
2213
  // src/utils/narrativeSync.ts
2214
2214
  function buildEntryRangeIndex(entries) {
@@ -3331,7 +3331,7 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
3331
3331
  });
3332
3332
 
3333
3333
  // src/components/FlowchartView/TracedFlow.tsx
3334
- import { useCallback as useCallback7, useMemo as useMemo10, useRef as useRef8, useState as useState10 } from "react";
3334
+ import { useCallback as useCallback7, useEffect as useEffect10, useMemo as useMemo10, useRef as useRef8, useState as useState10 } from "react";
3335
3335
  import {
3336
3336
  ReactFlow,
3337
3337
  Background,
@@ -3449,6 +3449,163 @@ function dagreTraceLayout(graph, options = {}) {
3449
3449
  });
3450
3450
  return { nodes: positioned, edges: graph.edges };
3451
3451
  }
3452
+ function createDagreTraceLayout(options = {}) {
3453
+ return (graph) => dagreTraceLayout(graph, options);
3454
+ }
3455
+
3456
+ // src/components/FlowchartView/_internal/devWarn.ts
3457
+ function isDevModeEnv() {
3458
+ const proc = globalThis.process;
3459
+ return proc?.env?.NODE_ENV !== "production";
3460
+ }
3461
+ function devWarn(messageFn, ...extras) {
3462
+ if (!isDevModeEnv()) return;
3463
+ console.warn(messageFn(), ...extras);
3464
+ }
3465
+
3466
+ // src/components/FlowchartView/_internal/snapLinearSuccessors.ts
3467
+ function snapLinearSuccessors(graph, options = {}) {
3468
+ if (graph.nodes.length === 0) return graph;
3469
+ const fallbackW = options.nodeWidth ?? DEFAULT_NODE_W;
3470
+ const fallbackH = options.nodeHeight ?? DEFAULT_NODE_H;
3471
+ const byId = /* @__PURE__ */ new Map();
3472
+ const width = /* @__PURE__ */ new Map();
3473
+ for (const n of graph.nodes) {
3474
+ byId.set(n.id, n);
3475
+ width.set(n.id, sizeOf(n, fallbackW, fallbackH, options.nodeSize).width);
3476
+ }
3477
+ const preds = /* @__PURE__ */ new Map();
3478
+ const outDegree = /* @__PURE__ */ new Map();
3479
+ const seenEdge = /* @__PURE__ */ new Set();
3480
+ for (const e of graph.edges) {
3481
+ if (e.data?.kind === "loop") continue;
3482
+ if (!byId.has(e.source) || !byId.has(e.target)) continue;
3483
+ const key = `${e.source}\0${e.target}`;
3484
+ if (seenEdge.has(key)) continue;
3485
+ seenEdge.add(key);
3486
+ const list = preds.get(e.target);
3487
+ if (list) list.push(e.source);
3488
+ else preds.set(e.target, [e.source]);
3489
+ outDegree.set(e.source, (outDegree.get(e.source) ?? 0) + 1);
3490
+ }
3491
+ const workingX = /* @__PURE__ */ new Map();
3492
+ for (const n of graph.nodes) workingX.set(n.id, n.position.x);
3493
+ const centerX = (id) => workingX.get(id) + width.get(id) / 2;
3494
+ const order = [...graph.nodes].sort(
3495
+ (a, b) => a.position.y - b.position.y || a.position.x - b.position.x || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
3496
+ );
3497
+ for (const n of order) {
3498
+ const p = preds.get(n.id);
3499
+ if (!p || p.length !== 1) continue;
3500
+ const pid = p[0];
3501
+ if ((outDegree.get(pid) ?? 0) !== 1) continue;
3502
+ const P = byId.get(pid);
3503
+ if ((n.parentId ?? void 0) !== (P.parentId ?? void 0)) continue;
3504
+ workingX.set(n.id, centerX(pid) - width.get(n.id) / 2);
3505
+ }
3506
+ const nodes = graph.nodes.map((n) => {
3507
+ const nx = workingX.get(n.id);
3508
+ return nx === n.position.x ? n : { ...n, position: { x: nx, y: n.position.y } };
3509
+ });
3510
+ return { nodes, edges: graph.edges };
3511
+ }
3512
+ function createSnappedDagreLayout(base, options = {}) {
3513
+ return (graph) => snapLinearSuccessors(base(graph), options);
3514
+ }
3515
+
3516
+ // src/components/FlowchartView/_internal/centerForkParents.ts
3517
+ function centerForkParents(graph, options = {}) {
3518
+ if (graph.nodes.length === 0) return graph;
3519
+ const fallbackW = options.nodeWidth ?? DEFAULT_NODE_W;
3520
+ const fallbackH = options.nodeHeight ?? DEFAULT_NODE_H;
3521
+ const byId = /* @__PURE__ */ new Map();
3522
+ const width = /* @__PURE__ */ new Map();
3523
+ for (const n of graph.nodes) {
3524
+ byId.set(n.id, n);
3525
+ width.set(n.id, sizeOf(n, fallbackW, fallbackH, options.nodeSize).width);
3526
+ }
3527
+ const childrenOf = /* @__PURE__ */ new Map();
3528
+ const predsOf = /* @__PURE__ */ new Map();
3529
+ const outDegree = /* @__PURE__ */ new Map();
3530
+ const inDegree = /* @__PURE__ */ new Map();
3531
+ const seen = /* @__PURE__ */ new Set();
3532
+ for (const e of graph.edges) {
3533
+ if (e.data?.kind === "loop") continue;
3534
+ if (!byId.has(e.source) || !byId.has(e.target)) continue;
3535
+ const key = `${e.source} ${e.target}`;
3536
+ if (seen.has(key)) continue;
3537
+ seen.add(key);
3538
+ const cl = childrenOf.get(e.source);
3539
+ if (cl) cl.push(e.target);
3540
+ else childrenOf.set(e.source, [e.target]);
3541
+ const pl = predsOf.get(e.target);
3542
+ if (pl) pl.push(e.source);
3543
+ else predsOf.set(e.target, [e.source]);
3544
+ outDegree.set(e.source, (outDegree.get(e.source) ?? 0) + 1);
3545
+ inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);
3546
+ }
3547
+ const workingX = /* @__PURE__ */ new Map();
3548
+ for (const n of graph.nodes) workingX.set(n.id, n.position.x);
3549
+ const centerX = (id) => workingX.get(id) + width.get(id) / 2;
3550
+ const nodeSep = options.nodeSep ?? 60;
3551
+ const clampX = (id, desiredX) => {
3552
+ const w = width.get(id);
3553
+ const x0 = workingX.get(id);
3554
+ const self = byId.get(id);
3555
+ let minX = -Infinity;
3556
+ let maxX = Infinity;
3557
+ for (const m of graph.nodes) {
3558
+ if (m.id === id || m.parentId !== self.parentId) continue;
3559
+ if (Math.abs(m.position.y - self.position.y) > 1) continue;
3560
+ const mLeft = workingX.get(m.id);
3561
+ const mRight = mLeft + width.get(m.id);
3562
+ if (mRight <= x0) minX = Math.max(minX, mRight + nodeSep);
3563
+ else if (mLeft >= x0 + w) maxX = Math.min(maxX, mLeft - nodeSep - w);
3564
+ }
3565
+ return minX <= maxX ? Math.max(minX, Math.min(maxX, desiredX)) : x0;
3566
+ };
3567
+ const order = [...graph.nodes].sort(
3568
+ (a, b) => b.position.y - a.position.y || a.position.x - b.position.x || a.id.localeCompare(b.id)
3569
+ );
3570
+ for (const n of order) {
3571
+ const outD = outDegree.get(n.id) ?? 0;
3572
+ const inD = inDegree.get(n.id) ?? 0;
3573
+ const isFork = outD >= 2 && inD <= 1;
3574
+ const isMerge = inD >= 2 && outD <= 1;
3575
+ if (!isFork && !isMerge) continue;
3576
+ const kin = ((isFork ? childrenOf.get(n.id) : predsOf.get(n.id)) ?? []).filter(
3577
+ (k) => byId.get(k)?.parentId === n.parentId
3578
+ // same compound only
3579
+ );
3580
+ if (kin.length < 2) continue;
3581
+ const centers = kin.map(centerX);
3582
+ const wN = width.get(n.id);
3583
+ const span = (Math.min(...centers) + Math.max(...centers)) / 2;
3584
+ workingX.set(n.id, clampX(n.id, span - wN / 2));
3585
+ const stepOf = isFork ? predsOf : childrenOf;
3586
+ let curId = n.id;
3587
+ const walked = /* @__PURE__ */ new Set([curId]);
3588
+ for (; ; ) {
3589
+ const nexts = stepOf.get(curId);
3590
+ if (!nexts || nexts.length !== 1) break;
3591
+ const m = nexts[0];
3592
+ if (walked.has(m)) break;
3593
+ if ((outDegree.get(m) ?? 0) > 1) break;
3594
+ if ((inDegree.get(m) ?? 0) > 1) break;
3595
+ if (byId.get(m)?.parentId !== byId.get(curId)?.parentId) break;
3596
+ workingX.set(m, clampX(m, centerX(curId) - width.get(m) / 2));
3597
+ walked.add(m);
3598
+ curId = m;
3599
+ }
3600
+ }
3601
+ const nodes = graph.nodes.map(
3602
+ (n) => workingX.get(n.id) === n.position.x ? n : { ...n, position: { x: workingX.get(n.id), y: n.position.y } }
3603
+ );
3604
+ return { nodes, edges: graph.edges };
3605
+ }
3606
+ function withForkCentering(base, options = {}) {
3607
+ return (graph) => centerForkParents(base(graph), options);
3608
+ }
3452
3609
 
3453
3610
  // src/components/FlowchartView/createTraceRuntimeOverlay.ts
3454
3611
  function sliceOverlay(overlay, index) {
@@ -3888,7 +4045,7 @@ var StageNode = memo3(function StageNode2({
3888
4045
  background: bg,
3889
4046
  border: `${isHero ? "2.5px" : isMuted ? "1px" : "2px"} ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
3890
4047
  borderRadius: theme.radius,
3891
- padding: description ? `${Math.round(8 * sizeScale)}px ${Math.round(16 * sizeScale)}px` : `${Math.round(10 * sizeScale)}px ${Math.round(20 * sizeScale)}px`,
4048
+ padding: description ? `${Math.round(6 * sizeScale)}px ${Math.round(12 * sizeScale)}px` : `${Math.round(7 * sizeScale)}px ${Math.round(14 * sizeScale)}px`,
3892
4049
  display: "flex",
3893
4050
  flexDirection: "column",
3894
4051
  alignItems: "center",
@@ -4509,6 +4666,14 @@ function staggeredBendY(sourceBottom, targetTop, others, minGapFromTarget = 8) {
4509
4666
  if (lowestSkippedBottom === -Infinity) return null;
4510
4667
  return Math.min((lowestSkippedBottom + targetTop) / 2, targetTop - minGapFromTarget);
4511
4668
  }
4669
+ function forkFanBendY(sourceBottom, childTops, minGapFromTarget = 8) {
4670
+ if (childTops.length < 2) return null;
4671
+ const nearestTop = Math.min(...childTops);
4672
+ return Math.min((sourceBottom + nearestTop) / 2, nearestTop - minGapFromTarget);
4673
+ }
4674
+ function resolveStepBendY(forkBend, staggeredBend) {
4675
+ return staggeredBend ?? forkBend;
4676
+ }
4512
4677
 
4513
4678
  // src/components/SmartStepEdge/SmartStepEdge.tsx
4514
4679
  import { jsx as jsx20 } from "react/jsx-runtime";
@@ -4531,6 +4696,16 @@ function SmartStepEdge({
4531
4696
  if (!src || !tgt) return null;
4532
4697
  const sourceBottom = src.internals.positionAbsolute.y + (src.measured.height ?? 0);
4533
4698
  const targetTop = tgt.internals.positionAbsolute.y;
4699
+ const childTops = [];
4700
+ for (const e of s.edges) {
4701
+ if (e.source !== source) continue;
4702
+ if (e.data?.kind === "loop") continue;
4703
+ const c = s.nodeLookup.get(e.target);
4704
+ if (c && c.type !== GROUP_CONTAINER_NODE_TYPE) {
4705
+ childTops.push(c.internals.positionAbsolute.y);
4706
+ }
4707
+ }
4708
+ const fan = forkFanBendY(sourceBottom, childTops);
4534
4709
  const others = [];
4535
4710
  for (const n of s.nodeLookup.values()) {
4536
4711
  if (n.id === source || n.id === target) continue;
@@ -4538,7 +4713,8 @@ function SmartStepEdge({
4538
4713
  const top = n.internals.positionAbsolute.y;
4539
4714
  others.push({ top, bottom: top + (n.measured.height ?? 0) });
4540
4715
  }
4541
- return staggeredBendY(sourceBottom, targetTop, others);
4716
+ const staggered = staggeredBendY(sourceBottom, targetTop, others);
4717
+ return resolveStepBendY(fan, staggered);
4542
4718
  });
4543
4719
  const [path] = getSmoothStepPath({
4544
4720
  sourceX,
@@ -4554,6 +4730,49 @@ function SmartStepEdge({
4554
4730
  return /* @__PURE__ */ jsx20(BaseEdge2, { id, path, markerEnd, style });
4555
4731
  }
4556
4732
 
4733
+ // src/components/FlowchartView/_internal/MeasuredNodeSizes.tsx
4734
+ import { useEffect as useEffect9 } from "react";
4735
+ import { useNodesInitialized, useStore as useStore3 } from "@xyflow/react";
4736
+
4737
+ // src/components/FlowchartView/_internal/measuredFootprints.ts
4738
+ function extractMeasuredFootprints(entries) {
4739
+ const sizes = /* @__PURE__ */ new Map();
4740
+ for (const [id, node] of entries) {
4741
+ const width = node.measured?.width;
4742
+ const height = node.measured?.height;
4743
+ if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
4744
+ sizes.set(id, { width: Math.round(width), height: Math.round(height) });
4745
+ }
4746
+ }
4747
+ return sizes;
4748
+ }
4749
+ function sameFootprints(a, b) {
4750
+ if (a === b) return true;
4751
+ if (a.size !== b.size) return false;
4752
+ for (const [id, s] of a) {
4753
+ const t = b.get(id);
4754
+ if (!t || t.width !== s.width || t.height !== s.height) return false;
4755
+ }
4756
+ return true;
4757
+ }
4758
+
4759
+ // src/components/FlowchartView/_internal/MeasuredNodeSizes.tsx
4760
+ function MeasuredNodeSizes({
4761
+ onSizes,
4762
+ includeHiddenNodes = false
4763
+ }) {
4764
+ const initialized = useNodesInitialized({ includeHiddenNodes });
4765
+ const sizes = useStore3(
4766
+ (s) => extractMeasuredFootprints(s.nodeLookup),
4767
+ sameFootprints
4768
+ );
4769
+ useEffect9(() => {
4770
+ if (!initialized || sizes.size === 0) return;
4771
+ onSizes(sizes);
4772
+ }, [initialized, sizes, onSizes]);
4773
+ return null;
4774
+ }
4775
+
4557
4776
  // src/components/FlowchartView/TracedFlow.tsx
4558
4777
  import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
4559
4778
  var DEFAULT_COLORS = {
@@ -4693,6 +4912,13 @@ function TracedFlow({
4693
4912
  style
4694
4913
  }) {
4695
4914
  const layout = layoutProp ?? dagreTraceLayout;
4915
+ useEffect10(() => {
4916
+ if (layoutProp === dagreTraceLayout) {
4917
+ devWarn(
4918
+ () => "[footprint-explainable-ui] <TracedFlow layout={dagreTraceLayout}> bypasses the built-in measure-then-layout pipeline (content-exact sizing, fork/merge centering, straight spines). OMIT the `layout` prop to use it \u2014 passing the raw dagreTraceLayout silently forfeits every layout improvement eui ships."
4919
+ );
4920
+ }
4921
+ }, [layoutProp]);
4696
4922
  const colors = useMemo10(
4697
4923
  () => ({ ...DEFAULT_COLORS, ...colorOverrides ?? {} }),
4698
4924
  [colorOverrides]
@@ -4726,8 +4952,19 @@ function TracedFlow({
4726
4952
  () => buildSubflowBreadcrumb(graph, drill.currentSubflowId),
4727
4953
  [graph, drill.currentSubflowId]
4728
4954
  );
4955
+ const [measuredSizes, setMeasuredSizes] = useState10(null);
4729
4956
  const positioned = useMemo10(() => {
4730
- const realBase = layout === "passthrough" ? (g) => g : layout;
4957
+ const nodeSize = measuredSizes ? (n) => measuredSizes.get(n.id) : void 0;
4958
+ const sizeOpts = nodeSize ? { nodeSize } : {};
4959
+ const dagreBase = withForkCentering(
4960
+ createSnappedDagreLayout(
4961
+ createDagreTraceLayout({ ...sizeOpts, rankSep: 52, nodeSep: 36 }),
4962
+ sizeOpts
4963
+ ),
4964
+ { ...sizeOpts, nodeSep: 36 }
4965
+ // same nodeSep → clamp preserves dagre's reserved gap
4966
+ );
4967
+ const realBase = layout === "passthrough" ? (g) => g : layoutProp === void 0 ? dagreBase : layout;
4731
4968
  if (groupedSet.size > 0) {
4732
4969
  const grouped = applyGroupLayout(filteredGraph, {
4733
4970
  groupedSubflowIds: [...groupedSet],
@@ -4738,8 +4975,8 @@ function TracedFlow({
4738
4975
  if (mainChartBox) {
4739
4976
  return wrapInMainChartBox(filteredGraph, { baseLayout: realBase, ...mainChartBox });
4740
4977
  }
4741
- return layout === "passthrough" ? filteredGraph : layout(filteredGraph);
4742
- }, [filteredGraph, layout, groupedSet, mainChartBox]);
4978
+ return realBase(filteredGraph);
4979
+ }, [filteredGraph, layout, layoutProp, groupedSet, mainChartBox, measuredSizes]);
4743
4980
  const slice = useMemo10(() => {
4744
4981
  const empty = {
4745
4982
  doneStageIds: /* @__PURE__ */ new Set(),
@@ -4783,7 +5020,11 @@ function TracedFlow({
4783
5020
  );
4784
5021
  const wrapperRef = useRef8(null);
4785
5022
  const [rfInstance, setRfInstance] = useState10(null);
4786
- useChartAutoRefit(wrapperRef, rfInstance, { refitKey: drill.currentSubflowId });
5023
+ useChartAutoRefit(wrapperRef, rfInstance, {
5024
+ // Re-fit on drill AND after the measured-size re-layout settles.
5025
+ refitKey: `${drill.currentSubflowId ?? ""}:${measuredSizes ? "measured" : "estimated"}`,
5026
+ padding: 0.18
5027
+ });
4787
5028
  return /* @__PURE__ */ jsxs18(
4788
5029
  "div",
4789
5030
  {
@@ -4815,8 +5056,11 @@ function TracedFlow({
4815
5056
  onNodeClick: handleNodeClick,
4816
5057
  onInit: setRfInstance,
4817
5058
  fitView: true,
5059
+ fitViewOptions: { padding: 0.18 },
5060
+ minZoom: 0.1,
4818
5061
  proOptions: { hideAttribution: true },
4819
5062
  children: [
5063
+ /* @__PURE__ */ jsx21(MeasuredNodeSizes, { onSizes: setMeasuredSizes }),
4820
5064
  /* @__PURE__ */ jsx21(Background, { variant: BackgroundVariant.Dots, gap: 20, size: 1 }),
4821
5065
  children
4822
5066
  ]
@@ -5590,7 +5834,7 @@ var DetailsContent = memo8(function DetailsContent2({
5590
5834
  const allViews = [...builtInViews, ...extraViews ?? []];
5591
5835
  const [activeViewId, setActiveViewId] = useState14(allViews[0]?.id ?? "memory");
5592
5836
  const viewIds = allViews.map((v2) => v2.id).join(",");
5593
- useEffect9(() => {
5837
+ useEffect11(() => {
5594
5838
  if (!allViews.find((v2) => v2.id === activeViewId)) {
5595
5839
  setActiveViewId(allViews[0]?.id ?? "memory");
5596
5840
  }
@@ -5847,7 +6091,7 @@ function ExplainableShell({
5847
6091
  const shellRef = useRef9(null);
5848
6092
  const [isNarrow, setIsNarrow] = useState14(false);
5849
6093
  const [isMedium, setIsMedium] = useState14(false);
5850
- useEffect9(() => {
6094
+ useEffect11(() => {
5851
6095
  const el = shellRef.current;
5852
6096
  if (!el) return;
5853
6097
  const ro = new ResizeObserver(([entry]) => {
@@ -5892,7 +6136,7 @@ function ExplainableShell({
5892
6136
  const [rightPanelMode, setRightPanelMode] = useState14("insights");
5893
6137
  const [leftExpanded, setLeftExpanded] = useState14(defaultExpanded?.topology ?? false);
5894
6138
  const [timelineExpanded, setTimelineExpanded] = useState14(defaultExpanded?.timeline ?? false);
5895
- useEffect9(() => {
6139
+ useEffect11(() => {
5896
6140
  if (isNarrow) {
5897
6141
  setLeftExpanded(false);
5898
6142
  setRightExpanded(false);