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.cjs CHANGED
@@ -2300,6 +2300,67 @@ function TimeTravelControls({
2300
2300
  // src/components/ExplainableShell/ExplainableShell.tsx
2301
2301
  var import_react31 = require("react");
2302
2302
 
2303
+ // src/components/ExplainableShell/_internal/dataTrace.ts
2304
+ function readsByStep(tree) {
2305
+ const byStep = /* @__PURE__ */ new Map();
2306
+ if (!tree) return byStep;
2307
+ const visited = /* @__PURE__ */ new Set();
2308
+ const stack = [tree];
2309
+ while (stack.length > 0) {
2310
+ const node = stack.pop();
2311
+ if (visited.has(node)) continue;
2312
+ visited.add(node);
2313
+ if (node.runtimeStageId && node.stageReads) {
2314
+ const keys = Object.keys(node.stageReads);
2315
+ if (keys.length > 0) byStep.set(node.runtimeStageId, keys);
2316
+ }
2317
+ if (node.next) stack.push(node.next);
2318
+ if (node.children) for (const c of node.children) stack.push(c);
2319
+ }
2320
+ return byStep;
2321
+ }
2322
+ function buildDataTrace(commitLog, executionTree, targetRuntimeStageId, maxDepth = 10, maxFrames = 50) {
2323
+ const log = commitLog ?? [];
2324
+ const reads = readsByStep(executionTree);
2325
+ const readsAvailable = reads.size > 0;
2326
+ if (!log.length) return { frames: [], readsAvailable };
2327
+ const idxOf = /* @__PURE__ */ new Map();
2328
+ for (let i = 0; i < log.length; i++) idxOf.set(log[i].runtimeStageId, i);
2329
+ const startIdx = idxOf.get(targetRuntimeStageId);
2330
+ if (startIdx === void 0) return { frames: [], readsAvailable };
2331
+ const findLastWriter = (key, beforeIdx) => {
2332
+ for (let i = beforeIdx - 1; i >= 0; i--) {
2333
+ if (log[i].trace.some((t) => t.path === key)) return i;
2334
+ }
2335
+ return -1;
2336
+ };
2337
+ const frames = [];
2338
+ const visited = /* @__PURE__ */ new Set();
2339
+ const queue = [[startIdx, 0, ""]];
2340
+ while (queue.length > 0 && frames.length < maxFrames) {
2341
+ const [idx, depth, linkedBy] = queue.shift();
2342
+ const commit = log[idx];
2343
+ if (visited.has(commit.runtimeStageId)) continue;
2344
+ visited.add(commit.runtimeStageId);
2345
+ frames.push({
2346
+ runtimeStageId: commit.runtimeStageId,
2347
+ stageId: commit.stageId,
2348
+ stageName: commit.stage,
2349
+ keysWritten: commit.trace.map((t) => t.path),
2350
+ linkedBy,
2351
+ depth
2352
+ });
2353
+ if (depth >= maxDepth) continue;
2354
+ for (const key of reads.get(commit.runtimeStageId) ?? []) {
2355
+ const writerIdx = findLastWriter(key, idx);
2356
+ if (writerIdx >= 0 && !visited.has(log[writerIdx].runtimeStageId)) {
2357
+ queue.push([writerIdx, depth + 1, key]);
2358
+ }
2359
+ }
2360
+ }
2361
+ return { frames, readsAvailable };
2362
+ }
2363
+
2303
2364
  // src/utils/narrativeSync.ts
2304
2365
  function buildEntryRangeIndex(entries) {
2305
2366
  const ranges = /* @__PURE__ */ new Map();
@@ -4413,8 +4474,8 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4413
4474
  gap: 6,
4414
4475
  padding: "6px 12px",
4415
4476
  fontSize: 11,
4416
- background: rawDefaults.colors.bgSecondary,
4417
- borderBottom: `1px solid ${rawDefaults.colors.border}`,
4477
+ background: theme.bgSecondary,
4478
+ borderBottom: `1px solid ${theme.border}`,
4418
4479
  flexShrink: 0
4419
4480
  },
4420
4481
  "aria-label": "Subflow breadcrumb",
@@ -4437,7 +4498,7 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4437
4498
  padding: 0,
4438
4499
  fontSize: 11,
4439
4500
  fontWeight: isLast ? 600 : 500,
4440
- color: isLast ? rawDefaults.colors.textPrimary : rawDefaults.colors.primary,
4501
+ color: isLast ? theme.textPrimary : theme.primary,
4441
4502
  cursor: isLast ? "default" : "pointer",
4442
4503
  textDecoration: isLast ? "none" : "underline",
4443
4504
  fontFamily: "inherit"
@@ -4445,7 +4506,7 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4445
4506
  children: entry.label
4446
4507
  }
4447
4508
  ),
