footprint-explainable-ui 0.25.1 → 0.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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 useEffect10 } 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,
@@ -3453,6 +3453,16 @@ function createDagreTraceLayout(options = {}) {
3453
3453
  return (graph) => dagreTraceLayout(graph, options);
3454
3454
  }
3455
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
+
3456
3466
  // src/components/FlowchartView/_internal/snapLinearSuccessors.ts
3457
3467
  function snapLinearSuccessors(graph, options = {}) {
3458
3468
  if (graph.nodes.length === 0) return graph;
@@ -3554,36 +3564,79 @@ function centerForkParents(graph, options = {}) {
3554
3564
  }
3555
3565
  return minX <= maxX ? Math.max(minX, Math.min(maxX, desiredX)) : x0;
3556
3566
  };
3567
+ const evenFanKids = (forkCenter, kids) => {
3568
+ if (kids.length < 2) return;
3569
+ const sorted = [...kids].sort((a, b) => centerX(a) - centerX(b));
3570
+ let gap = 0;
3571
+ for (let i = 0; i < sorted.length - 1; i++) {
3572
+ gap = Math.max(gap, width.get(sorted[i]) / 2 + nodeSep + width.get(sorted[i + 1]) / 2);
3573
+ }
3574
+ const mid = (sorted.length - 1) / 2;
3575
+ for (let i = 0; i < sorted.length; i++) {
3576
+ workingX.set(sorted[i], forkCenter + (i - mid) * gap - width.get(sorted[i]) / 2);
3577
+ }
3578
+ };
3557
3579
  const order = [...graph.nodes].sort(
3558
3580
  (a, b) => b.position.y - a.position.y || a.position.x - b.position.x || a.id.localeCompare(b.id)
3559
3581
  );
