footprint-explainable-ui 0.26.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -625,10 +625,17 @@ interface RecorderView {
625
625
  }) => React.ReactNode;
626
626
  }
627
627
  /**
628
- * The Trace flowchart's two-colour theme (footprintjs level). `mode` selects
629
- * the neutral base (unvisited / edges) for dark or light; `visited` and
630
- * `current` are the two semantic colours. All optional sensible per-mode
631
- * defaults are used for anything omitted.
628
+ * The Trace flowchart's two-colour theme (footprintjs level).
629
+ *
630
+ * `mode` is the COARSE switch: it applies eui's full light or dark preset
631
+ * (`coolLight` / `coolDark`) as `--fp-*` variables on the shell root, so the
632
+ * ENTIRE shell — canvas, panels, nodes, text, borders — follows dark/light from
633
+ * this one field. You do NOT need to hand-set `--fp-*` yourself. (`--fp-*`
634
+ * remains available as a fine escape hatch for individual token overrides.)
635
+ *
636
+ * `visited` and `current` are the two semantic node colours, layered on top of
637
+ * the mode base. All optional — sensible per-mode defaults are used for anything
638
+ * omitted.
632
639
  */
633
640
  interface TraceTheme {
634
641
  mode?: "dark" | "light";
@@ -676,11 +683,11 @@ interface ExplainableShellProps extends BaseComponentProps {
676
683
  runtimeOverlay?: RuntimeOverlay | null;
677
684
  /**
678
685
  * Trace flowchart theme — the footprintjs-LEVEL **two-colour** scheme:
679
- * `visited` (executed nodes) + `current` (the cursor node). `mode` picks the
680
- * neutral base (unvisited nodes / edges follow dark/light; the background is
681
- * transparent, so it inherits your container). Colours are optional — omit to
682
- * use the per-mode defaults. The agent-semantic three-colour theme belongs to
683
- * `<Lens>`, not here.
686
+ * `visited` (executed nodes) + `current` (the cursor node). `mode` is the
687
+ * coarse light/dark switch it applies eui's full preset to the whole shell,
688
+ * so you pass one word instead of a wall of `--fp-*` vars. Colours are optional
689
+ * — omit to use the per-mode defaults. The agent-semantic three-colour theme
690
+ * belongs to `<Lens>`, not here.
684
691
  */
685
692
  traceTheme?: TraceTheme;
686
693
  title?: string;
@@ -1075,6 +1082,8 @@ interface DataTracePanelProps {
1075
1082
  onFrameClick?: (runtimeStageId: string) => void;
1076
1083
  /** Optional: stage name for the "tracing from" header. */
1077
1084
  fromStageName?: string;
1085
+ /** Optional honesty line rendered under the header (⚠-style). */
1086
+ note?: string;
1078
1087
  }
1079
1088
  /**
1080
1089
  * Render the backward causal chain as a stack trace.
@@ -1088,6 +1097,8 @@ interface InspectorPanelProps {
1088
1097
  selectedIndex: number;
1089
1098
  /** Causal chain frames for the selected node (empty = no trace available). */
1090
1099
  dataTraceFrames: CausalFrame[];
1100
+ /** Optional honesty line for the Data Trace tab (e.g. reads not recorded). */
1101
+ dataTraceNote?: string;
1091
1102
  /** Currently selected runtimeStageId. */
1092
1103
  selectedStageId?: string;
1093
1104
  /** Navigate to a stage when clicking a Data Trace frame. */
package/dist/index.d.ts CHANGED
@@ -625,10 +625,17 @@ interface RecorderView {
625
625
  }) => React.ReactNode;
626
626
  }
627
627
  /**
628
- * The Trace flowchart's two-colour theme (footprintjs level). `mode` selects
629
- * the neutral base (unvisited / edges) for dark or light; `visited` and
630
- * `current` are the two semantic colours. All optional sensible per-mode
631
- * defaults are used for anything omitted.
628
+ * The Trace flowchart's two-colour theme (footprintjs level).
629
+ *
630
+ * `mode` is the COARSE switch: it applies eui's full light or dark preset
631
+ * (`coolLight` / `coolDark`) as `--fp-*` variables on the shell root, so the
632
+ * ENTIRE shell — canvas, panels, nodes, text, borders — follows dark/light from
633
+ * this one field. You do NOT need to hand-set `--fp-*` yourself. (`--fp-*`
634
+ * remains available as a fine escape hatch for individual token overrides.)
635
+ *
636
+ * `visited` and `current` are the two semantic node colours, layered on top of
637
+ * the mode base. All optional — sensible per-mode defaults are used for anything
638
+ * omitted.
632
639
  */