4448
- !isLast && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { color: rawDefaults.colors.textMuted }, children: "\u203A" })
4509
+ !isLast && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { color: theme.textMuted }, children: "\u203A" })
4449
4510
  ]
4450
4511
  },
4451
4512
  entry.subflowId ?? "__top__"
@@ -4458,10 +4519,9 @@ function SubflowBreadcrumbBar({ entries, onNavigate }) {
4458
4519
  // src/components/GroupContainerNode/GroupContainerNode.tsx
4459
4520
  var import_react19 = require("@xyflow/react");
4460
4521
  var import_jsx_runtime18 = require("react/jsx-runtime");
4461
- var C = rawDefaults.colors;
4462
4522
  function GroupContainerNode({ data }) {
4463
4523
  const d = data;
4464
- const borderColor = d.error ? C.error : d.active ? C.primary : d.done ? C.success : C.border;
4524
+ const borderColor = d.error ? theme.error : d.active ? theme.primary : d.done ? theme.nodeVisited : theme.border;
4465
4525
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4466
4526
  "div",
4467
4527
  {
@@ -4471,8 +4531,9 @@ function GroupContainerNode({ data }) {
4471
4531
  boxSizing: "border-box",
4472
4532
  border: `1.5px ${d.active || d.done || d.error ? "solid" : "dashed"} ${borderColor}`,
4473
4533
  borderRadius: 12,
4474
- // Translucent so the dotted background + nested children read clearly.
4475
- background: "rgba(148, 163, 184, 0.06)",
4534
+ // Translucent (theme-derived) so the dotted background + nested children
4535
+ // read clearly, while still following dark/light.
4536
+ background: `color-mix(in srgb, ${theme.textMuted} 7%, transparent)`,
4476
4537
  opacity: d.dimmed ? 0.4 : 1,
4477
4538
  position: "relative"
4478
4539
  },
@@ -4487,7 +4548,7 @@ function GroupContainerNode({ data }) {
4487
4548
  padding: "8px 12px",
4488
4549
  fontSize: 12,
4489
4550
  fontWeight: 600,
4490
- color: C.textMuted,
4551
+ color: theme.textMuted,
4491
4552
  letterSpacing: 0.2
4492
4553
  },
4493
4554
  children: [
@@ -5205,8 +5266,10 @@ var DataTracePanel = (0, import_react27.memo)(function DataTracePanel2({
5205
5266
  frames,
5206
5267
  selectedStageId,
5207
5268
  onFrameClick,
5208
- fromStageName
5269
+ fromStageName,
5270
+ note
5209
5271
  }) {
5272
+ const noteLine = note ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: theme.textMuted, fontSize: 11, fontStyle: "italic", marginBottom: 8 }, children: note }) : null;
5210
5273
  if (frames.length === 0) {
5211
5274
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { padding: "14px 14px 12px", fontSize: 13, lineHeight: 1.55 }, children: [
5212
5275
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
@@ -5224,10 +5287,12 @@ var DataTracePanel = (0, import_react27.memo)(function DataTracePanel2({
5224
5287
  }
5225
5288
  ),
5226
5289
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("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." }),
5290
+ noteLine,
5227
5291
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: theme.textMuted, fontSize: 12 }, children: "Select a stage above to see its dependency chain." })
5228
5292
  ] });
5229
5293
  }
