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.cjs CHANGED
@@ -2280,7 +2280,7 @@ function TimeTravelControls({
2280
2280
  }
2281
2281
 
2282
2282
  // src/components/ExplainableShell/ExplainableShell.tsx
2283
- var import_react29 = require("react");
2283
+ var import_react31 = require("react");
2284
2284
 
2285
2285
  // src/utils/narrativeSync.ts
2286
2286
  function buildEntryRangeIndex(entries) {
@@ -3403,8 +3403,8 @@ var SubflowBreadcrumb = (0, import_react14.memo)(function SubflowBreadcrumb2({
3403
3403
  });
3404
3404
 
3405
3405
  // src/components/FlowchartView/TracedFlow.tsx
3406
- var import_react23 = require("react");
3407
- var import_react24 = require("@xyflow/react");
3406
+ var import_react25 = require("react");
3407
+ var import_react26 = require("@xyflow/react");
3408
3408
 
3409
3409
  // src/components/FlowchartView/_internal/dagreTraceLayout.ts
3410
3410
  var import_dagre = __toESM(require("dagre"), 1);
@@ -3516,6 +3516,163 @@ function dagreTraceLayout(graph, options = {}) {
3516
3516
  });
3517
3517
  return { nodes: positioned, edges: graph.edges };
3518
3518
  }
3519
+ function createDagreTraceLayout(options = {}) {
3520
+ return (graph) => dagreTraceLayout(graph, options);
3521
+ }
3522
+
3523
+ // src/components/FlowchartView/_internal/devWarn.ts
3524
+ function isDevModeEnv() {
3525
+ const proc = globalThis.process;
3526
+ return proc?.env?.NODE_ENV !== "production";
3527
+ }
3528
+ function devWarn(messageFn, ...extras) {
3529
+ if (!isDevModeEnv()) return;
3530
+ console.warn(messageFn(), ...extras);
3531
+ }
3532
+
3533
+ // src/components/FlowchartView/_internal/snapLinearSuccessors.ts
3534
+ function snapLinearSuccessors(graph, options = {}) {
3535
+ if (graph.nodes.length === 0) return graph;
3536
+ const fallbackW = options.nodeWidth ?? DEFAULT_NODE_W;
3537
+ const fallbackH = options.nodeHeight ?? DEFAULT_NODE_H;
3538
+ const byId = /* @__PURE__ */ new Map();
3539
+ const width = /* @__PURE__ */ new Map();
3540
+ for (const n of graph.nodes) {
3541
+ byId.set(n.id, n);
3542
+ width.set(n.id, sizeOf(n, fallbackW, fallbackH, options.nodeSize).width);
3543
+ }
3544
+ const preds = /* @__PURE__ */ new Map();
3545
+ const outDegree = /* @__PURE__ */ new Map();
3546
+ const seenEdge = /* @__PURE__ */ new Set();
3547
+ for (const e of graph.edges) {
3548
+ if (e.data?.kind === "loop") continue;
3549
+ if (!byId.has(e.source) || !byId.has(e.target)) continue;
3550
+ const key = `${e.source}\0${e.target}`;
3551
+ if (seenEdge.has(key)) continue;
3552
+ seenEdge.add(key);
3553
+ const list = preds.get(e.target);
3554
+ if (list) list.push(e.source);
3555
+ else preds.set(e.target, [e.source]);
3556
+ outDegree.set(e.source, (outDegree.get(e.source) ?? 0) + 1);
3557
+ }
3558
+ const workingX = /* @__PURE__ */ new Map();
3559
+ for (const n of graph.nodes) workingX.set(n.id, n.position.x);
3560
+ const centerX = (id) => workingX.get(id) + width.get(id) / 2;
3561
+ const order = [...graph.nodes].sort(
3562
+ (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)
3563
+ );
3564
+ for (const n of order) {
3565
+ const p = preds.get(n.id);
3566
+ if (!p || p.length !== 1) continue;
3567
+ const pid = p[0];
3568
+ if ((outDegree.get(pid) ?? 0) !== 1) continue;
3569
+ const P = byId.get(pid);
3570
+ if ((n.parentId ?? void 0) !== (P.parentId ?? void 0)) continue;
3571
+ workingX.set(n.id, centerX(pid) - width.get(n.id) / 2);
3572
+ }
3573
+ const nodes = graph.nodes.map((n) => {
3574
+ const nx = workingX.get(n.id);
3575
+ return nx === n.position.x ? n : { ...n, position: { x: nx, y: n.position.y } };
3576
+ });
3577
+ return { nodes, edges: graph.edges };
3578
+ }
3579
+ function createSnappedDagreLayout(base, options = {}) {
3580
+ return (graph) => snapLinearSuccessors(base(graph), options);
3581
+ }
3582
+
3583
+ // src/components/FlowchartView/_internal/centerForkParents.ts
3584
+ function centerForkParents(graph, options = {}) {
3585
+ if (graph.nodes.length === 0) return graph;
3586
+ const fallbackW = options.nodeWidth ?? DEFAULT_NODE_W;
3587
+ const fallbackH = options.nodeHeight ?? DEFAULT_NODE_H;
3588
+ const byId = /* @__PURE__ */ new Map();
3589
+ const width = /* @__PURE__ */ new Map();
3590
+ for (const n of graph.nodes) {
3591
+ byId.set(n.id, n);
3592
+ width.set(n.id, sizeOf(n, fallbackW, fallbackH, options.nodeSize).width);
3593
+ }
3594
+ const childrenOf = /* @__PURE__ */ new Map();
3595
+ const predsOf = /* @__PURE__ */ new Map();
3596
+ const outDegree = /* @__PURE__ */ new Map();
3597
+ const inDegree = /* @__PURE__ */ new Map();
3598
+ const seen = /* @__PURE__ */ new Set();
3599
+ for (const e of graph.edges) {
3600
+ if (e.data?.kind === "loop") continue;
3601
+ if (!byId.has(e.source) || !byId.has(e.target)) continue;
3602
+ const key = `${e.source} ${e.target}`;
3603
+ if (seen.has(key)) continue;
3604
+ seen.add(key);
3605
+ const cl = childrenOf.get(e.source);
3606
+ if (cl) cl.push(e.target);
3607
+ else childrenOf.set(e.source, [e.target]);
3608
+ const pl = predsOf.get(e.target);
3609
+ if (pl) pl.push(e.source);
3610
+ else predsOf.set(e.target, [e.source]);
3611
+ outDegree.set(e.source, (outDegree.get(e.source) ?? 0) + 1);
3612
+ inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);
3613
+ }
3614
+ const workingX = /* @__PURE__ */ new Map();
3615
+ for (const n of graph.nodes) workingX.set(n.id, n.position.x);
3616
+ const centerX = (id) => workingX.get(id) + width.get(id) / 2;
3617
+ const nodeSep = options.nodeSep ?? 60;
3618
+ const clampX = (id, desiredX) => {
3619
+ const w = width.get(id);
3620
+ const x0 = workingX.get(id);
3621
+ const self = byId.get(id);
3622
+ let minX = -Infinity;
3623
+ let maxX = Infinity;
3624
+ for (const m of graph.nodes) {
3625
+ if (m.id === id || m.parentId !== self.parentId) continue;
3626
+ if (Math.abs(m.position.y - self.position.y) > 1) continue;
3627
+ const mLeft = workingX.get(m.id);
3628
+ const mRight = mLeft + width.get(m.id);
3629
+ if (mRight <= x0) minX = Math.max(minX, mRight + nodeSep);
3630
+ else if (mLeft >= x0 + w) maxX = Math.min(maxX, mLeft - nodeSep - w);
3631
+ }
3632
+ return minX <= maxX ? Math.max(minX, Math.min(maxX, desiredX)) : x0;
3633
+ };
3634
+ const order = [...graph.nodes].sort(
3635
+ (a, b) => b.position.y - a.position.y || a.position.x - b.position.x || a.id.localeCompare(b.id)
3636
+ );
3637
+ for (const n of order) {
3638
+ const outD = outDegree.get(n.id) ?? 0;
3639
+ const inD = inDegree.get(n.id) ?? 0;
3640
+ const isFork = outD >= 2 && inD <= 1;
3641
+ const isMerge = inD >= 2 && outD <= 1;
3642
+ if (!isFork && !isMerge) continue;
3643
+ const kin = ((isFork ? childrenOf.get(n.id) : predsOf.get(n.id)) ?? []).filter(
3644
+ (k) => byId.get(k)?.parentId === n.parentId
3645
+ // same compound only
3646
+ );
3647
+ if (kin.length < 2) continue;
3648
+ const centers = kin.map(centerX);
3649
+ const wN = width.get(n.id);
3650
+ const span = (Math.min(...centers) + Math.max(...centers)) / 2;
3651
+ workingX.set(n.id, clampX(n.id, span - wN / 2));
3652
+ const stepOf = isFork ? predsOf : childrenOf;
3653
+ let curId = n.id;
3654
+ const walked = /* @__PURE__ */ new Set([curId]);
3655
+ for (; ; ) {
3656
+ const nexts = stepOf.get(curId);
3657
+ if (!nexts || nexts.length !== 1) break;
3658
+ const m = nexts[0];
3659
+ if (walked.has(m)) break;
3660
+ if ((outDegree.get(m) ?? 0) > 1) break;
3661
+ if ((inDegree.get(m) ?? 0) > 1) break;
3662
+ if (byId.get(m)?.parentId !== byId.get(curId)?.parentId) break;
3663
+ workingX.set(m, clampX(m, centerX(curId) - width.get(m) / 2));
3664
+ walked.add(m);
3665
+ curId = m;
3666
+ }
3667
+ }
3668
+ const nodes = graph.nodes.map(
3669
+ (n) => workingX.get(n.id) === n.position.x ? n : { ...n, position: { x: workingX.get(n.id), y: n.position.y } }
3670
+ );
3671
+ return { nodes, edges: graph.edges };
3672
+ }
3673
+ function withForkCentering(base, options = {}) {
3674
+ return (graph) => centerForkParents(base(graph), options);
3675
+ }
3519
3676
 
