footprint-explainable-ui 0.29.0 → 0.30.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
@@ -163,6 +163,18 @@ have. One cursor, no new axis.
163
163
  - **[Copy story]** emits the same text an LLM backtrack tool returns — the
164
164
  human's board and the agent's answer are one artifact.
165
165
  - Tracing lives on the root rail: drilling into a subflow exits it honestly.
166
+ - **Trace anything**: below the current step's chips, a search box lists
167
+ *every* variable the run ever wrote — trace any of them from wherever you
168
+ stand.
169
+ - **Forks ask, never assume**: at a value made from two or more ingredients
170
+ the walk-back button becomes **⑂ choose cause…** and asks which ingredient
171
+ to follow (or "visit all, in time order"). Nothing is ever silently picked.
172
+
173
+ ![The fork chooser: which cause should the walk follow?](demo/fork-chooser-verified.png)
174
+
175
+ - **Unmistakable mode**: the whole tracing rail wears its own color
176
+ (`--fp-tracing`, teal by default — themeable) so tracing can never be
177
+ confused with normal time-travel.
166
178
 
167
179
  ### Panel Labels
168
180
 
package/dist/index.cjs CHANGED
@@ -2089,14 +2089,19 @@ function TimeTravelControls({
2089
2089
  if (!tracing) return null;
2090
2090
  return tracing.stopIndices.find((i) => i > selectedIndex) ?? null;
2091
2091
  }, [tracing, selectedIndex]);
2092
- const canPrev = isTracing ? earlierStop !== null : selectedIndex > 0;
2092
+ const forkPrompt = isTracing && (tracing.forkCount ?? 0) >= 2 && !!tracing.onForkPrompt;
2093
+ const canPrev = isTracing ? forkPrompt || earlierStop !== null : selectedIndex > 0;
2093
2094
  const canNext = isTracing ? laterStop !== null : selectedIndex < total - 1;
