footprint-explainable-ui 0.21.0 → 0.23.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/README.md CHANGED
@@ -362,6 +362,22 @@ Strip all built-in styles for full CSS control. Components render semantic `data
362
362
 
363
363
  ---
364
364
 
365
+ ## Golden-Trace Fixtures (contributors)
366
+
367
+ The pipeline (structure/runtime translators, dagre layout, snapshot adapter,
368
+ narrative sync) is pinned against **real footprintjs engine output**, not
369
+ hand-built mocks. `test/fixtures/golden/` holds recorded traces from 4
370
+ representative charts (linear+decider, subflow+loop, parallel fork,
371
+ pause/resume); `test/golden/goldenTraces.test.ts` replays them through the full
372
+ pipeline and snapshot-asserts the outputs in `test/golden/__snapshots__/`.
373
+
374
+ - **Engine shape changed** (new footprintjs): `npm i -D --save-exact footprintjs@<version> && npm run fixtures:regen`. The generator runs every chart twice and fails on any nondeterminism.
375
+ - **Pipeline output changed intentionally** (eui edit): `npx vitest run test/golden -u`, then review the snapshot diff.
376
+ - `test/fixtures/golden/manifest.json` records the footprintjs version the fixtures were recorded with.
377
+
378
+ `footprintjs` is a devDependency used ONLY by the generator — the published
379
+ library still has zero footprintjs dependency (it consumes plain JSON shapes).
380
+
365
381
  ## License
366
382
 
367
383
  MIT
@@ -362,7 +362,7 @@ var StageNode = (0, import_react3.memo)(function StageNode2({
362
362
  const restingShadow = isHero ? `0 0 10px color-mix(in srgb, ${theme.primary} 22%, transparent)` : `0 2px 8px rgba(0,0,0,0.15)`;
363
363
  const bg = active ? theme.primary : done ? theme.success : error ? theme.error : restingBg;
364
364
  const borderColor = active ? theme.primary : done ? theme.success : error ? theme.error : restingBorder;
365
- const shadow = active ? `0 0 16px color-mix(in srgb, ${theme.primary} 40%, transparent)` : done ? `0 0 8px color-mix(in srgb, ${theme.success} 20%, transparent)` : error ? `0 0 12px color-mix(in srgb, ${theme.error} 30%, transparent)` : restingShadow;
365
+ const shadow = active ? `0 0 22px color-mix(in srgb, ${theme.primary} 55%, transparent)` : done ? `0 0 8px color-mix(in srgb, ${theme.success} 20%, transparent)` : error ? `0 0 12px color-mix(in srgb, ${theme.error} 30%, transparent)` : restingShadow;
366
366
  const textColor = active || done || error ? "#fff" : theme.textPrimary;
367
367
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
368
368
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Top, style: { opacity: 0 } }),
@@ -445,6 +445,26 @@ var StageNode = (0, import_react3.memo)(function StageNode2({
445
445
  }
446
446
  }
447
447
  ),
448
+ active && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
449
+ "div",
450
+ {
451
+ style: {
452
+ position: "absolute",
453
+ top: -9,
454
+ right: -8,
455
+ zIndex: 11,
456
+ background: theme.warning,
457
+ color: "#1a1a1a",
458
+ fontSize: 9,
459
+ fontWeight: 800,
460
+ letterSpacing: 0.6,
461
+ padding: "2px 6px",
462
+ borderRadius: 10,
463
+ boxShadow: `0 0 10px color-mix(in srgb, ${theme.warning} 60%, transparent)`
464
+ },
465
+ children: "NOW"
466
+ }
467
+ ),
448
468
  isDecider ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: 120, height: 72 }, children: [
449
469
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
450
470
  "div",
@@ -1455,6 +1475,19 @@ function createMainChartBoxLayout(opts) {
1455
1475
 
1456
1476
  // src/components/LoopBackEdge/LoopBackEdge.tsx
1457
1477
  var import_jsx_runtime6 = require("react/jsx-runtime");
1478
+ var LOOP_DASH = "5 5";
1479
+ var LOOP_STROKE_OPACITY_CAP = 0.55;
1480
+ var LOOP_STROKE_WIDTH = 1.5;
1481
+ function softenLoopStyle(style) {
1482
+ const passedStrokeOpacity = typeof style?.strokeOpacity === "number" ? style.strokeOpacity : 1;
1483
+ return {
1484
+ ...style,
1485
+ strokeDasharray: style?.strokeDasharray ?? LOOP_DASH,
1486
+ strokeOpacity: Math.min(passedStrokeOpacity, LOOP_STROKE_OPACITY_CAP),
1487
+ strokeWidth: LOOP_STROKE_WIDTH
1488
+ };
1489
+ }
1490
+ var LOOP_CORNER_RADIUS = 28;
1458
1491
  function rightEdge(node) {
1459
1492
  return node.internals.positionAbsolute.x + (node.measured.width ?? 0);
1460
1493
  }
@@ -1475,11 +1508,21 @@ function LoopBackEdge({ id, source, target, markerEnd, style }) {
1475
1508
  return loopBackPath(
1476
1509
  { right: rightEdge(src), centerY: centerY(src) },
1477
1510
  { right: rightEdge(tgt), centerY: centerY(tgt) },
1478
- laneX
1511
+ laneX,
1512
+ LOOP_CORNER_RADIUS
1479
1513
  );
1480
1514
  });
1481
1515
  if (!path) return null;
1482
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react8.BaseEdge, { id, path, markerEnd, style, "aria-label": "Loop back" });
1516
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1517
+ import_react8.BaseEdge,
1518
+ {
1519
+ id,
1520
+ path,
1521
+ markerEnd,
1522
+ style: softenLoopStyle(style),
1523
+ "aria-label": "Loop back"
1524
+ }
1525
+ );
1483
1526
  }
1484
1527
 
1485
1528
  // src/components/SmartStepEdge/SmartStepEdge.tsx
@@ -2078,10 +2121,9 @@ function aggregateMountStatus(slice, graph, currentSubflowId) {
2078
2121
  const members = graph.nodes.filter((n) => n.data?.subflowOf === sfId);
2079
2122
  if (members.length === 0) continue;
2080
2123
  const anyActive = members.some((m) => m.id === slice.activeStageId);
2081
- const anyDone = members.some((m) => slice.doneStageIds.has(m.id));
2082
2124
  const allDone = members.every((m) => slice.doneStageIds.has(m.id));
2083
2125
  if (allDone) doneIds.add(mount.id);
2084
- else if ((anyActive || anyDone) && currentSubflowId === null) {
2126
+ else if (anyActive && currentSubflowId === null) {
2085
2127
  activeId = mount.id;
2086
2128
  }
2087
2129
  }
@@ -2124,6 +2166,7 @@ var import_react14 = require("react");
2124
2166
  function useChartAutoRefit(wrapperRef, rfInstance, options = {}) {
2125
2167
  const duration = options.duration ?? 200;
2126
2168
  const padding2 = options.padding ?? 0.1;
2169
+ const refitKey = options.refitKey;
2127
2170
  (0, import_react14.useEffect)(() => {
2128
2171
  const el = wrapperRef.current;
2129
2172
  if (!el || !rfInstance) return;
@@ -2143,6 +2186,19 @@ function useChartAutoRefit(wrapperRef, rfInstance, options = {}) {
2143
2186
  cancelAnimationFrame(raf);
2144
2187
  };
2145
2188
  }, [rfInstance, wrapperRef, duration, padding2]);
2189
+ (0, import_react14.useEffect)(() => {
2190
+ if (!rfInstance) return;
2191
+ let raf2 = 0;
2192
+ const raf1 = requestAnimationFrame(() => {
2193
+ raf2 = requestAnimationFrame(() => {
2194
+ rfInstance.fitView({ duration, padding: padding2 });
2195
+ });
2196
+ });
2197
+ return () => {
2198
+ cancelAnimationFrame(raf1);
2199
+ cancelAnimationFrame(raf2);
2200
+ };
2201
+ }, [rfInstance, refitKey, duration, padding2]);
2146
2202
  }
2147
2203
 
2148
2204
  // src/components/FlowchartView/SubflowBreadcrumbBar.tsx
@@ -2476,7 +2532,7 @@ function TracedFlow({
2476
2532
  );
2477
2533
  const wrapperRef = (0, import_react16.useRef)(null);
2478
2534
  const [rfInstance, setRfInstance] = (0, import_react16.useState)(null);
2479
- useChartAutoRefit(wrapperRef, rfInstance);
2535
+ useChartAutoRefit(wrapperRef, rfInstance, { refitKey: drill.currentSubflowId });
2480
2536
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2481
2537
  "div",
2482
2538
  {