3520
3677
  // src/components/FlowchartView/createTraceRuntimeOverlay.ts
3521
3678
  function sliceOverlay(overlay, index) {
@@ -3955,7 +4112,7 @@ var StageNode = (0, import_react15.memo)(function StageNode2({
3955
4112
  background: bg,
3956
4113
  border: `${isHero ? "2.5px" : isMuted ? "1px" : "2px"} ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
3957
4114
  borderRadius: theme.radius,
3958
- padding: description ? `${Math.round(8 * sizeScale)}px ${Math.round(16 * sizeScale)}px` : `${Math.round(10 * sizeScale)}px ${Math.round(20 * sizeScale)}px`,
4115
+ padding: description ? `${Math.round(6 * sizeScale)}px ${Math.round(12 * sizeScale)}px` : `${Math.round(7 * sizeScale)}px ${Math.round(14 * sizeScale)}px`,
3959
4116
  display: "flex",
3960
4117
  flexDirection: "column",
3961
4118
  alignItems: "center",
@@ -4576,6 +4733,14 @@ function staggeredBendY(sourceBottom, targetTop, others, minGapFromTarget = 8) {
4576
4733
  if (lowestSkippedBottom === -Infinity) return null;
4577
4734
  return Math.min((lowestSkippedBottom + targetTop) / 2, targetTop - minGapFromTarget);
4578
4735
  }
4736
+ function forkFanBendY(sourceBottom, childTops, minGapFromTarget = 8) {
4737
+ if (childTops.length < 2) return null;
4738
+ const nearestTop = Math.min(...childTops);
4739
+ return Math.min((sourceBottom + nearestTop) / 2, nearestTop - minGapFromTarget);
4740
+ }
4741
+ function resolveStepBendY(forkBend, staggeredBend) {
4742
+ return staggeredBend ?? forkBend;
4743
+ }
4579
4744
 
4580
4745
  // src/components/SmartStepEdge/SmartStepEdge.tsx
4581
4746
  var import_jsx_runtime20 = require("react/jsx-runtime");
@@ -4598,6 +4763,16 @@ function SmartStepEdge({
4598
4763
  if (!src || !tgt) return null;
4599
4764
  const sourceBottom = src.internals.positionAbsolute.y + (src.measured.height ?? 0);
4600
4765
  const targetTop = tgt.internals.positionAbsolute.y;
4766
+ const childTops = [];
4767
+ for (const e of s.edges) {
4768
+ if (e.source !== source) continue;
4769
+ if (e.data?.kind === "loop") continue;
4770
+ const c = s.nodeLookup.get(e.target);
4771
+ if (c && c.type !== GROUP_CONTAINER_NODE_TYPE) {
4772
+ childTops.push(c.internals.positionAbsolute.y);
4773
+ }
4774
+ }
4775
+ const fan = forkFanBendY(sourceBottom, childTops);
4601
4776
  const others = [];
4602
4777
  for (const n of s.nodeLookup.values()) {
4603
4778
  if (n.id === source || n.id === target) continue;
@@ -4605,7 +4780,8 @@ function SmartStepEdge({
4605
4780
  const top = n.internals.positionAbsolute.y;
4606
4781
  others.push({ top, bottom: top + (n.measured.height ?? 0) });
4607
4782
  }
4608
- return staggeredBendY(sourceBottom, targetTop, others);
4783
+ const staggered = staggeredBendY(sourceBottom, targetTop, others);
4784
+ return resolveStepBendY(fan, staggered);
4609
4785
  });
4610
4786
  const [path] = (0, import_react21.getSmoothStepPath)({
4611
4787
  sourceX,
@@ -4621,6 +4797,49 @@ function SmartStepEdge({
4621
4797
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react21.BaseEdge, { id, path, markerEnd, style });
4622
4798
  }
4623
4799
 
4800
+ // src/components/FlowchartView/_internal/MeasuredNodeSizes.tsx
4801
+ var import_react23 = require("react");
4802
+ var import_react24 = require("@xyflow/react");
4803
+
4804
+ // src/components/FlowchartView/_internal/measuredFootprints.ts
4805
+ function extractMeasuredFootprints(entries) {
4806
+ const sizes = /* @__PURE__ */ new Map();
4807
+ for (const [id, node] of entries) {
4808
+ const width = node.measured?.width;
4809
+ const height = node.measured?.height;
4810
+ if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
4811
+ sizes.set(id, { width: Math.round(width), height: Math.round(height) });
4812
+ }
4813
+ }
4814
+ return sizes;
4815
+ }
4816
+ function sameFootprints(a, b) {
4817
+ if (a === b) return true;
4818
+ if (a.size !== b.size) return false;
4819
+ for (const [id, s] of a) {
4820
+ const t = b.get(id);
4821
+ if (!t || t.width !== s.width || t.height !== s.height) return false;
4822
+ }
4823
+ return true;
4824
+ }
4825
+
4826
+ // src/components/FlowchartView/_internal/MeasuredNodeSizes.tsx
4827
+ function MeasuredNodeSizes({
4828
+ onSizes,
4829
+ includeHiddenNodes = false
4830
+ }) {
4831
+ const initialized = (0, import_react24.useNodesInitialized)({ includeHiddenNodes });
4832
+ const sizes = (0, import_react24.useStore)(
4833
+ (s) => extractMeasuredFootprints(s.nodeLookup),
4834
+ sameFootprints
4835
+ );
4836
+ (0, import_react23.useEffect)(() => {
4837
+ if (!initialized || sizes.size === 0) return;
4838
+ onSizes(sizes);
4839
+ }, [initialized, sizes, onSizes]);
4840
+ return null;
4841
+ }
4842
+
4624
4843
  // src/components/FlowchartView/TracedFlow.tsx
4625
4844
  var import_jsx_runtime21 = require("react/jsx-runtime");
4626
4845
  var DEFAULT_COLORS = {
@@ -4730,7 +4949,7 @@ function styleEdgeWithOverlay(edge, doneStageIds, activeStageId, colors) {
4730
4949
  type: kind === "loop" ? "loopBack" : "smartStep",
4731
4950
  animated: isLeadingEdge,
4732
4951
  style: { stroke: color, strokeWidth: traversed ? 2 : 1.5 },
4733
- markerEnd: { type: import_react24.MarkerType.ArrowClosed, color, width: 16, height: 16 }
4952
+ markerEnd: { type: import_react26.MarkerType.ArrowClosed, color, width: 16, height: 16 }
4734
4953
  };
4735
4954
  if (kind === "loop") {
4736
4955
  styled.style = { ...styled.style, strokeDasharray: "4 3" };
@@ -4760,21 +4979,28 @@ function TracedFlow({
4760
4979
  style
4761
4980
  }) {
4762
4981
  const layout = layoutProp ?? dagreTraceLayout;
4763
- const colors = (0, import_react23.useMemo)(
4982
+ (0, import_react25.useEffect)(() => {
4983
+ if (layoutProp === dagreTraceLayout) {
4984
+ devWarn(
4985
+ () => "[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."
4986
+ );
4987
+ }
4988
+ }, [layoutProp]);
4989
+ const colors = (0, import_react25.useMemo)(
4764
4990
  () => ({ ...DEFAULT_COLORS, ...colorOverrides ?? {} }),
4765
4991
  [colorOverrides]
4766
4992
  );
4767
- const mergedNodeTypes = (0, import_react23.useMemo)(
4993
+ const mergedNodeTypes = (0, import_react25.useMemo)(
4768
4994
  () => userNodeTypes ? { ...DEFAULT_NODE_TYPES, ...userNodeTypes } : DEFAULT_NODE_TYPES,
4769
4995
  [userNodeTypes]
4770
4996
  );
4771
- const mergedEdgeTypes = (0, import_react23.useMemo)(
4997
+ const mergedEdgeTypes = (0, import_react25.useMemo)(
4772
4998
  () => userEdgeTypes ? { ...DEFAULT_EDGE_TYPES, ...userEdgeTypes } : DEFAULT_EDGE_TYPES,
4773
4999
  [userEdgeTypes]
4774
5000
  );
4775
5001
  const drill = useSubflowDrill(graph, onSubflowChange);
4776
- const groupedSet = (0, import_react23.useMemo)(() => new Set(groupedSubflows ?? []), [groupedSubflows]);
4777
- const filteredGraph = (0, import_react23.useMemo)(() => {
5002
+ const groupedSet = (0, import_react25.useMemo)(() => new Set(groupedSubflows ?? []), [groupedSubflows]);
5003
+ const filteredGraph = (0, import_react25.useMemo)(() => {
4778
5004
  const base = filterGraphForDrill(graph, drill.currentSubflowId);
4779
5005
  if (groupedSet.size === 0) return base;
4780
5006
  const baseIds = new Set(base.nodes.map((n) => n.id));
@@ -4789,12 +5015,23 @@ function TracedFlow({
4789
5015
  );
4790
5016
  return { nodes: [...base.nodes, ...extraNodes], edges: [...base.edges, ...extraEdges] };
4791
5017
  }, [graph, drill.currentSubflowId, groupedSet]);
4792
- const breadcrumb = (0, import_react23.useMemo)(
5018
+ const breadcrumb = (0, import_react25.useMemo)(
4793
5019
  () => buildSubflowBreadcrumb(graph, drill.currentSubflowId),
4794
5020
  [graph, drill.currentSubflowId]
4795
5021
  );
4796
- const positioned = (0, import_react23.useMemo)(() => {
4797
- const realBase = layout === "passthrough" ? (g) => g : layout;
5022
+ const [measuredSizes, setMeasuredSizes] = (0, import_react25.useState)(null);
5023
+ const positioned = (0, import_react25.useMemo)(() => {
5024
+ const nodeSize = measuredSizes ? (n) => measuredSizes.get(n.id) : void 0;
5025
+ const sizeOpts = nodeSize ? { nodeSize } : {};
5026
+ const dagreBase = withForkCentering(
5027
+ createSnappedDagreLayout(
5028
+ createDagreTraceLayout({ ...sizeOpts, rankSep: 52, nodeSep: 36 }),
5029
+ sizeOpts
5030
+ ),
5031
+ { ...sizeOpts, nodeSep: 36 }
5032
+ // same nodeSep → clamp preserves dagre's reserved gap
5033
+ );
5034
+ const realBase = layout === "passthrough" ? (g) => g : layoutProp === void 0 ? dagreBase : layout;
4798
5035
  if (groupedSet.size > 0) {
4799
5036
  const grouped = applyGroupLayout(filteredGraph, {
4800
5037
  groupedSubflowIds: [...groupedSet],
@@ -4805,9 +5042,9 @@ function TracedFlow({
4805
5042
  if (mainChartBox) {
4806
5043
  return wrapInMainChartBox(filteredGraph, { baseLayout: realBase, ...mainChartBox });
4807
5044
  }
4808
- return layout === "passthrough" ? filteredGraph : layout(filteredGraph);
4809
- }, [filteredGraph, layout, groupedSet, mainChartBox]);
4810
- const slice = (0, import_react23.useMemo)(() => {
5045
+ return realBase(filteredGraph);
5046
+ }, [filteredGraph, layout, layoutProp, groupedSet, mainChartBox, measuredSizes]);
5047
+ const slice = (0, import_react25.useMemo)(() => {
4811
5048
  const empty = {
4812
5049
  doneStageIds: /* @__PURE__ */ new Set(),
4813
5050
  activeStageId: null,
@@ -4819,7 +5056,7 @@ function TracedFlow({
4819
5056
  const idx = scrubIndex ?? Math.max(0, overlay.executionOrder.length - 1);
4820
5057
  return aggregateMountStatus(sliceOverlay(overlay, idx), graph, drill.currentSubflowId);
4821
5058
  }, [overlay, scrubIndex, graph, drill.currentSubflowId]);
4822
- const reactFlowNodes = (0, import_react23.useMemo)(
5059
+ const reactFlowNodes = (0, import_react25.useMemo)(
4823
5060
  () => positioned.nodes.map(
4824
5061
  (n) => toStageNodeWithOverlay(
4825
5062
  n,
@@ -4832,13 +5069,13 @@ function TracedFlow({
4832
5069
  ),
4833
5070
  [positioned.nodes, slice, coActiveStageIds]
4834
5071
  );
4835
- const reactFlowEdges = (0, import_react23.useMemo)(
5072
+ const reactFlowEdges = (0, import_react25.useMemo)(
4836
5073
  () => positioned.edges.map(
4837
5074
  (e) => styleEdgeWithOverlay(e, slice.doneStageIds, slice.activeStageId, colors)
4838
5075
  ),
4839
5076
  [positioned.edges, slice, colors]
4840
5077
  );
4841
- const handleNodeClick = (0, import_react23.useCallback)(
5078
+ const handleNodeClick = (0, import_react25.useCallback)(
4842
5079
  (_, node) => {
4843
5080
  const data = node.data ?? {};
4844
5081
  if (data.isSubflow && data.subflowId && !groupedSet.has(data.subflowId)) {
@@ -4848,9 +5085,13 @@ function TracedFlow({
4848
5085
  },
4849
5086
  [drill, onNodeClick, groupedSet]
4850
5087
  );
4851
- const wrapperRef = (0, import_react23.useRef)(null);
4852
- const [rfInstance, setRfInstance] = (0, import_react23.useState)(null);
4853
- useChartAutoRefit(wrapperRef, rfInstance, { refitKey: drill.currentSubflowId });
5088
+ const wrapperRef = (0, import_react25.useRef)(null);
5089
+ const [rfInstance, setRfInstance] = (0, import_react25.useState)(null);
5090
+ useChartAutoRefit(wrapperRef, rfInstance, {
5091
+ // Re-fit on drill AND after the measured-size re-layout settles.
5092
+ refitKey: `${drill.currentSubflowId ?? ""}:${measuredSizes ? "measured" : "estimated"}`,
5093
+ padding: 0.18
5094
+ });
4854
5095
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4855
5096
  "div",
4856
5097
  {
@@ -4873,7 +5114,7 @@ function TracedFlow({
4873
5114
  }
4874
5115
  ),
4875
5116
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4876
- import_react24.ReactFlow,
5117
+ import_react26.ReactFlow,
4877
5118
  {
4878
5119
  nodes: reactFlowNodes,
4879
5120
  edges: reactFlowEdges,
@@ -4882,9 +5123,12 @@ function TracedFlow({
4882
5123
  onNodeClick: handleNodeClick,
4883
5124
  onInit: setRfInstance,
4884
5125
  fitView: true,
5126
+ fitViewOptions: { padding: 0.18 },
5127
+ minZoom: 0.1,
4885
5128
  proOptions: { hideAttribution: true },
4886
5129
  children: [
4887
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react24.Background, { variant: import_react24.BackgroundVariant.Dots, gap: 20, size: 1 }),
5130
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MeasuredNodeSizes, { onSizes: setMeasuredSizes }),
5131
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react26.Background, { variant: import_react26.BackgroundVariant.Dots, gap: 20, size: 1 }),
4888
5132
  children
4889
5133
  ]
4890
5134
  }
@@ -4895,12 +5139,12 @@ function TracedFlow({
4895
5139
  }
4896
5140
 
4897
5141
  // src/components/InspectorPanel/InspectorPanel.tsx
4898
- var import_react26 = require("react");
5142
+ var import_react28 = require("react");
4899
5143
 
4900
5144
  // src/components/DataTracePanel/DataTracePanel.tsx
4901
- var import_react25 = require("react");
5145
+ var import_react27 = require("react");
4902
5146
  var import_jsx_runtime22 = require("react/jsx-runtime");
4903
- var DataTracePanel = (0, import_react25.memo)(function DataTracePanel2({
5147
+ var DataTracePanel = (0, import_react27.memo)(function DataTracePanel2({
4904
5148
  frames,
4905
5149
  selectedStageId,
4906
5150
  onFrameClick,
@@ -4970,7 +5214,7 @@ var DataTracePanel = (0, import_react25.memo)(function DataTracePanel2({
4970
5214
  ))
4971
5215
  ] });
4972
5216
  });
4973
- var DataTraceFrame = (0, import_react25.memo)(function DataTraceFrame2({
5217
+ var DataTraceFrame = (0, import_react27.memo)(function DataTraceFrame2({
4974
5218
  frame,
4975
5219
  isFirst,
4976
5220
  isLast,
@@ -5056,14 +5300,14 @@ var DataTraceFrame = (0, import_react25.memo)(function DataTraceFrame2({
5056
5300
 
5057
5301
  // src/components/InspectorPanel/InspectorPanel.tsx
5058
5302
  var import_jsx_runtime23 = require("react/jsx-runtime");
5059
- var InspectorPanel = (0, import_react26.memo)(function InspectorPanel2({
5303
+ var InspectorPanel = (0, import_react28.memo)(function InspectorPanel2({
5060
5304
  snapshots,
5061
5305
  selectedIndex,
5062
5306
  dataTraceFrames,
5063
5307
  selectedStageId,
5064
5308
  onNavigateToStage
5065
5309
  }) {
5066
- const [tab, setTab] = (0, import_react26.useState)("state");
5310
+ const [tab, setTab] = (0, import_react28.useState)("state");
5067
5311
  const currentSnapshot = snapshots[selectedIndex];
5068
5312
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
5069
5313
  "div",
@@ -5171,9 +5415,9 @@ function TabButton({
5171
5415
  }
5172
5416
 
5173
5417
  // src/components/InsightPanel/InsightPanel.tsx
5174
- var import_react27 = require("react");
5418
+ var import_react29 = require("react");
5175
5419
  var import_jsx_runtime24 = require("react/jsx-runtime");
5176
- var InsightPanel = (0, import_react27.memo)(function InsightPanel2({
5420
+ var InsightPanel = (0, import_react29.memo)(function InsightPanel2({
5177
5421
  insights,
5178
5422
  expandedId,
5179
5423
  mode
@@ -5186,11 +5430,11 @@ var InsightPanel = (0, import_react27.memo)(function InsightPanel2({
5186
5430
  }
5187
5431
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(InsightTabs, { insights, defaultId: expandedId });
5188
5432
  });
5189
- var InsightTabs = (0, import_react27.memo)(function InsightTabs2({
5433
+ var InsightTabs = (0, import_react29.memo)(function InsightTabs2({
5190
5434
  insights,
5191
5435
  defaultId
5192
5436
  }) {
5193
- const [activeId, setActiveId] = (0, import_react27.useState)(defaultId ?? insights[0]?.id);
5437
+ const [activeId, setActiveId] = (0, import_react29.useState)(defaultId ?? insights[0]?.id);
5194
5438
  const active = insights.find((i) => i.id === activeId) ?? insights[0];
5195
5439
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
5196
5440
  "div",
@@ -5237,7 +5481,7 @@ var InsightTabs = (0, import_react27.memo)(function InsightTabs2({
5237
5481
  }
5238
5482
  );
5239
5483
  });
5240
- var InsightGrid = (0, import_react27.memo)(function InsightGrid2({
5484
+ var InsightGrid = (0, import_react29.memo)(function InsightGrid2({
5241
5485
  insights
5242
5486
  }) {
5243
5487
  const cols = insights.length <= 2 ? 1 : 2;
@@ -5302,14 +5546,14 @@ var InsightGrid = (0, import_react27.memo)(function InsightGrid2({
5302
5546
  });
5303
5547
 
5304
5548
  // src/components/CompactTimeline/CompactTimeline.tsx
5305
- var import_react28 = require("react");
5549
+ var import_react30 = require("react");
5306
5550
  var import_jsx_runtime25 = require("react/jsx-runtime");
5307
- var CompactTimeline = (0, import_react28.memo)(function CompactTimeline2({
5551
+ var CompactTimeline = (0, import_react30.memo)(function CompactTimeline2({
5308
5552
  snapshots,
5309
5553
  selectedIndex,
5310
5554
  defaultExpanded = false
5311
5555
  }) {
5312
- const [expanded, setExpanded] = (0, import_react28.useState)(defaultExpanded);
5556
+ const [expanded, setExpanded] = (0, import_react30.useState)(defaultExpanded);
5313
5557
  if (snapshots.length === 0) return null;
5314
5558
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { style: { borderTop: `1px solid ${theme.border}` }, children: [
5315
5559
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
@@ -5394,7 +5638,7 @@ var CompactTimeline = (0, import_react28.memo)(function CompactTimeline2({
5394
5638
 
5395
5639
  // src/components/ExplainableShell/ExplainableShell.tsx
5396
5640
  var import_jsx_runtime26 = require("react/jsx-runtime");
5397
- var HLinePill = (0, import_react29.memo)(function HLinePill2({
5641
+ var HLinePill = (0, import_react31.memo)(function HLinePill2({
5398
5642
  label,
5399
5643
  detail,
5400
5644
  expanded,
@@ -5440,7 +5684,7 @@ var HLinePill = (0, import_react29.memo)(function HLinePill2({
5440
5684
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { style: { flex: 1, height: 1, background: theme.border } })
5441
5685
  ] });
5442
5686
  });
5443
- var VLinePill = (0, import_react29.memo)(function VLinePill2({
5687
+ var VLinePill = (0, import_react31.memo)(function VLinePill2({
5444
5688
  label,
5445
5689
  expanded,
5446
5690
  side = "right",
@@ -5522,9 +5766,9 @@ function KeyedRecorderView({
5522
5766
  snapshots,
5523
5767
  selectedIndex
5524
5768
  }) {
5525
- const [showAggregate, setShowAggregate] = (0, import_react29.useState)(false);
5526
- const detected = (0, import_react29.useMemo)(() => detectKeyedSteps(data), [data]);
5527
- const visibleKeys = (0, import_react29.useMemo)(() => {
5769
+ const [showAggregate, setShowAggregate] = (0, import_react31.useState)(false);
5770
+ const detected = (0, import_react31.useMemo)(() => detectKeyedSteps(data), [data]);
5771
+ const visibleKeys = (0, import_react31.useMemo)(() => {
5528
5772
  const keys = /* @__PURE__ */ new Set();
5529
5773
  for (let i = 0; i <= selectedIndex && i < snapshots.length; i++) {
5530
5774
  const snap = snapshots[i];
@@ -5634,7 +5878,7 @@ function KeyedRecorderView({
5634
5878
  ] })
5635
5879
  ] });
5636
5880
  }
5637
- var DetailsContent = (0, import_react29.memo)(function DetailsContent2({
5881
+ var DetailsContent = (0, import_react31.memo)(function DetailsContent2({
5638
5882
  snapshots,
5639
5883
  selectedIndex,
5640
5884
  narrativeEntries,
@@ -5655,9 +5899,9 @@ var DetailsContent = (0, import_react29.memo)(function DetailsContent2({
5655
5899
  }
5656
5900
  ];
5657
5901
  const allViews = [...builtInViews, ...extraViews ?? []];
5658
- const [activeViewId, setActiveViewId] = (0, import_react29.useState)(allViews[0]?.id ?? "memory");
5902
+ const [activeViewId, setActiveViewId] = (0, import_react31.useState)(allViews[0]?.id ?? "memory");
5659
5903
  const viewIds = allViews.map((v2) => v2.id).join(",");
5660
- (0, import_react29.useEffect)(() => {
5904
+ (0, import_react31.useEffect)(() => {
5661
5905
  if (!allViews.find((v2) => v2.id === activeViewId)) {
5662
5906
  setActiveViewId(allViews[0]?.id ?? "memory");
5663
5907
  }
@@ -5764,7 +6008,7 @@ function buildDataTrace(commitLog, targetRuntimeStageId, maxDepth = 10) {
5764
6008
  }
5765
6009
  return frames;
5766
6010
  }
5767
- var RightPanel = (0, import_react29.memo)(function RightPanel2({
6011
+ var RightPanel = (0, import_react31.memo)(function RightPanel2({
5768
6012
  mode,
5769
6013
  onModeChange,
5770
6014
  snapshots,
@@ -5871,7 +6115,7 @@ function ExplainableShell({
5871
6115
  className,
5872
6116
  style
5873
6117
  }) {
5874
- const derivedFromRuntime = (0, import_react29.useMemo)(() => {
6118
+ const derivedFromRuntime = (0, import_react31.useMemo)(() => {
5875
6119
  if (!runtimeSnapshot) return null;
5876
6120
  try {
5877
6121
  const snaps = toVisualizationSnapshots(runtimeSnapshot, narrativeEntries);
@@ -5882,7 +6126,7 @@ function ExplainableShell({
5882
6126
  }, [runtimeSnapshot, narrativeEntries]);
5883
6127
  const snapshots = snapshotsProp ?? derivedFromRuntime?.snapshots ?? [];
5884
6128
  const resultData = resultDataProp ?? derivedFromRuntime?.resultData ?? null;
5885
- const tracedFlowRenderer = (0, import_react29.useMemo)(() => {
6129
+ const tracedFlowRenderer = (0, import_react31.useMemo)(() => {
5886
6130
  if (!traceGraph) return void 0;
5887
6131
  return ({ selectedIndex, snapshots: snapshots2, onNodeClick }) => {
5888
6132
  const activeRsid = snapshots2[selectedIndex]?.runtimeStageId;
@@ -5911,10 +6155,10 @@ function ExplainableShell({
5911
6155
  const leftLabel = panelLabels?.topology ?? "Topology";
5912
6156
  const rightLabel = panelLabels?.details ?? "Details";
5913
6157
  const bottomLabel = panelLabels?.timeline ?? "Timeline";
5914
- const shellRef = (0, import_react29.useRef)(null);
5915
- const [isNarrow, setIsNarrow] = (0, import_react29.useState)(false);
5916
- const [isMedium, setIsMedium] = (0, import_react29.useState)(false);
5917
- (0, import_react29.useEffect)(() => {
6158
+ const shellRef = (0, import_react31.useRef)(null);
6159
+ const [isNarrow, setIsNarrow] = (0, import_react31.useState)(false);
6160
+ const [isMedium, setIsMedium] = (0, import_react31.useState)(false);
6161
+ (0, import_react31.useEffect)(() => {
5918
6162
  const el = shellRef.current;
5919
6163
  if (!el) return;
5920
6164
  const ro = new ResizeObserver(([entry]) => {
@@ -5926,14 +6170,14 @@ function ExplainableShell({
5926
6170
  ro.observe(el);
5927
6171
  return () => ro.disconnect();
5928
6172
  }, []);
5929
- const autoRecorderViews = (0, import_react29.useMemo)(() => {
6173
+ const autoRecorderViews = (0, import_react31.useMemo)(() => {
5930
6174
  const recorders = runtimeSnapshot?.recorders;
5931
6175
  if (!recorders?.length) return [];
5932
6176
  const explicitIds = new Set((recorderViews ?? []).map((v2) => v2.id));
5933
6177
  return recorders.filter((r) => !explicitIds.has(r.id)).map((r) => ({ id: r.id, name: r.name, description: r.description, preferredOperation: r.preferredOperation, data: r.data }));
5934
6178
  }, [runtimeSnapshot, recorderViews]);
5935
6179
  const hasNarrative = !!narrativeEntries?.length;
5936
- const allTabs = (0, import_react29.useMemo)(() => {
6180
+ const allTabs = (0, import_react31.useMemo)(() => {
5937
6181
  const tabs2 = [
5938
6182
  { id: "result", name: "Result", description: "Final output and console logs" },
5939
6183
  { id: "memory", name: "Memory", description: "Accumulator \u2014 progressive shared state at each stage" }
@@ -5952,38 +6196,38 @@ function ExplainableShell({
5952
6196
  }, [hasNarrative, recorderViews, autoRecorderViews, hideTabsProp]);
5953
6197
  const validTabIds = new Set(allTabs.map((t) => t.id));
5954
6198
  const resolvedDefault = defaultTab && validTabIds.has(defaultTab) ? defaultTab : allTabs[0]?.id ?? "result";
5955
- const [activeTab, setActiveTab] = (0, import_react29.useState)(resolvedDefault);
5956
- const [snapshotIdx, setSnapshotIdx] = (0, import_react29.useState)(0);
5957
- const [drillDownStack, setDrillDownStack] = (0, import_react29.useState)([]);
5958
- const [rightExpanded, setRightExpanded] = (0, import_react29.useState)(defaultExpanded?.details ?? true);
5959
- const [rightPanelMode, setRightPanelMode] = (0, import_react29.useState)("insights");
5960
- const [leftExpanded, setLeftExpanded] = (0, import_react29.useState)(defaultExpanded?.topology ?? false);
5961
- const [timelineExpanded, setTimelineExpanded] = (0, import_react29.useState)(defaultExpanded?.timeline ?? false);
5962
- (0, import_react29.useEffect)(() => {
6199
+ const [activeTab, setActiveTab] = (0, import_react31.useState)(resolvedDefault);
6200
+ const [snapshotIdx, setSnapshotIdx] = (0, import_react31.useState)(0);
6201
+ const [drillDownStack, setDrillDownStack] = (0, import_react31.useState)([]);
6202
+ const [rightExpanded, setRightExpanded] = (0, import_react31.useState)(defaultExpanded?.details ?? true);
6203
+ const [rightPanelMode, setRightPanelMode] = (0, import_react31.useState)("insights");
6204
+ const [leftExpanded, setLeftExpanded] = (0, import_react31.useState)(defaultExpanded?.topology ?? false);
6205
+ const [timelineExpanded, setTimelineExpanded] = (0, import_react31.useState)(defaultExpanded?.timeline ?? false);
6206
+ (0, import_react31.useEffect)(() => {
5963
6207
  if (isNarrow) {
5964
6208
  setLeftExpanded(false);
5965
6209
  setRightExpanded(false);
5966
6210
  setTimelineExpanded(false);
5967
6211
  }
5968
6212
  }, [isNarrow]);
5969
- const triggerReflow = (0, import_react29.useCallback)(() => {
6213
+ const triggerReflow = (0, import_react31.useCallback)(() => {
5970
6214
  requestAnimationFrame(() => window.dispatchEvent(new Event("resize")));
5971
6215
  setTimeout(() => window.dispatchEvent(new Event("resize")), 320);
5972
6216
  }, []);
5973
- const toggleLeft = (0, import_react29.useCallback)((v2) => {
6217
+ const toggleLeft = (0, import_react31.useCallback)((v2) => {
5974
6218
  setLeftExpanded(v2);
5975
6219
  triggerReflow();
5976
6220
  }, [triggerReflow]);
5977
- const toggleRight = (0, import_react29.useCallback)((v2) => {
6221
+ const toggleRight = (0, import_react31.useCallback)((v2) => {
5978
6222
  setRightExpanded(v2);
5979
6223
  triggerReflow();
5980
6224
  }, [triggerReflow]);
5981
- const toggleTimeline = (0, import_react29.useCallback)(() => {
6225
+ const toggleTimeline = (0, import_react31.useCallback)(() => {
5982
6226
  setTimelineExpanded((p) => !p);
5983
6227
  triggerReflow();
5984
6228
  }, [triggerReflow]);
5985
6229
  const isInSubflow = drillDownStack.length > 0;
5986
- const currentLevel = (0, import_react29.useMemo)(() => {
6230
+ const currentLevel = (0, import_react31.useMemo)(() => {
5987
6231
  if (drillDownStack.length > 0) {
5988
6232
  const top = drillDownStack[drillDownStack.length - 1];
5989
6233
  return { spec: top.spec, snapshots: top.snapshots };
@@ -5994,30 +6238,30 @@ function ExplainableShell({
5994
6238
  const activeSpec = currentLevel.spec;
5995
6239
  const safeIdx = activeSnapshots.length > 0 ? Math.max(0, Math.min(snapshotIdx, activeSnapshots.length - 1)) : 0;
5996
6240
  const activeNarrativeEntries = isInSubflow ? void 0 : narrativeEntries;
5997
- const breadcrumbs = (0, import_react29.useMemo)(() => {
6241
+ const breadcrumbs = (0, import_react31.useMemo)(() => {
5998
6242
  const root = { label: title || "Flowchart", spec, description: spec?.description };
5999
6243
  return [root, ...drillDownStack.map((e) => ({ label: e.label, spec: e.spec, description: void 0 }))];
6000
6244
  }, [spec, title, drillDownStack]);
6001
- const showTreeSidebar = (0, import_react29.useMemo)(() => {
6245
+ const showTreeSidebar = (0, import_react31.useMemo)(() => {
6002
6246
  if (traceGraph?.nodes?.length) {
6003
6247
  return traceGraph.nodes.some((n) => n.data?.isSubflow === true);
6004
6248
  }
6005
6249
  return !!spec && hasSubflowNodes(spec);
6006
6250
  }, [traceGraph, spec]);
6007
- const rootOverlay = (0, import_react29.useMemo)(() => {
6251
+ const rootOverlay = (0, import_react31.useMemo)(() => {
6008
6252
  if (isInSubflow || !snapshots.length) return { activeStage: void 0, doneStages: void 0 };
6009
6253
  const doneStages = new Set(snapshots.slice(0, safeIdx).map((s) => s.stageLabel));
6010
6254
  const activeStage = snapshots[safeIdx]?.stageLabel ?? null;
6011
6255
  return { activeStage, doneStages };
6012
6256
  }, [isInSubflow, snapshots, safeIdx]);
6013
- const handleTabChange = (0, import_react29.useCallback)((tab) => {
6257
+ const handleTabChange = (0, import_react31.useCallback)((tab) => {
6014
6258
  setActiveTab(tab);
6015
6259
  setDrillDownStack([]);
6016
6260
  }, []);
6017
- const handleSnapshotChange = (0, import_react29.useCallback)((idx) => {
6261
+ const handleSnapshotChange = (0, import_react31.useCallback)((idx) => {
6018
6262
  if (typeof idx === "number") setSnapshotIdx(idx);
6019
6263
  }, []);
6020
- const handleDrillDown = (0, import_react29.useCallback)(
6264
+ const handleDrillDown = (0, import_react31.useCallback)(
6021
6265
  (nodeName) => {
6022
6266
  if (!activeSpec) return;
6023
6267
  const entry = resolveSubflowLevel(activeSpec, activeSnapshots, nodeName, narrativeEntries);
@@ -6028,14 +6272,14 @@ function ExplainableShell({
6028
6272
  },
6029
6273
  [activeSpec, activeSnapshots, narrativeEntries, snapshotIdx]
6030
6274
  );
6031
- const handleBreadcrumbNavigate = (0, import_react29.useCallback)((level) => {
6275
+ const handleBreadcrumbNavigate = (0, import_react31.useCallback)((level) => {
6032
6276
  setDrillDownStack((prev) => {
6033
6277
  const popped = level === 0 ? prev[0] : prev[level];
6034
6278
  if (popped) setSnapshotIdx(popped.parentSnapshotIdx);
6035
6279
  return level === 0 ? [] : prev.slice(0, level);
6036
6280
  });
6037
6281
  }, []);
6038
- const handleNodeClick = (0, import_react29.useCallback)(
6282
+ const handleNodeClick = (0, import_react31.useCallback)(
6039
6283
  (indexOrId) => {
6040
6284
  if (typeof indexOrId === "number") {
6041
6285
  setSnapshotIdx(indexOrId);
@@ -6053,7 +6297,7 @@ function ExplainableShell({
6053
6297
  },
6054
6298
  [activeSpec, activeSnapshots, handleDrillDown]
6055
6299
  );
6056
- const handleTreeNodeSelect = (0, import_react29.useCallback)(
6300
+ const handleTreeNodeSelect = (0, import_react31.useCallback)(
6057
6301
  (name, isSubflow) => {
6058
6302
  if (isSubflow && spec) {
6059
6303
  setDrillDownStack([]);
@@ -6088,7 +6332,7 @@ function ExplainableShell({
6088
6332
  ] });
6089
6333
  }
6090
6334
  const showTopology = !!effectiveRenderFlowchart && !!activeSpec;
6091
- const detailsContent = (0, import_react29.useMemo)(() => {
6335
+ const detailsContent = (0, import_react31.useMemo)(() => {
6092
6336
  if (activeTab === "result") {
6093
6337
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ResultPanel, { data: resultData ?? null, logs, hideConsole, size });
6094
6338
  }
@@ -6270,7 +6514,7 @@ function ExplainableShell({
6270
6514
 
6271
6515
  // src/components/TraceViewer/TraceViewer.tsx
6272
6516
  var React = __toESM(require("react"), 1);
6273
- var import_react30 = require("react");
6517
+ var import_react32 = require("react");
6274
6518
  var import_jsx_runtime27 = require("react/jsx-runtime");
6275
6519
  function parseTrace(input) {
6276
6520
  if (input == null) {
@@ -6334,11 +6578,11 @@ function TraceViewer({
6334
6578
  recorderViews,
6335
6579
  renderFlowchart
6336
6580
  }) {
6337
- const parsed = (0, import_react30.useMemo)(() => parseTrace(trace), [trace]);
6581
+ const parsed = (0, import_react32.useMemo)(() => parseTrace(trace), [trace]);
6338
6582
  React.useEffect(() => {
6339
6583
  if (!parsed.ok && onError) onError(parsed.error);
6340
6584
  }, [parsed, onError]);
6341
- const snapshots = (0, import_react30.useMemo)(() => {
6585
+ const snapshots = (0, import_react32.useMemo)(() => {
6342
6586
  if (!parsed.ok || !parsed.trace.snapshot) return [];
6343
6587
  try {
6344
6588
  return toVisualizationSnapshots(