2094
2095
  const goPrev = (0, import_react10.useCallback)(() => {
2095
2096
  setPlaying(false);
2096
2097
  if (isTracing) {
2098
+ if (forkPrompt) {
2099
+ tracing.onForkPrompt();
2100
+ return;
2101
+ }
2097
2102
  if (earlierStop !== null) onIndexChange(earlierStop);
2098
2103
  } else if (selectedIndex > 0) onIndexChange(selectedIndex - 1);
2099
- }, [isTracing, earlierStop, selectedIndex, onIndexChange]);
2104
+ }, [isTracing, forkPrompt, tracing, earlierStop, selectedIndex, onIndexChange]);
2100
2105
  const goNext = (0, import_react10.useCallback)(() => {
2101
2106
  setPlaying(false);
2102
2107
  if (isTracing) {
@@ -2148,7 +2153,7 @@ function TimeTravelControls({
2148
2153
  [canPrev, canNext, playing, goPrev, goNext, autoPlayable, togglePlay, isTracing, tracing]
2149
2154
  );
2150
2155
  const fs = fontSize[size];
2151
- const accent = "var(--fp-accent, #6366f1)";
2156
+ const tracingColor = "var(--fp-tracing, #0d9488)";
2152
2157
  if (unstyled) {
2153
2158
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2154
2159
  "div",
@@ -2185,8 +2190,8 @@ function TimeTravelControls({
2185
2190
  "data-fp": "tt-prev",
2186
2191
  disabled: !canPrev || playing,
2187
2192
  onClick: goPrev,
2188
- "aria-label": isTracing ? "Earlier cause" : "Previous stage",
2189
- children: isTracing ? "Earlier cause" : "Prev"
2193
+ "aria-label": isTracing ? forkPrompt ? "Choose cause" : "Earlier cause" : "Previous stage",
2194
+ children: isTracing ? forkPrompt ? "Choose cause\u2026" : "Earlier cause" : "Prev"
2190
2195
  }
2191
2196
  ),
2192
2197
  autoPlayable && !isTracing && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "data-fp": "tt-play", onClick: togglePlay, "aria-label": playing ? "Pause" : "Play", children: playing ? "Pause" : "Play" }),
@@ -2225,8 +2230,8 @@ function TimeTravelControls({
2225
2230
  }
2226
2231
  const btnStyle = (disabled) => ({
2227
2232
  background: theme.bgTertiary,
2228
- border: `1px solid ${theme.border}`,
2229
- color: disabled ? theme.textMuted : theme.textPrimary,
2233
+ border: `1px solid ${isTracing ? tracingColor : theme.border}`,
2234
+ color: disabled ? theme.textMuted : isTracing ? tracingColor : theme.textPrimary,
2230
2235
  borderRadius: "6px",
2231
2236
  padding: "4px 12px",
2232
2237
  fontSize: fs.body,
@@ -2242,7 +2247,7 @@ function TimeTravelControls({
2242
2247
  style: {
2243
2248
  padding: "6px 12px",
2244
2249
  background: theme.bgSecondary,
2245
- borderBottom: isTracing ? `2px solid ${accent}` : `1px solid ${theme.border}`,
2250
+ borderBottom: isTracing ? `2px solid ${tracingColor}` : `1px solid ${theme.border}`,
2246
2251
  display: "flex",
2247
2252
  alignItems: "center",
2248
2253
  gap: 6,
@@ -2277,14 +2282,14 @@ function TimeTravelControls({
2277
2282
  letterSpacing: "0.06em",
2278
2283
  textTransform: "uppercase",
2279
2284
  color: "#fff",
2280
- background: accent,
2285
+ background: tracingColor,
2281
2286
  borderRadius: 4,
2282
2287
  padding: "2px 7px"
2283
2288
  },
2284
2289
  children: "Tracing"
2285
2290
  }
2286
2291
  ),
2287
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontFamily: "monospace", fontWeight: 600, color: accent }, children: tracing.tracedKey }),
2292
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontFamily: "monospace", fontWeight: 600, color: tracingColor }, children: tracing.tracedKey }),
2288
2293
  tracing.viaKey && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { style: { color: theme.textMuted }, children: [
2289
2294
  "\u25B8 via",
2290
2295
  " ",
@@ -2298,7 +2303,7 @@ function TimeTravelControls({
2298
2303
  style: {
2299
2304
  border: "none",
2300
2305
  background: "transparent",
2301
- color: accent,
2306
+ color: tracingColor,
2302
2307
  cursor: "pointer",
2303
2308
  fontSize: fs.body,
2304
2309
  textDecoration: "underline",
@@ -2323,10 +2328,10 @@ function TimeTravelControls({
2323
2328
  style: btnStyle(!canPrev || playing),
2324
2329
  disabled: !canPrev || playing,
2325
2330
  onClick: goPrev,
2326
- "aria-label": isTracing ? "Earlier cause" : "Previous stage",
2327
- title: isTracing ? "Earlier cause" : "Previous stage",
2331
+ "aria-label": isTracing ? forkPrompt ? "Choose cause" : "Earlier cause" : "Previous stage",
2332
+ title: isTracing ? forkPrompt ? "This stop is a fork \u2014 choose which cause to follow" : "Earlier cause" : "Previous stage",
2328
2333
  "data-fp": "tt-prev",
2329
- children: isTracing ? "\u25C0 earlier cause" : "\u25C0"
2334
+ children: isTracing ? forkPrompt ? "\u2442 choose cause\u2026" : "\u25C0 earlier cause" : "\u25C0"
2330
2335
  }
2331
2336
  ),
2332
2337
  autoPlayable && !isTracing && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -2397,7 +2402,7 @@ function TimeTravelControls({
2397
2402
  borderRadius: 3,
2398
2403
  border: "none",
2399
2404
  cursor: unlandable ? "default" : "pointer",
2400
- background: isTracing ? isActive ? accent : isStop ? "color-mix(in srgb, " + accent + " 55%, transparent)" : theme.bgTertiary : isActive ? theme.primary : isDone ? theme.success : theme.bgTertiary,
2405
+ background: isTracing ? isActive ? tracingColor : isStop ? "color-mix(in srgb, " + tracingColor + " 55%, transparent)" : theme.bgTertiary : isActive ? theme.primary : isDone ? theme.success : theme.bgTertiary,
2401
2406
  opacity: unlandable ? 0.3 : isTracing || isDone || isActive ? 1 : 0.4,
2402
2407
  transition: "all 0.15s ease"
2403
2408
  }
@@ -2648,16 +2653,22 @@ var TraceWalkCard = (0, import_react11.memo)(function TraceWalkCard2({
2648
2653
  onFollowIngredient,
2649
2654
  onJumpToStop,
2650
2655
  onShowAll,
2651
- onExit
2656
+ onExit,
2657
+ forkChooserOpen,
2658
+ onContinueTimeOrder,
2659
+ canContinueTimeOrder = true
2652
2660
  }) {
2653
2661
  const [copied, setCopied] = (0, import_react11.useState)(false);
2654
2662
  const accent = "var(--fp-accent, #6366f1)";
2663
+ const tracingColor = "var(--fp-tracing, #0d9488)";
2655
2664
  const currentIdx = (0, import_react11.useMemo)(() => {
2656
2665
  if (!cursorRuntimeStageId) return 0;
2657
2666
  const i = walk.stops.findIndex((s) => s.runtimeStageId === cursorRuntimeStageId);
2658
2667
  return i >= 0 ? i : 0;
2659
2668
  }, [walk, cursorRuntimeStageId]);
2660
2669
  const current = walk.stops[currentIdx];
2670
+ const followableCount = current ? current.ingredients.filter((ing) => ing.writerRuntimeStageId !== null).length : 0;
2671
+ const chooserVisible = !!forkChooserOpen && followableCount >= 2;
2661
2672
  const copyStory = () => {
2662
2673
  const text = formatTraceWalk(walk, stepNumberOf);
2663
2674
  void navigator.clipboard?.writeText(text).then(() => {
@@ -2743,7 +2754,60 @@ var TraceWalkCard = (0, import_react11.memo)(function TraceWalkCard2({
2743
2754
  preview
2744
2755
  ] })
2745
2756
  ] }),
2746
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { marginTop: 10 }, children: current.ingredients.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2757
+ chooserVisible && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2758
+ "div",
2759
+ {
2760
+ "data-fp": "twc-fork-chooser",
2761
+ style: {
2762
+ marginTop: 10,
2763
+ padding: "10px 12px",
2764
+ border: `1.5px solid ${tracingColor}`,
2765
+ borderRadius: 8,
2766
+ background: `color-mix(in srgb, ${tracingColor} 8%, transparent)`
2767
+ },
2768
+ children: [
2769
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { fontWeight: 600, fontSize: 12, color: theme.textPrimary }, children: [
2770
+ "This value was made from ",
2771
+ current.ingredients.length,
2772
+ " ingredients \u2014 which one should the walk follow?"
2773
+ ] }),
2774
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 8 }, children: current.ingredients.map((ing, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2775
+ IngredientChip,
2776
+ {
2777
+ ing,
2778
+ color: i < CHIP_COLORS.length ? CHIP_COLORS[i] : theme.textMuted,
2779
+ step: ing.writerRuntimeStageId ? stepNumberOf(ing.writerRuntimeStageId) : null,
2780
+ onFollow: onFollowIngredient
2781
+ },
2782
+ ing.key
2783
+ )) }),
2784
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2785
+ "button",
2786
+ {
2787
+ "data-fp": "twc-continue-time",
2788
+ onClick: canContinueTimeOrder ? onContinueTimeOrder : void 0,
2789
+ disabled: !canContinueTimeOrder,
2790
+ title: canContinueTimeOrder ? void 0 : "This is the walk's earliest stop \u2014 there is nothing earlier to visit",
2791
+ style: {
2792
+ display: "block",
2793
+ width: "100%",
2794
+ marginTop: 8,
2795
+ border: `1px solid ${theme.border}`,
2796
+ background: theme.bgTertiary,
2797
+ color: theme.textPrimary,
2798
+ borderRadius: 6,
2799
+ padding: "5px 10px",
2800
+ fontSize: 11,
2801
+ fontWeight: 600,
2802
+ cursor: "pointer"
2803
+ },
2804
+ children: "visit all, oldest cause last (time order)"
2805
+ }
2806
+ )
2807
+ ]
2808
+ }
2809
+ ),
2810
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { marginTop: 10 }, children: chooserVisible ? null : current.ingredients.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2747
2811
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { fontSize: 11, color: theme.textMuted, textTransform: "uppercase", letterSpacing: "0.5px", fontWeight: 600 }, children: [
2748
2812
  "Made from ",
2749
2813
  current.ingredients.length,
@@ -6875,6 +6939,11 @@ function ExplainableShell({
6875
6939
  const [rightPanelMode, setRightPanelMode] = (0, import_react32.useState)("insights");
6876
6940
  const [inspectorTab, setInspectorTab] = (0, import_react32.useState)("state");
6877
6941
  const [tracing, setTracing] = (0, import_react32.useState)(null);
6942
+ const [forkChooserOpen, setForkChooserOpen] = (0, import_react32.useState)(false);
6943
+ const [traceSearch, setTraceSearch] = (0, import_react32.useState)("");
6944
+ (0, import_react32.useEffect)(() => {
6945
+ setForkChooserOpen(false);
6946
+ }, [snapshotIdx]);
6878
6947
  const [leftExpanded, setLeftExpanded] = (0, import_react32.useState)(defaultExpanded?.topology ?? false);
6879
6948
  const [timelineExpanded, setTimelineExpanded] = (0, import_react32.useState)(defaultExpanded?.timeline ?? false);
6880
6949
  (0, import_react32.useEffect)(() => {
@@ -6914,6 +6983,21 @@ function ExplainableShell({
6914
6983
  () => runtimeSnapshot?.commitLog ? buildDataTrace(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, activeSnapshots[safeIdx]?.runtimeStageId ?? "") : { frames: [], readsAvailable: true },
6915
6984
  [runtimeSnapshot, activeSnapshots, safeIdx]
6916
6985
  );
6986
+ const allTracedKeys = (0, import_react32.useMemo)(() => {
6987
+ const log = runtimeSnapshot?.commitLog;
6988
+ if (!log?.length) return [];
6989
+ const seen = /* @__PURE__ */ new Set();
6990
+ const keys = [];
6991
+ for (const c of log) {
6992
+ for (const t of c.trace ?? []) {
6993
+ if (!seen.has(t.path)) {
6994
+ seen.add(t.path);
6995
+ keys.push(t.path);
6996
+ }
6997
+ }
6998
+ }
6999
+ return keys;
7000
+ }, [runtimeSnapshot]);
6917
7001
  const traceWalk = (0, import_react32.useMemo)(() => {
6918
7002
  if (!tracing || !runtimeSnapshot?.commitLog) return null;
6919
7003
  const scope = tracing.via.length > 0 ? tracing.via[tracing.via.length - 1] : tracing;
@@ -6987,6 +7071,8 @@ function ExplainableShell({
6987
7071
  const cursorCommitIdx = log.findIndex((c) => c.runtimeStageId === cursorRsid);
6988
7072
  const beforeCommitIdx = cursorCommitIdx >= 0 ? cursorCommitIdx + 1 : void 0;
6989
7073
  setTracing({ key, beforeCommitIdx, via: [] });
7074
+ setForkChooserOpen(false);
7075
+ setTraceSearch("");
6990
7076
  setRightPanelMode("what");
6991
7077
  setInspectorTab("trace");
6992
7078
  jumpToAnchor(
@@ -7000,6 +7086,7 @@ function ExplainableShell({
7000
7086
  if (!tracing || !runtimeSnapshot?.commitLog || ing.writerCommitIdx === null) return;
7001
7087
  const scope = { key: ing.key, beforeCommitIdx: ing.writerCommitIdx + 1 };
7002
7088
  setTracing({ ...tracing, via: [...tracing.via, scope] });
7089
+ setForkChooserOpen(false);
7003
7090
  jumpToAnchor(
7004
7091
  buildTraceWalk(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, scope.key, {
7005
7092
  beforeCommitIdx: scope.beforeCommitIdx
@@ -7011,18 +7098,29 @@ function ExplainableShell({
7011
7098
  const handleShowAllIngredients = (0, import_react32.useCallback)(() => {
7012
7099
  if (!tracing || !runtimeSnapshot?.commitLog) return;
7013
7100
  setTracing({ ...tracing, via: [] });
7101
+ setForkChooserOpen(false);
7014
7102
  jumpToAnchor(
7015
7103
  buildTraceWalk(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, tracing.key, {
7016
7104
  beforeCommitIdx: tracing.beforeCommitIdx
7017
7105
  })
7018
7106
  );
7019
7107
  }, [tracing, runtimeSnapshot, jumpToAnchor]);
7020
- const handleExitTracing = (0, import_react32.useCallback)(() => setTracing(null), []);
7108
+ const handleExitTracing = (0, import_react32.useCallback)(() => {
7109
+ setTracing(null);
7110
+ setForkChooserOpen(false);
7111
+ }, []);
7112
+ const handleForkPrompt = (0, import_react32.useCallback)(() => setForkChooserOpen(true), []);
7113
+ const handleContinueTimeOrder = (0, import_react32.useCallback)(() => {
7114
+ const earlier = traceStopIndices.filter((i) => i < safeIdx);
7115
+ if (earlier.length > 0) setSnapshotIdx(earlier[earlier.length - 1]);
7116
+ setForkChooserOpen(false);
7117
+ }, [traceStopIndices, safeIdx]);
7021
7118
  const handleDrillDown = (0, import_react32.useCallback)(
7022
7119
  (nodeName) => {
7023
7120
  const entry = resolveSubflowFromRuntime(activeSnapshots, nodeName, narrativeEntries);
7024
7121
  if (entry) {
7025
7122
  setTracing(null);
7123
+ setForkChooserOpen(false);
7026
7124
  setDrillDownStack((prev) => [...prev, { ...entry, parentSnapshotIdx: snapshotIdx }]);
7027
7125
  setSnapshotIdx(0);
7028
7126
  }
@@ -7088,6 +7186,7 @@ function ExplainableShell({
7088
7186
  if (!tracing || !traceWalk || traceWalk.missing || traceStopIndices.length === 0) return null;
7089
7187
  const cursorRsid = activeSnapshots[safeIdx]?.runtimeStageId;
7090
7188
  const walkIdx = traceWalk.stops.findIndex((st) => st.runtimeStageId === cursorRsid);
7189
+ const currentStop = traceWalk.stops[walkIdx >= 0 ? walkIdx : 0];
7091
7190
  return {
7092
7191
  tracedKey: tracing.key,
7093
7192
  viaKey: activeViaKey,
@@ -7095,59 +7194,116 @@ function ExplainableShell({
7095
7194
  stopOrdinal: walkIdx >= 0 ? walkIdx + 1 : 1,
7096
7195
  totalStops: traceWalk.stops.length,
7097
7196
  onExit: handleExitTracing,
7098
- onShowAll: activeViaKey ? handleShowAllIngredients : void 0
7099
- };
7100
- }, [tracing, traceWalk, traceStopIndices, activeSnapshots, safeIdx, activeViaKey, handleExitTracing, handleShowAllIngredients]);
7101
- const traceTabContent = (0, import_react32.useMemo)(() => tracing && traceWalk ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7102
- TraceWalkCard,
7103
- {
7104
- walk: traceWalk,
7105
- cursorRuntimeStageId: activeSnapshots[safeIdx]?.runtimeStageId ?? null,
7106
- viaKey: activeViaKey,
7107
- stepNumberOf,
7108
- previewValueOf: (k) => activeSnapshots[safeIdx]?.memory?.[k],
7109
- onFollowIngredient: handleFollowIngredient,
7110
- onJumpToStop: navigateToStage,
7111
7197
  onShowAll: activeViaKey ? handleShowAllIngredients : void 0,
7112
- onExit: handleExitTracing
7113
- }
7114
- ) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
7115
- !isInSubflow && (shellDataTrace.frames[0]?.keysWritten?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-fp": "trace-entry", style: { padding: "10px 14px 0", fontSize: 12 }, children: [
7116
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { color: theme.textMuted, marginRight: 6 }, children: "Trace a value:" }),
7117
- shellDataTrace.frames[0].keysWritten.map((k) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7118
- "button",
7198
+ // Followable ingredients only — termini can't be chosen, so a stop of
7199
+ // run-inputs must not prompt (matches the card's chooser gate).
7200
+ forkCount: currentStop?.ingredients.filter((ing) => ing.writerRuntimeStageId !== null).length ?? 0,
7201
+ onForkPrompt: handleForkPrompt
7202
+ };
7203
+ }, [tracing, traceWalk, traceStopIndices, activeSnapshots, safeIdx, activeViaKey, handleExitTracing, handleShowAllIngredients, handleForkPrompt]);
7204
+ const traceTabContent = (0, import_react32.useMemo)(() => {
7205
+ if (tracing && traceWalk) {
7206
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7207
+ TraceWalkCard,
7119
7208
  {
7120
- "data-fp": "trace-entry-chip",
7121
- onClick: () => handleStartTracing(k),
7122
- title: "Where did " + k + " come from? Walk its causes on the timeline.",
7123
- style: {
7124
- border: "1px solid var(--fp-accent, #6366f1)",
7125
- background: "transparent",
7126
- color: "var(--fp-accent, #6366f1)",
7127
- borderRadius: 12,
7128
- padding: "2px 10px",
7129
- margin: "0 6px 6px 0",
7130
- fontSize: 11,
7131
- fontWeight: 600,
7132
- fontFamily: "monospace",
7133
- cursor: "pointer"
7209
+ walk: traceWalk,
7210
+ cursorRuntimeStageId: activeSnapshots[safeIdx]?.runtimeStageId ?? null,
7211
+ viaKey: activeViaKey,
7212
+ stepNumberOf,
7213
+ previewValueOf: (k) => activeSnapshots[safeIdx]?.memory?.[k],
7214
+ onFollowIngredient: handleFollowIngredient,
7215
+ onJumpToStop: navigateToStage,
7216
+ onShowAll: activeViaKey ? handleShowAllIngredients : void 0,
7217
+ onExit: handleExitTracing,
7218
+ forkChooserOpen,
7219
+ onContinueTimeOrder: handleContinueTimeOrder,
7220
+ canContinueTimeOrder: traceStopIndices.some((i) => i < safeIdx)
7221
+ }
7222
+ );
7223
+ }
7224
+ const chipStyle = {
7225
+ border: "1px solid var(--fp-accent, #6366f1)",
7226
+ background: "transparent",
7227
+ color: "var(--fp-accent, #6366f1)",
7228
+ borderRadius: 12,
7229
+ padding: "2px 10px",
7230
+ margin: "0 6px 6px 0",
7231
+ fontSize: 11,
7232
+ fontWeight: 600,
7233
+ fontFamily: "monospace",
7234
+ cursor: "pointer"
7235
+ };
7236
+ const query = traceSearch.trim().toLowerCase();
7237
+ const matchedKeys = query ? allTracedKeys.filter((k) => k.toLowerCase().includes(query)) : allTracedKeys;
7238
+ const shownKeys = matchedKeys.slice(0, 12);
7239
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
7240
+ !isInSubflow && (shellDataTrace.frames[0]?.keysWritten?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-fp": "trace-entry", style: { padding: "10px 14px 0", fontSize: 12 }, children: [
7241
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { color: theme.textMuted, marginRight: 6 }, children: "This step wrote:" }),
7242
+ shellDataTrace.frames[0].keysWritten.map((k) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7243
+ "button",
7244
+ {
7245
+ "data-fp": "trace-entry-chip",
7246
+ onClick: () => handleStartTracing(k),
7247
+ title: "Where did " + k + " come from? Walk its causes on the timeline.",
7248
+ style: chipStyle,
7249
+ children: k
7134
7250
  },
7135
- children: k
7136
- },
7137
- k
7138
- ))
7139
- ] }),
7140
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7141
- DataTracePanel,
7142
- {
7143
- frames: shellDataTrace.frames,
7144
- note: shellDataTrace.readsAvailable ? void 0 : "\u26A0 reads were not recorded (readTracking off) \u2014 dependencies are unknowable, not absent.",
7145
- selectedStageId: activeSnapshots[safeIdx]?.runtimeStageId,
7146
- onFrameClick: navigateToStage,
7147
- fromStageName: activeSnapshots[safeIdx]?.stageName
7148
- }
7149
- )
7150
- ] }), [tracing, traceWalk, activeSnapshots, safeIdx, activeViaKey, stepNumberOf, handleFollowIngredient, navigateToStage, handleShowAllIngredients, handleExitTracing, handleStartTracing, isInSubflow, shellDataTrace]);
7251
+ k
7252
+ ))
7253
+ ] }),
7254
+ !isInSubflow && allTracedKeys.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-fp": "trace-any", style: { padding: "6px 14px 0", fontSize: 12 }, children: [
7255
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { color: theme.textMuted, marginBottom: 4 }, children: "Trace any variable:" }),
7256
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7257
+ "input",
7258
+ {
7259
+ "data-fp": "trace-search",
7260
+ value: traceSearch,
7261
+ onChange: (e) => setTraceSearch(e.target.value),
7262
+ placeholder: "search any variable...",
7263
+ style: {
7264
+ display: "block",
7265
+ width: "100%",
7266
+ boxSizing: "border-box",
7267
+ background: theme.bgTertiary,
7268
+ border: `1px solid ${theme.border}`,
7269
+ borderRadius: 6,
7270
+ color: theme.textPrimary,
7271
+ fontSize: 11,
7272
+ fontFamily: "monospace",
7273
+ padding: "4px 8px",
7274
+ marginBottom: 6
7275
+ }
7276
+ }
7277
+ ),
7278
+ shownKeys.map((k) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7279
+ "button",
7280
+ {
7281
+ "data-fp": "trace-any-chip",
7282
+ onClick: () => handleStartTracing(k),
7283
+ title: "Where did " + k + " come from? Walk its causes on the timeline.",
7284
+ style: chipStyle,
7285
+ children: k
7286
+ },
7287
+ k
7288
+ )),
7289
+ query === "" && matchedKeys.length > shownKeys.length && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { color: theme.textMuted, fontSize: 11 }, children: [
7290
+ "+",
7291
+ matchedKeys.length - shownKeys.length,
7292
+ " more \u2014 type to search"
7293
+ ] })
7294
+ ] }),
7295
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
7296
+ DataTracePanel,
7297
+ {
7298
+ frames: shellDataTrace.frames,
7299
+ note: shellDataTrace.readsAvailable ? void 0 : "\u26A0 reads were not recorded (readTracking off) \u2014 dependencies are unknowable, not absent.",
7300
+ selectedStageId: activeSnapshots[safeIdx]?.runtimeStageId,
7301
+ onFrameClick: navigateToStage,
7302
+ fromStageName: activeSnapshots[safeIdx]?.stageName
7303
+ }
7304
+ )
7305
+ ] });
7306
+ }, [tracing, traceWalk, activeSnapshots, safeIdx, activeViaKey, stepNumberOf, handleFollowIngredient, navigateToStage, handleShowAllIngredients, handleExitTracing, handleStartTracing, isInSubflow, shellDataTrace, forkChooserOpen, handleContinueTimeOrder, traceStopIndices, traceSearch, allTracedKeys]);
7151
7307
  const tabLabels = new Map(allTabs.map((t) => [t.id, t.name]));
7152
7308
  if (unstyled) {
7153
7309
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className, style, "data-fp": "explainable-shell", children: [