633
640
  interface TraceTheme {
634
641
  mode?: "dark" | "light";
@@ -676,11 +683,11 @@ interface ExplainableShellProps extends BaseComponentProps {
676
683
  runtimeOverlay?: RuntimeOverlay | null;
677
684
  /**
678
685
  * Trace flowchart theme — the footprintjs-LEVEL **two-colour** scheme:
679
- * `visited` (executed nodes) + `current` (the cursor node). `mode` picks the
680
- * neutral base (unvisited nodes / edges follow dark/light; the background is
681
- * transparent, so it inherits your container). Colours are optional — omit to
682
- * use the per-mode defaults. The agent-semantic three-colour theme belongs to
683
- * `<Lens>`, not here.
686
+ * `visited` (executed nodes) + `current` (the cursor node). `mode` is the
687
+ * coarse light/dark switch it applies eui's full preset to the whole shell,
688
+ * so you pass one word instead of a wall of `--fp-*` vars. Colours are optional
689
+ * — omit to use the per-mode defaults. The agent-semantic three-colour theme
690
+ * belongs to `<Lens>`, not here.
684
691
  */
685
692
  traceTheme?: TraceTheme;
686
693
  title?: string;
@@ -1075,6 +1082,8 @@ interface DataTracePanelProps {
1075
1082
  onFrameClick?: (runtimeStageId: string) => void;
1076
1083
  /** Optional: stage name for the "tracing from" header. */
1077
1084
  fromStageName?: string;
1085
+ /** Optional honesty line rendered under the header (⚠-style). */
1086
+ note?: string;
1078
1087
  }
1079
1088
  /**
1080
1089
  * Render the backward causal chain as a stack trace.
@@ -1088,6 +1097,8 @@ interface InspectorPanelProps {
1088
1097
  selectedIndex: number;
1089
1098
  /** Causal chain frames for the selected node (empty = no trace available). */
1090
1099
  dataTraceFrames: CausalFrame[];
1100
+ /** Optional honesty line for the Data Trace tab (e.g. reads not recorded). */
1101
+ dataTraceNote?: string;
1091
1102
  /** Currently selected runtimeStageId. */
1092
1103
  selectedStageId?: string;
1093
1104
  /** Navigate to a stage when clicking a Data Trace frame. */
package/dist/index.js CHANGED
@@ -2228,6 +2228,67 @@ function TimeTravelControls({
2228
2228
  // src/components/ExplainableShell/ExplainableShell.tsx
2229
2229
  import { memo as memo8, useState as useState14, useCallback as useCallback8, useMemo as useMemo12, useRef as useRef9, useEffect as useEffect11 } from "react";
2230
2230
 
2231
+ // src/components/ExplainableShell/_internal/dataTrace.ts
2232
+ function readsByStep(tree) {
2233
+ const byStep = /* @__PURE__ */ new Map();
2234
+ if (!tree) return byStep;
2235
+ const visited = /* @__PURE__ */ new Set();
2236
+ const stack = [tree];
2237
+ while (stack.length > 0) {
2238
+ const node = stack.pop();
2239
+ if (visited.has(node)) continue;
2240
+ visited.add(node);
2241
+ if (node.runtimeStageId && node.stageReads) {
2242
+ const keys = Object.keys(node.stageReads);
2243
+ if (keys.length > 0) byStep.set(node.runtimeStageId, keys);
2244
+ }
2245
+ if (node.next) stack.push(node.next);
2246
+ if (node.children) for (const c of node.children) stack.push(c);
2247
+ }
2248
+ return byStep;
2249
+ }
2250
+ function buildDataTrace(commitLog, executionTree, targetRuntimeStageId, maxDepth = 10, maxFrames = 50) {
2251
+ const log = commitLog ?? [];
2252
+ const reads = readsByStep(executionTree);
2253
+ const readsAvailable = reads.size > 0;
2254
+ if (!log.length) return { frames: [], readsAvailable };
2255
+ const idxOf = /* @__PURE__ */ new Map();
2256
+ for (let i = 0; i < log.length; i++) idxOf.set(log[i].runtimeStageId, i);
2257
+ const startIdx = idxOf.get(targetRuntimeStageId);
2258
+ if (startIdx === void 0) return { frames: [], readsAvailable };
2259
+ const findLastWriter = (key, beforeIdx) => {
2260
+ for (let i = beforeIdx - 1; i >= 0; i--) {
2261
+ if (log[i].trace.some((t) => t.path === key)) return i;
2262
+ }
2263
+ return -1;
2264
+ };
2265
+ const frames = [];
2266
+ const visited = /* @__PURE__ */ new Set();
2267
+ const queue = [[startIdx, 0, ""]];
2268
+ while (queue.length > 0 && frames.length < maxFrames) {
2269
+ const [idx, depth, linkedBy] = queue.shift();
2270
+ const commit = log[idx];
2271
+ if (visited.has(commit.runtimeStageId)) continue;
2272
+ visited.add(commit.runtimeStageId);
2273
+ frames.push({
2274
+ runtimeStageId: commit.runtimeStageId,
2275
+ stageId: commit.stageId,
2276
+ stageName: commit.stage,
2277
+ keysWritten: commit.trace.map((t) => t.path),
2278
+ linkedBy,
2279
+ depth
2280
+ });
2281
+ if (depth >= maxDepth) continue;
2282
+ for (const key of reads.get(commit.runtimeStageId) ?? []) {
2283
+ const writerIdx = findLastWriter(key, idx);
2284
+ if (writerIdx >= 0 && !visited.has(log[writerIdx].runtimeStageId)) {
2285
+ queue.push([writerIdx, depth + 1, key]);
2286
+ }
2287
+ }
2288
+ }
2289
+ return { frames, readsAvailable };
2290
+ }
2291
+
2231
2292
  // src/utils/narrativeSync.ts
2232
2293
  function buildEntryRangeIndex(entries) {
2233
2294
  const ranges = /* @__PURE__ */ new Map();
@@ -4346,8 +4407,8 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4346
4407
  gap: 6,
4347
4408
  padding: "6px 12px",
4348
4409
  fontSize: 11,
4349
- background: rawDefaults.colors.bgSecondary,
4350
- borderBottom: `1px solid ${rawDefaults.colors.border}`,
4410
+ background: theme.bgSecondary,
4411
+ borderBottom: `1px solid ${theme.border}`,
4351
4412
  flexShrink: 0
4352
4413
  },
4353
4414
  "aria-label": "Subflow breadcrumb",
@@ -4370,7 +4431,7 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4370
4431
  padding: 0,
4371
4432
  fontSize: 11,
4372
4433
  fontWeight: isLast ? 600 : 500,
4373
- color: isLast ? rawDefaults.colors.textPrimary : rawDefaults.colors.primary,
4434
+ color: isLast ? theme.textPrimary : theme.primary,
4374
4435
  cursor: isLast ? "default" : "pointer",
4375
4436
  textDecoration: isLast ? "none" : "underline",
4376
4437
  fontFamily: "inherit"
@@ -4378,7 +4439,7 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4378
4439
  children: entry.label
4379
4440
  }
4380
4441
  ),
4381
- !isLast && /* @__PURE__ */ jsx17("span", { style: { color: rawDefaults.colors.textMuted }, children: "\u203A" })
4442
+ !isLast && /* @__PURE__ */ jsx17("span", { style: { color: theme.textMuted }, children: "\u203A" })
4382
4443
  ]
4383
4444
  },