3560
3582
  for (const n of order) {
3561
- if ((outDegree.get(n.id) ?? 0) < 2) continue;
3562
- if ((inDegree.get(n.id) ?? 0) > 1) continue;
3563
- const kids = (childrenOf.get(n.id) ?? []).filter(
3583
+ const outD = outDegree.get(n.id) ?? 0;
3584
+ const inD = inDegree.get(n.id) ?? 0;
3585
+ const isFork = outD >= 2 && inD <= 1;
3586
+ const isMerge = inD >= 2 && outD <= 1;
3587
+ if (!isFork && !isMerge) continue;
3588
+ const kin = ((isFork ? childrenOf.get(n.id) : predsOf.get(n.id)) ?? []).filter(
3564
3589
  (k) => byId.get(k)?.parentId === n.parentId
3565
3590
  // same compound only
3566
3591
  );
3567
- if (kids.length < 2) continue;
3568
- const centers = kids.map(centerX);
3592
+ if (kin.length < 2) continue;
3593
+ const centers = kin.map(centerX);
3569
3594
  const wN = width.get(n.id);
3570
3595
  const span = (Math.min(...centers) + Math.max(...centers)) / 2;
3571
3596
  workingX.set(n.id, clampX(n.id, span - wN / 2));
3597
+ if (isFork) {
3598
+ const succSets = kin.map((k) => childrenOf.get(k) ?? []);
3599
+ const isDiamond = kin.length >= 2 && succSets[0].some((s) => succSets.every((ss) => ss.includes(s)));
3600
+ if (isDiamond) evenFanKids(centerX(n.id), kin);
3601
+ }
3602
+ const stepOf = isFork ? predsOf : childrenOf;
3572
3603
  let curId = n.id;
3573
3604
  const walked = /* @__PURE__ */ new Set([curId]);
3574
3605
  for (; ; ) {
3575
- const ps = predsOf.get(curId);
3576
- if (!ps || ps.length !== 1) break;
3577
- const p = ps[0];
3578
- if (walked.has(p)) break;
3579
- if ((outDegree.get(p) ?? 0) !== 1) break;
3580
- if ((inDegree.get(p) ?? 0) > 1) break;
3581
- if (byId.get(p)?.parentId !== byId.get(curId)?.parentId) break;
3582
- workingX.set(p, clampX(p, centerX(curId) - width.get(p) / 2));
3583
- walked.add(p);
3584
- curId = p;
3606
+ const nexts = stepOf.get(curId);
3607
+ if (!nexts || nexts.length !== 1) break;
3608
+ const m = nexts[0];
3609
+ if (walked.has(m)) break;
3610
+ if ((outDegree.get(m) ?? 0) > 1) break;
3611
+ if ((inDegree.get(m) ?? 0) > 1) break;
3612
+ if (byId.get(m)?.parentId !== byId.get(curId)?.parentId) break;
3613
+ workingX.set(m, clampX(m, centerX(curId) - width.get(m) / 2));
3614
+ walked.add(m);
3615
+ curId = m;
3585
3616
  }
3586
3617
  }
3618
+ for (const n of order) {
3619
+ const outD = outDegree.get(n.id) ?? 0;
3620
+ const inD = inDegree.get(n.id) ?? 0;
3621
+ if (!(outD >= 2 && inD <= 1)) continue;
3622
+ const kids = (childrenOf.get(n.id) ?? []).filter(
3623
+ (k) => byId.get(k)?.parentId === n.parentId
3624
+ );
3625
+ if (kids.length < 2) continue;
3626
+ const succSets = kids.map((k) => childrenOf.get(k) ?? []);
3627
+ const isDiamond = succSets[0].some((s) => succSets.every((ss) => ss.includes(s)));
3628
+ if (isDiamond) continue;
3629
+ const ps = predsOf.get(n.id);
3630
+ if (!ps || ps.length !== 1) continue;
3631
+ const pred = ps[0];
3632
+ if ((outDegree.get(pred) ?? 0) !== 1) continue;
3633
+ if (byId.get(pred)?.parentId !== byId.get(n.id)?.parentId) continue;
3634
+ const before = centerX(n.id);
3635
+ workingX.set(n.id, clampX(n.id, centerX(pred) - width.get(n.id) / 2));
3636
+ const delta = centerX(n.id) - before;
3637
+ if (delta === 0) continue;
3638
+ for (const k of kids) workingX.set(k, clampX(k, workingX.get(k) + delta));
3639
+ }
3587
3640
  const nodes = graph.nodes.map(
3588
3641
  (n) => workingX.get(n.id) === n.position.x ? n : { ...n, position: { x: workingX.get(n.id), y: n.position.y } }
3589
3642
  );
@@ -4898,6 +4951,13 @@ function TracedFlow({
4898
4951
  style
4899
4952
  }) {
4900
4953
  const layout = layoutProp ?? dagreTraceLayout;
4954
+ useEffect10(() => {
4955
+ if (layoutProp === dagreTraceLayout) {
4956
+ devWarn(
4957
+ () => "[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."
4958
+ );
4959
+ }
4960
+ }, [layoutProp]);
4901
4961
  const colors = useMemo10(
4902
4962
  () => ({ ...DEFAULT_COLORS, ...colorOverrides ?? {} }),
4903
4963
  [colorOverrides]
@@ -5813,7 +5873,7 @@ var DetailsContent = memo8(function DetailsContent2({
5813
5873
  const allViews = [...builtInViews, ...extraViews ?? []];
5814
5874
  const [activeViewId, setActiveViewId] = useState14(allViews[0]?.id ?? "memory");
5815
5875
  const viewIds = allViews.map((v2) => v2.id).join(",");
5816
- useEffect10(() => {
5876
+ useEffect11(() => {
5817
5877
  if (!allViews.find((v2) => v2.id === activeViewId)) {
5818
5878
  setActiveViewId(allViews[0]?.id ?? "memory");
5819
5879
  }
@@ -6070,7 +6130,7 @@ function ExplainableShell({
6070
6130
  const shellRef = useRef9(null);
6071
6131
  const [isNarrow, setIsNarrow] = useState14(false);
6072
6132
  const [isMedium, setIsMedium] = useState14(false);
6073
- useEffect10(() => {
6133
+ useEffect11(() => {
6074
6134
  const el = shellRef.current;
6075
6135
  if (!el) return;
6076
6136
  const ro = new ResizeObserver(([entry]) => {
@@ -6115,7 +6175,7 @@ function ExplainableShell({
6115
6175
  const [rightPanelMode, setRightPanelMode] = useState14("insights");
6116
6176
  const [leftExpanded, setLeftExpanded] = useState14(defaultExpanded?.topology ?? false);
6117
6177
  const [timelineExpanded, setTimelineExpanded] = useState14(defaultExpanded?.timeline ?? false);
6118
- useEffect10(() => {
6178
+ useEffect11(() => {
6119
6179
  if (isNarrow) {
6120
6180
  setLeftExpanded(false);
6121
6181
  setRightExpanded(false);