5230
5294
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { padding: "8px 0", fontSize: 13 }, children: [
5295
+ note && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { padding: "4px 12px 0", fontSize: 11, color: theme.textMuted, fontStyle: "italic" }, children: note }),
5231
5296
  fromStageName && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { padding: "4px 12px 8px" }, children: [
5232
5297
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
5233
5298
  "div",
@@ -5361,6 +5426,7 @@ var InspectorPanel = (0, import_react28.memo)(function InspectorPanel2({
5361
5426
  snapshots,
5362
5427
  selectedIndex,
5363
5428
  dataTraceFrames,
5429
+ dataTraceNote,
5364
5430
  selectedStageId,
5365
5431
  onNavigateToStage
5366
5432
  }) {
@@ -5417,6 +5483,7 @@ var InspectorPanel = (0, import_react28.memo)(function InspectorPanel2({
5417
5483
  DataTracePanel,
5418
5484
  {
5419
5485
  frames: dataTraceFrames,
5486
+ note: dataTraceNote,
5420
5487
  selectedStageId,
5421
5488
  onFrameClick: onNavigateToStage,
5422
5489
  fromStageName: currentSnapshot?.stageName
@@ -6008,40 +6075,6 @@ function resolveSubflowFromRuntime(parentSnapshots, subflowId, narrativeEntries)
6008
6075
  if (sfSnapshots.length === 0) return null;
6009
6076
  return { subflowId, label, spec: null, snapshots: sfSnapshots, narrative: sfNarrative };
6010
6077
  }
6011
- function buildDataTrace(commitLog, targetRuntimeStageId, maxDepth = 10) {
6012
- const log = commitLog;
6013
- if (!log?.length) return [];
6014
- const idxMap = /* @__PURE__ */ new Map();
6015
- for (let i = 0; i < log.length; i++) idxMap.set(log[i].runtimeStageId, i);
6016
- const startIdx = idxMap.get(targetRuntimeStageId);
6017
- if (startIdx === void 0) return [];
6018
- const startCommit = log[startIdx];
6019
- const frames = [];
6020
- const visited = /* @__PURE__ */ new Set();
6021
- let current = startCommit;
6022
- let currentIdx = startIdx;
6023
- let depth = 0;
6024
- while (current && depth <= maxDepth) {
6025
- if (visited.has(current.runtimeStageId)) break;
6026
- visited.add(current.runtimeStageId);
6027
- frames.push({
6028
- runtimeStageId: current.runtimeStageId,
6029
- stageId: current.stageId,
6030
- stageName: current.stage,
6031
- keysWritten: current.trace.map((t) => t.path),
6032
- linkedBy: depth === 0 ? "" : current.trace[0]?.path ?? "",
6033
- depth
6034
- });
6035
- if (currentIdx > 0) {
6036
- currentIdx--;
6037
- current = log[currentIdx];
6038
- depth++;
6039
- } else {
6040
- break;
6041
- }
6042
- }
6043
- return frames;
6044
- }
6045
6078
  var RightPanel = (0, import_react31.memo)(function RightPanel2({
6046
6079
  mode,
6047
6080
  onModeChange,
@@ -6056,6 +6089,10 @@ var RightPanel = (0, import_react31.memo)(function RightPanel2({
6056
6089
  size,
6057
6090
  onNavigateToStage
6058
6091
  }) {
6092
+ const dataTrace = (0, import_react31.useMemo)(
6093
+ () => runtimeSnapshot?.commitLog ? buildDataTrace(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, snapshots[selectedIndex]?.runtimeStageId ?? "") : { frames: [], readsAvailable: true },
6094
+ [runtimeSnapshot, snapshots, selectedIndex]
6095
+ );
6059
6096
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
6060
6097
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { style: {
6061
6098
  display: "flex",
@@ -6107,7 +6144,8 @@ var RightPanel = (0, import_react31.memo)(function RightPanel2({
6107
6144
  {
6108
6145
  snapshots,
6109
6146
  selectedIndex,
6110
- dataTraceFrames: runtimeSnapshot?.commitLog ? buildDataTrace(runtimeSnapshot.commitLog, snapshots[selectedIndex]?.runtimeStageId ?? "") : [],
6147
+ dataTraceFrames: dataTrace.frames,
6148
+ dataTraceNote: dataTrace.readsAvailable ? void 0 : "\u26A0 reads were not recorded (readTracking off) \u2014 dependencies are unknowable, not absent.",
6111
6149
  selectedStageId: snapshots[selectedIndex]?.runtimeStageId,
6112
6150
  onNavigateToStage
6113
6151
  }
@@ -6436,12 +6474,22 @@ function ExplainableShell({
6436
6474
  }) }),
6437
6475
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { style: { flex: 1, overflow: "auto" }, children: detailsContent })
6438
6476
  ] });
6477
+ const shellThemeVars = (0, import_react31.useMemo)(() => {
6478
+ if (!traceTheme) return {};
6479
+ const base = traceTheme.mode ? tokensToCSSVars(traceTheme.mode === "light" ? coolLight : coolDark) : {};
6480
+ return {
6481
+ ...base,
6482
+ ...traceTheme.visited !== void 0 && { ["--fp-node-visited"]: traceTheme.visited },
6483
+ ...traceTheme.current !== void 0 && { ["--fp-node-cursor"]: traceTheme.current }
6484
+ };
6485
+ }, [traceTheme]);
6439
6486
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
6440
6487
  "div",
6441
6488
  {
6442
6489
  ref: shellRef,
6443
6490
  className,
6444
6491
  style: {
6492
+ ...shellThemeVars,
6445
6493
  height: "100%",
6446
6494
  display: "flex",
6447
6495
  flexDirection: "column",