4384
4445
  entry.subflowId ?? "__top__"
@@ -4391,10 +4452,9 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4391
4452
  // src/components/GroupContainerNode/GroupContainerNode.tsx
4392
4453
  import { Handle as Handle2, Position as Position2 } from "@xyflow/react";
4393
4454
  import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
4394
- var C = rawDefaults.colors;
4395
4455
  function GroupContainerNode({ data }) {
4396
4456
  const d = data;
4397
- const borderColor = d.error ? C.error : d.active ? C.primary : d.done ? C.success : C.border;
4457
+ const borderColor = d.error ? theme.error : d.active ? theme.primary : d.done ? theme.nodeVisited : theme.border;
4398
4458
  return /* @__PURE__ */ jsxs17(
4399
4459
  "div",
4400
4460
  {
@@ -4404,8 +4464,9 @@ function GroupContainerNode({ data }) {
4404
4464
  boxSizing: "border-box",
4405
4465
  border: `1.5px ${d.active || d.done || d.error ? "solid" : "dashed"} ${borderColor}`,
4406
4466
  borderRadius: 12,
4407
- // Translucent so the dotted background + nested children read clearly.
4408
- background: "rgba(148, 163, 184, 0.06)",
4467
+ // Translucent (theme-derived) so the dotted background + nested children
4468
+ // read clearly, while still following dark/light.
4469
+ background: `color-mix(in srgb, ${theme.textMuted} 7%, transparent)`,
4409
4470
  opacity: d.dimmed ? 0.4 : 1,
4410
4471
  position: "relative"
4411
4472
  },
@@ -4420,7 +4481,7 @@ function GroupContainerNode({ data }) {
4420
4481
  padding: "8px 12px",
4421
4482
  fontSize: 12,
4422
4483
  fontWeight: 600,
4423
- color: C.textMuted,
4484
+ color: theme.textMuted,
4424
4485
  letterSpacing: 0.2
4425
4486
  },
4426
4487
  children: [
@@ -5138,8 +5199,10 @@ var DataTracePanel = memo4(function DataTracePanel2({
5138
5199
  frames,
5139
5200
  selectedStageId,
5140
5201
  onFrameClick,
5141
- fromStageName
5202
+ fromStageName,
5203
+ note
5142
5204
  }) {
5205
+ const noteLine = note ? /* @__PURE__ */ jsx22("div", { style: { color: theme.textMuted, fontSize: 11, fontStyle: "italic", marginBottom: 8 }, children: note }) : null;
5143
5206
  if (frames.length === 0) {
5144
5207
  return /* @__PURE__ */ jsxs19("div", { style: { padding: "14px 14px 12px", fontSize: 13, lineHeight: 1.55 }, children: [
5145
5208
  /* @__PURE__ */ jsx22(
@@ -5157,10 +5220,12 @@ var DataTracePanel = memo4(function DataTracePanel2({
5157
5220
  }
5158
5221
  ),
5159
5222
  /* @__PURE__ */ jsx22("div", { style: { color: theme.textSecondary, marginBottom: 10 }, children: "Trace any value back to the stage that created it \u2014 and everything upstream that influenced it." }),
5223
+ noteLine,
5160
5224
  /* @__PURE__ */ jsx22("div", { style: { color: theme.textMuted, fontSize: 12 }, children: "Select a stage above to see its dependency chain." })
5161
5225
  ] });
5162
5226
  }
5163
5227
  return /* @__PURE__ */ jsxs19("div", { style: { padding: "8px 0", fontSize: 13 }, children: [
5228
+ note && /* @__PURE__ */ jsx22("div", { style: { padding: "4px 12px 0", fontSize: 11, color: theme.textMuted, fontStyle: "italic" }, children: note }),
5164
5229
  fromStageName && /* @__PURE__ */ jsxs19("div", { style: { padding: "4px 12px 8px" }, children: [
5165
5230
  /* @__PURE__ */ jsxs19(
5166
5231
  "div",
@@ -5294,6 +5359,7 @@ var InspectorPanel = memo5(function InspectorPanel2({
5294
5359
  snapshots,
5295
5360
  selectedIndex,
5296
5361
  dataTraceFrames,
5362
+ dataTraceNote,
5297
5363
  selectedStageId,
5298
5364
  onNavigateToStage
5299
5365
  }) {
@@ -5350,6 +5416,7 @@ var InspectorPanel = memo5(function InspectorPanel2({
5350
5416
  DataTracePanel,
5351
5417
  {
5352
5418
  frames: dataTraceFrames,
5419
+ note: dataTraceNote,
5353
5420
  selectedStageId,
5354
5421
  onFrameClick: onNavigateToStage,
5355
5422
  fromStageName: currentSnapshot?.stageName
@@ -5941,40 +6008,6 @@ function resolveSubflowFromRuntime(parentSnapshots, subflowId, narrativeEntries)
5941
6008
  if (sfSnapshots.length === 0) return null;
5942
6009
  return { subflowId, label, spec: null, snapshots: sfSnapshots, narrative: sfNarrative };
5943
6010
  }
5944
- function buildDataTrace(commitLog, targetRuntimeStageId, maxDepth = 10) {
5945
- const log = commitLog;
5946
- if (!log?.length) return [];
5947
- const idxMap = /* @__PURE__ */ new Map();
5948
- for (let i = 0; i < log.length; i++) idxMap.set(log[i].runtimeStageId, i);
5949
- const startIdx = idxMap.get(targetRuntimeStageId);
5950
- if (startIdx === void 0) return [];
5951
- const startCommit = log[startIdx];
5952
- const frames = [];
5953
- const visited = /* @__PURE__ */ new Set();
5954
- let current = startCommit;
5955
- let currentIdx = startIdx;
5956
- let depth = 0;
5957
- while (current && depth <= maxDepth) {
5958
- if (visited.has(current.runtimeStageId)) break;
5959
- visited.add(current.runtimeStageId);
5960
- frames.push({
5961
- runtimeStageId: current.runtimeStageId,
5962
- stageId: current.stageId,
5963
- stageName: current.stage,
5964
- keysWritten: current.trace.map((t) => t.path),
5965
- linkedBy: depth === 0 ? "" : current.trace[0]?.path ?? "",
5966
- depth
5967
- });
5968
- if (currentIdx > 0) {
5969
- currentIdx--;
5970
- current = log[currentIdx];
5971
- depth++;
5972
- } else {
5973
- break;
5974
- }
5975
- }
5976
- return frames;
5977
- }
5978
6011
  var RightPanel = memo8(function RightPanel2({
5979
6012
  mode,
5980
6013
  onModeChange,
@@ -5989,6 +6022,10 @@ var RightPanel = memo8(function RightPanel2({
5989
6022
  size,
5990
6023
  onNavigateToStage
5991
6024
  }) {
6025
+ const dataTrace = useMemo12(
6026
+ () => runtimeSnapshot?.commitLog ? buildDataTrace(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, snapshots[selectedIndex]?.runtimeStageId ?? "") : { frames: [], readsAvailable: true },
6027
+ [runtimeSnapshot, snapshots, selectedIndex]
6028
+ );
5992
6029
  return /* @__PURE__ */ jsxs23(Fragment6, { children: [
5993
6030
  /* @__PURE__ */ jsx26("div", { style: {
5994
6031
  display: "flex",
@@ -6040,7 +6077,8 @@ var RightPanel = memo8(function RightPanel2({
6040
6077
  {
6041
6078
  snapshots,
6042
6079
  selectedIndex,
6043
- dataTraceFrames: runtimeSnapshot?.commitLog ? buildDataTrace(runtimeSnapshot.commitLog, snapshots[selectedIndex]?.runtimeStageId ?? "") : [],
6080
+ dataTraceFrames: dataTrace.frames,
6081
+ dataTraceNote: dataTrace.readsAvailable ? void 0 : "\u26A0 reads were not recorded (readTracking off) \u2014 dependencies are unknowable, not absent.",
6044
6082
  selectedStageId: snapshots[selectedIndex]?.runtimeStageId,
6045
6083
  onNavigateToStage
6046
6084
  }
@@ -6369,12 +6407,22 @@ function ExplainableShell({
6369
6407
  }) }),
6370
6408
  /* @__PURE__ */ jsx26("div", { style: { flex: 1, overflow: "auto" }, children: detailsContent })
6371
6409
  ] });
6410
+ const shellThemeVars = useMemo12(() => {
6411
+ if (!traceTheme) return {};
6412
+ const base = traceTheme.mode ? tokensToCSSVars(traceTheme.mode === "light" ? coolLight : coolDark) : {};
6413
+ return {
6414
+ ...base,
6415
+ ...traceTheme.visited !== void 0 && { ["--fp-node-visited"]: traceTheme.visited },
6416
+ ...traceTheme.current !== void 0 && { ["--fp-node-cursor"]: traceTheme.current }
6417
+ };
6418
+ }, [traceTheme]);
6372
6419
  return /* @__PURE__ */ jsxs23(
6373
6420
  "div",
6374
6421
  {
6375
6422
  ref: shellRef,
6376
6423
  className,
6377
6424
  style: {
6425
+ ...shellThemeVars,
6378
6426
  height: "100%",
6379
6427
  display: "flex",
6380
6428
  flexDirection: "column",