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/dist/index.d.cts CHANGED
@@ -326,6 +326,12 @@ declare function StageDetailPanel({ snapshots, selectedIndex, mode: controlledMo
326
326
  * is set, slice members stay landable ("stops"), everything else fades to
327
327
  * unlandable ticks, and prev/next walk stop-to-stop ("◀ earlier cause").
328
328
  * The cursor stays the ONE `selectedIndex` — no second position exists.
329
+ *
330
+ * THEMING: tracing mode recolors its chrome with ONE token, `--fp-tracing`
331
+ * (default #0d9488 teal) — the TRACING badge, the rail's bottom border, the
332
+ * stop ticks, the walk buttons, and Done — so the tracing rail is
333
+ * unmistakably not normal time-travel. Consumers retheme by setting
334
+ * `--fp-tracing` on any ancestor. Normal mode never reads the token.
329
335
  */
330
336
  interface TracingRail {
331
337
  /** The traced variable — rendered in the mode header. */
@@ -343,6 +349,14 @@ interface TracingRail {
343
349
  onExit: () => void;
344
350
  /** Clear the via filter back to the full walk (breadcrumb's "show all"). */
345
351
  onShowAll?: () => void;
352
+ /** Ingredient count at the CURRENT stop. Together with `onForkPrompt`,
353
+ * >= 2 turns the walk-back control into "choose cause…": it PROMPTS
354
+ * instead of moving, so a silent default never hides one parent of a
355
+ * fork. 0/undefined = not a fork — classic earlier-cause behavior. */
356
+ forkCount?: number;
357
+ /** Open the fork chooser — fired INSTEAD of a cursor move when
358
+ * `forkCount >= 2` (click or ArrowLeft on the walk-back control). */
359
+ onForkPrompt?: () => void;
346
360
  }
347
361
  interface TimeTravelControlsProps extends BaseComponentProps {
348
362
  /** Stage snapshots */
@@ -1265,6 +1279,17 @@ interface TraceWalkCardProps {
1265
1279
  onJumpToStop?: (runtimeStageId: string) => void;
1266
1280
  onShowAll?: () => void;
1267
1281
  onExit?: () => void;
1282
+ /** F2 fork chooser: when true AND the current stop has 2+ ingredients,
1283
+ * a loud chooser block asks WHICH ingredient the walk should follow —
1284
+ * the rail's walk-back control opens it instead of moving. */
1285
+ forkChooserOpen?: boolean;
1286
+ /** The chooser's neutral option — today's behavior: step to the nearest
1287
+ * earlier stop in time. The SHELL computes the move; the card only
1288
+ * fires this. */
1289
+ onContinueTimeOrder?: () => void;
1290
+ /** False at the walk's earliest stop — there IS no earlier stop, so the
1291
+ * chooser's time-order button must not pretend to move (review fix). */
1292
+ canContinueTimeOrder?: boolean;
1268
1293
  }
1269
1294
  declare const TraceWalkCard: react.NamedExoticComponent<TraceWalkCardProps>;
1270
1295
 
package/dist/index.d.ts CHANGED
@@ -326,6 +326,12 @@ declare function StageDetailPanel({ snapshots, selectedIndex, mode: controlledMo
326
326
  * is set, slice members stay landable ("stops"), everything else fades to
327
327
  * unlandable ticks, and prev/next walk stop-to-stop ("◀ earlier cause").
328
328
  * The cursor stays the ONE `selectedIndex` — no second position exists.
329
+ *
330
+ * THEMING: tracing mode recolors its chrome with ONE token, `--fp-tracing`
331
+ * (default #0d9488 teal) — the TRACING badge, the rail's bottom border, the
332
+ * stop ticks, the walk buttons, and Done — so the tracing rail is
333
+ * unmistakably not normal time-travel. Consumers retheme by setting
334
+ * `--fp-tracing` on any ancestor. Normal mode never reads the token.
329
335
  */
330
336
  interface TracingRail {
331
337
  /** The traced variable — rendered in the mode header. */
@@ -343,6 +349,14 @@ interface TracingRail {
343
349
  onExit: () => void;
344
350
  /** Clear the via filter back to the full walk (breadcrumb's "show all"). */
345
351
  onShowAll?: () => void;
352
+ /** Ingredient count at the CURRENT stop. Together with `onForkPrompt`,
353
+ * >= 2 turns the walk-back control into "choose cause…": it PROMPTS
354
+ * instead of moving, so a silent default never hides one parent of a
355
+ * fork. 0/undefined = not a fork — classic earlier-cause behavior. */
356
+ forkCount?: number;
357
+ /** Open the fork chooser — fired INSTEAD of a cursor move when
358
+ * `forkCount >= 2` (click or ArrowLeft on the walk-back control). */
359
+ onForkPrompt?: () => void;
346
360
  }
347
361
  interface TimeTravelControlsProps extends BaseComponentProps {
348
362
  /** Stage snapshots */
@@ -1265,6 +1279,17 @@ interface TraceWalkCardProps {
1265
1279
  onJumpToStop?: (runtimeStageId: string) => void;
1266
1280
  onShowAll?: () => void;
1267
1281
  onExit?: () => void;
1282
+ /** F2 fork chooser: when true AND the current stop has 2+ ingredients,
1283
+ * a loud chooser block asks WHICH ingredient the walk should follow —
1284
+ * the rail's walk-back control opens it instead of moving. */
1285
+ forkChooserOpen?: boolean;
1286
+ /** The chooser's neutral option — today's behavior: step to the nearest
1287
+ * earlier stop in time. The SHELL computes the move; the card only
1288
+ * fires this. */
1289
+ onContinueTimeOrder?: () => void;
1290
+ /** False at the walk's earliest stop — there IS no earlier stop, so the
1291
+ * chooser's time-order button must not pretend to move (review fix). */
1292
+ canContinueTimeOrder?: boolean;
1268
1293
  }
1269
1294
  declare const TraceWalkCard: react.NamedExoticComponent<TraceWalkCardProps>;
1270
1295
 
package/dist/index.js CHANGED
@@ -2014,14 +2014,19 @@ function TimeTravelControls({
2014
2014
  if (!tracing) return null;
2015
2015
  return tracing.stopIndices.find((i) => i > selectedIndex) ?? null;
2016
2016
  }, [tracing, selectedIndex]);
2017
- const canPrev = isTracing ? earlierStop !== null : selectedIndex > 0;
2017
+ const forkPrompt = isTracing && (tracing.forkCount ?? 0) >= 2 && !!tracing.onForkPrompt;
2018
+ const canPrev = isTracing ? forkPrompt || earlierStop !== null : selectedIndex > 0;
2018
2019
  const canNext = isTracing ? laterStop !== null : selectedIndex < total - 1;
2019
2020
  const goPrev = useCallback3(() => {
2020
2021
  setPlaying(false);
2021
2022
  if (isTracing) {
2023
+ if (forkPrompt) {
2024
+ tracing.onForkPrompt();
2025
+ return;
2026
+ }
2022
2027
  if (earlierStop !== null) onIndexChange(earlierStop);
2023
2028
  } else if (selectedIndex > 0) onIndexChange(selectedIndex - 1);
2024
- }, [isTracing, earlierStop, selectedIndex, onIndexChange]);
2029
+ }, [isTracing, forkPrompt, tracing, earlierStop, selectedIndex, onIndexChange]);
2025
2030
  const goNext = useCallback3(() => {
2026
2031
  setPlaying(false);
2027
2032
  if (isTracing) {
@@ -2073,7 +2078,7 @@ function TimeTravelControls({
2073
2078
  [canPrev, canNext, playing, goPrev, goNext, autoPlayable, togglePlay, isTracing, tracing]
2074
2079
  );
2075
2080
  const fs = fontSize[size];
2076
- const accent = "var(--fp-accent, #6366f1)";
2081
+ const tracingColor = "var(--fp-tracing, #0d9488)";
2077
2082
  if (unstyled) {
2078
2083
  return /* @__PURE__ */ jsxs9(
2079
2084
  "div",
@@ -2110,8 +2115,8 @@ function TimeTravelControls({
2110
2115
  "data-fp": "tt-prev",
2111
2116
  disabled: !canPrev || playing,
2112
2117
  onClick: goPrev,
2113
- "aria-label": isTracing ? "Earlier cause" : "Previous stage",
2114
- children: isTracing ? "Earlier cause" : "Prev"
2118
+ "aria-label": isTracing ? forkPrompt ? "Choose cause" : "Earlier cause" : "Previous stage",
2119
+ children: isTracing ? forkPrompt ? "Choose cause\u2026" : "Earlier cause" : "Prev"
2115
2120
  }
2116
2121
  ),
2117
2122
  autoPlayable && !isTracing && /* @__PURE__ */ jsx10("button", { "data-fp": "tt-play", onClick: togglePlay, "aria-label": playing ? "Pause" : "Play", children: playing ? "Pause" : "Play" }),
@@ -2150,8 +2155,8 @@ function TimeTravelControls({
2150
2155
  }
2151
2156
  const btnStyle = (disabled) => ({
2152
2157
  background: theme.bgTertiary,
2153
- border: `1px solid ${theme.border}`,
2154
- color: disabled ? theme.textMuted : theme.textPrimary,
2158
+ border: `1px solid ${isTracing ? tracingColor : theme.border}`,
2159
+ color: disabled ? theme.textMuted : isTracing ? tracingColor : theme.textPrimary,
2155
2160
  borderRadius: "6px",
2156
2161
  padding: "4px 12px",
2157
2162
  fontSize: fs.body,
@@ -2167,7 +2172,7 @@ function TimeTravelControls({
2167
2172
  style: {
2168
2173
  padding: "6px 12px",
2169
2174
  background: theme.bgSecondary,
2170
- borderBottom: isTracing ? `2px solid ${accent}` : `1px solid ${theme.border}`,
2175
+ borderBottom: isTracing ? `2px solid ${tracingColor}` : `1px solid ${theme.border}`,
2171
2176
  display: "flex",
2172
2177
  alignItems: "center",
2173
2178
  gap: 6,
@@ -2202,14 +2207,14 @@ function TimeTravelControls({
2202
2207
  letterSpacing: "0.06em",
2203
2208
  textTransform: "uppercase",
2204
2209
  color: "#fff",
2205
- background: accent,
2210
+ background: tracingColor,
2206
2211
  borderRadius: 4,
2207
2212
  padding: "2px 7px"
2208
2213
  },
2209
2214
  children: "Tracing"
2210
2215
  }
2211
2216
  ),
2212
- /* @__PURE__ */ jsx10("span", { style: { fontFamily: "monospace", fontWeight: 600, color: accent }, children: tracing.tracedKey }),
2217
+ /* @__PURE__ */ jsx10("span", { style: { fontFamily: "monospace", fontWeight: 600, color: tracingColor }, children: tracing.tracedKey }),
2213
2218
  tracing.viaKey && /* @__PURE__ */ jsxs9("span", { style: { color: theme.textMuted }, children: [
2214
2219
  "\u25B8 via",
2215
2220
  " ",
@@ -2223,7 +2228,7 @@ function TimeTravelControls({
2223
2228
  style: {
2224
2229
  border: "none",
2225
2230
  background: "transparent",
2226
- color: accent,
2231
+ color: tracingColor,
2227
2232
  cursor: "pointer",
2228
2233
  fontSize: fs.body,
2229
2234
  textDecoration: "underline",
@@ -2248,10 +2253,10 @@ function TimeTravelControls({
2248
2253
  style: btnStyle(!canPrev || playing),
2249
2254
  disabled: !canPrev || playing,
2250
2255
  onClick: goPrev,
2251
- "aria-label": isTracing ? "Earlier cause" : "Previous stage",
2252
- title: isTracing ? "Earlier cause" : "Previous stage",
2256
+ "aria-label": isTracing ? forkPrompt ? "Choose cause" : "Earlier cause" : "Previous stage",
2257
+ title: isTracing ? forkPrompt ? "This stop is a fork \u2014 choose which cause to follow" : "Earlier cause" : "Previous stage",
2253
2258
  "data-fp": "tt-prev",
2254
- children: isTracing ? "\u25C0 earlier cause" : "\u25C0"
2259
+ children: isTracing ? forkPrompt ? "\u2442 choose cause\u2026" : "\u25C0 earlier cause" : "\u25C0"
2255
2260
  }
2256
2261
  ),
2257
2262
  autoPlayable && !isTracing && /* @__PURE__ */ jsx10(
@@ -2322,7 +2327,7 @@ function TimeTravelControls({
2322
2327
  borderRadius: 3,
2323
2328
  border: "none",
2324
2329
  cursor: unlandable ? "default" : "pointer",
2325
- background: isTracing ? isActive ? accent : isStop ? "color-mix(in srgb, " + accent + " 55%, transparent)" : theme.bgTertiary : isActive ? theme.primary : isDone ? theme.success : theme.bgTertiary,
2330
+ background: isTracing ? isActive ? tracingColor : isStop ? "color-mix(in srgb, " + tracingColor + " 55%, transparent)" : theme.bgTertiary : isActive ? theme.primary : isDone ? theme.success : theme.bgTertiary,
2326
2331
  opacity: unlandable ? 0.3 : isTracing || isDone || isActive ? 1 : 0.4,
2327
2332
  transition: "all 0.15s ease"
2328
2333
  }
@@ -2573,16 +2578,22 @@ var TraceWalkCard = memo(function TraceWalkCard2({
2573
2578
  onFollowIngredient,
2574
2579
  onJumpToStop,
2575
2580
  onShowAll,
2576
- onExit
2581
+ onExit,
2582
+ forkChooserOpen,
2583
+ onContinueTimeOrder,
2584
+ canContinueTimeOrder = true
2577
2585
  }) {
2578
2586
  const [copied, setCopied] = useState7(false);
2579
2587
  const accent = "var(--fp-accent, #6366f1)";
2588
+ const tracingColor = "var(--fp-tracing, #0d9488)";
2580
2589
  const currentIdx = useMemo8(() => {
2581
2590
  if (!cursorRuntimeStageId) return 0;
2582
2591
  const i = walk.stops.findIndex((s) => s.runtimeStageId === cursorRuntimeStageId);
2583
2592
  return i >= 0 ? i : 0;
2584
2593
  }, [walk, cursorRuntimeStageId]);
2585
2594
  const current = walk.stops[currentIdx];
2595
+ const followableCount = current ? current.ingredients.filter((ing) => ing.writerRuntimeStageId !== null).length : 0;
2596
+ const chooserVisible = !!forkChooserOpen && followableCount >= 2;
2586
2597
  const copyStory = () => {
2587
2598
  const text = formatTraceWalk(walk, stepNumberOf);
2588
2599
  void navigator.clipboard?.writeText(text).then(() => {
@@ -2668,7 +2679,60 @@ var TraceWalkCard = memo(function TraceWalkCard2({
2668
2679
  preview
2669
2680
  ] })
2670
2681
  ] }),
2671
- /* @__PURE__ */ jsx11("div", { style: { marginTop: 10 }, children: current.ingredients.length > 0 ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
2682
+ chooserVisible && /* @__PURE__ */ jsxs10(
2683
+ "div",
2684
+ {
2685
+ "data-fp": "twc-fork-chooser",
2686
+ style: {
2687
+ marginTop: 10,
2688
+ padding: "10px 12px",
2689
+ border: `1.5px solid ${tracingColor}`,
2690
+ borderRadius: 8,
2691
+ background: `color-mix(in srgb, ${tracingColor} 8%, transparent)`
2692
+ },
2693
+ children: [
2694
+ /* @__PURE__ */ jsxs10("div", { style: { fontWeight: 600, fontSize: 12, color: theme.textPrimary }, children: [
2695
+ "This value was made from ",
2696
+ current.ingredients.length,
2697
+ " ingredients \u2014 which one should the walk follow?"
2698
+ ] }),
2699
+ /* @__PURE__ */ jsx11("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 8 }, children: current.ingredients.map((ing, i) => /* @__PURE__ */ jsx11(
2700
+ IngredientChip,
2701
+ {
2702
+ ing,
2703
+ color: i < CHIP_COLORS.length ? CHIP_COLORS[i] : theme.textMuted,
2704
+ step: ing.writerRuntimeStageId ? stepNumberOf(ing.writerRuntimeStageId) : null,
2705
+ onFollow: onFollowIngredient
2706
+ },
2707
+ ing.key
2708
+ )) }),
2709
+ /* @__PURE__ */ jsx11(
2710
+ "button",
2711
+ {
2712
+ "data-fp": "twc-continue-time",
2713
+ onClick: canContinueTimeOrder ? onContinueTimeOrder : void 0,
2714
+ disabled: !canContinueTimeOrder,
2715
+ title: canContinueTimeOrder ? void 0 : "This is the walk's earliest stop \u2014 there is nothing earlier to visit",
2716
+ style: {
2717
+ display: "block",
2718
+ width: "100%",
2719
+ marginTop: 8,
2720
+ border: `1px solid ${theme.border}`,
2721
+ background: theme.bgTertiary,
2722
+ color: theme.textPrimary,
2723
+ borderRadius: 6,
2724
+ padding: "5px 10px",
2725
+ fontSize: 11,
2726
+ fontWeight: 600,
2727
+ cursor: "pointer"
2728
+ },
2729
+ children: "visit all, oldest cause last (time order)"
2730
+ }
2731
+ )
2732
+ ]
2733
+ }
2734
+ ),
2735
+ /* @__PURE__ */ jsx11("div", { style: { marginTop: 10 }, children: chooserVisible ? null : current.ingredients.length > 0 ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
2672
2736
  /* @__PURE__ */ jsxs10("div", { style: { fontSize: 11, color: theme.textMuted, textTransform: "uppercase", letterSpacing: "0.5px", fontWeight: 600 }, children: [
2673
2737
  "Made from ",
2674
2738
  current.ingredients.length,
@@ -6805,6 +6869,11 @@ function ExplainableShell({
6805
6869
  const [rightPanelMode, setRightPanelMode] = useState15("insights");
6806
6870
  const [inspectorTab, setInspectorTab] = useState15("state");
6807
6871
  const [tracing, setTracing] = useState15(null);
6872
+ const [forkChooserOpen, setForkChooserOpen] = useState15(false);
6873
+ const [traceSearch, setTraceSearch] = useState15("");
6874
+ useEffect11(() => {
6875
+ setForkChooserOpen(false);
6876
+ }, [snapshotIdx]);
6808
6877
  const [leftExpanded, setLeftExpanded] = useState15(defaultExpanded?.topology ?? false);
6809
6878
  const [timelineExpanded, setTimelineExpanded] = useState15(defaultExpanded?.timeline ?? false);
6810
6879
  useEffect11(() => {
@@ -6844,6 +6913,21 @@ function ExplainableShell({
6844
6913
  () => runtimeSnapshot?.commitLog ? buildDataTrace(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, activeSnapshots[safeIdx]?.runtimeStageId ?? "") : { frames: [], readsAvailable: true },
6845
6914
  [runtimeSnapshot, activeSnapshots, safeIdx]
6846
6915
  );
6916
+ const allTracedKeys = useMemo14(() => {
6917
+ const log = runtimeSnapshot?.commitLog;
6918
+ if (!log?.length) return [];
6919
+ const seen = /* @__PURE__ */ new Set();
6920
+ const keys = [];
6921
+ for (const c of log) {
6922
+ for (const t of c.trace ?? []) {
6923
+ if (!seen.has(t.path)) {
6924
+ seen.add(t.path);
6925
+ keys.push(t.path);
6926
+ }
6927
+ }
6928
+ }
6929
+ return keys;
6930
+ }, [runtimeSnapshot]);
6847
6931
  const traceWalk = useMemo14(() => {
6848
6932
  if (!tracing || !runtimeSnapshot?.commitLog) return null;
6849
6933
  const scope = tracing.via.length > 0 ? tracing.via[tracing.via.length - 1] : tracing;
@@ -6917,6 +7001,8 @@ function ExplainableShell({
6917
7001
  const cursorCommitIdx = log.findIndex((c) => c.runtimeStageId === cursorRsid);
6918
7002
  const beforeCommitIdx = cursorCommitIdx >= 0 ? cursorCommitIdx + 1 : void 0;
6919
7003
  setTracing({ key, beforeCommitIdx, via: [] });
7004
+ setForkChooserOpen(false);
7005
+ setTraceSearch("");
6920
7006
  setRightPanelMode("what");
6921
7007
  setInspectorTab("trace");
6922
7008
  jumpToAnchor(
@@ -6930,6 +7016,7 @@ function ExplainableShell({
6930
7016
  if (!tracing || !runtimeSnapshot?.commitLog || ing.writerCommitIdx === null) return;
6931
7017
  const scope = { key: ing.key, beforeCommitIdx: ing.writerCommitIdx + 1 };
6932
7018
  setTracing({ ...tracing, via: [...tracing.via, scope] });
7019
+ setForkChooserOpen(false);
6933
7020
  jumpToAnchor(
6934
7021
  buildTraceWalk(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, scope.key, {
6935
7022
  beforeCommitIdx: scope.beforeCommitIdx
@@ -6941,18 +7028,29 @@ function ExplainableShell({
6941
7028
  const handleShowAllIngredients = useCallback8(() => {
6942
7029
  if (!tracing || !runtimeSnapshot?.commitLog) return;
6943
7030
  setTracing({ ...tracing, via: [] });
7031
+ setForkChooserOpen(false);
6944
7032
  jumpToAnchor(
6945
7033
  buildTraceWalk(runtimeSnapshot.commitLog, runtimeSnapshot.executionTree, tracing.key, {
6946
7034
  beforeCommitIdx: tracing.beforeCommitIdx
6947
7035
  })
6948
7036
  );
6949
7037
  }, [tracing, runtimeSnapshot, jumpToAnchor]);
6950
- const handleExitTracing = useCallback8(() => setTracing(null), []);
7038
+ const handleExitTracing = useCallback8(() => {
7039
+ setTracing(null);
7040
+ setForkChooserOpen(false);
7041
+ }, []);
7042
+ const handleForkPrompt = useCallback8(() => setForkChooserOpen(true), []);
7043
+ const handleContinueTimeOrder = useCallback8(() => {
7044
+ const earlier = traceStopIndices.filter((i) => i < safeIdx);
7045
+ if (earlier.length > 0) setSnapshotIdx(earlier[earlier.length - 1]);
7046
+ setForkChooserOpen(false);
7047
+ }, [traceStopIndices, safeIdx]);
6951
7048
  const handleDrillDown = useCallback8(
6952
7049
  (nodeName) => {
6953
7050
  const entry = resolveSubflowFromRuntime(activeSnapshots, nodeName, narrativeEntries);
6954
7051
  if (entry) {
6955
7052
  setTracing(null);
7053
+ setForkChooserOpen(false);
6956
7054
  setDrillDownStack((prev) => [...prev, { ...entry, parentSnapshotIdx: snapshotIdx }]);
6957
7055
  setSnapshotIdx(0);
6958
7056
  }
@@ -7018,6 +7116,7 @@ function ExplainableShell({
7018
7116
  if (!tracing || !traceWalk || traceWalk.missing || traceStopIndices.length === 0) return null;
7019
7117
  const cursorRsid = activeSnapshots[safeIdx]?.runtimeStageId;
7020
7118
  const walkIdx = traceWalk.stops.findIndex((st) => st.runtimeStageId === cursorRsid);
7119
+ const currentStop = traceWalk.stops[walkIdx >= 0 ? walkIdx : 0];
7021
7120
  return {
7022
7121
  tracedKey: tracing.key,
7023
7122
  viaKey: activeViaKey,
@@ -7025,59 +7124,116 @@ function ExplainableShell({
7025
7124
  stopOrdinal: walkIdx >= 0 ? walkIdx + 1 : 1,
7026
7125
  totalStops: traceWalk.stops.length,
7027
7126
  onExit: handleExitTracing,
7028
- onShowAll: activeViaKey ? handleShowAllIngredients : void 0
7029
- };
7030
- }, [tracing, traceWalk, traceStopIndices, activeSnapshots, safeIdx, activeViaKey, handleExitTracing, handleShowAllIngredients]);
7031
- const traceTabContent = useMemo14(() => tracing && traceWalk ? /* @__PURE__ */ jsx27(
7032
- TraceWalkCard,
7033
- {
7034
- walk: traceWalk,
7035
- cursorRuntimeStageId: activeSnapshots[safeIdx]?.runtimeStageId ?? null,
7036
- viaKey: activeViaKey,
7037
- stepNumberOf,
7038
- previewValueOf: (k) => activeSnapshots[safeIdx]?.memory?.[k],
7039
- onFollowIngredient: handleFollowIngredient,
7040
- onJumpToStop: navigateToStage,
7041
7127
  onShowAll: activeViaKey ? handleShowAllIngredients : void 0,
7042
- onExit: handleExitTracing
7043
- }
7044
- ) : /* @__PURE__ */ jsxs24(Fragment8, { children: [
7045
- !isInSubflow && (shellDataTrace.frames[0]?.keysWritten?.length ?? 0) > 0 && /* @__PURE__ */ jsxs24("div", { "data-fp": "trace-entry", style: { padding: "10px 14px 0", fontSize: 12 }, children: [
7046
- /* @__PURE__ */ jsx27("span", { style: { color: theme.textMuted, marginRight: 6 }, children: "Trace a value:" }),
7047
- shellDataTrace.frames[0].keysWritten.map((k) => /* @__PURE__ */ jsx27(
7048
- "button",
7128
+ // Followable ingredients only — termini can't be chosen, so a stop of
7129
+ // run-inputs must not prompt (matches the card's chooser gate).
7130
+ forkCount: currentStop?.ingredients.filter((ing) => ing.writerRuntimeStageId !== null).length ?? 0,
7131
+ onForkPrompt: handleForkPrompt
7132
+ };
7133
+ }, [tracing, traceWalk, traceStopIndices, activeSnapshots, safeIdx, activeViaKey, handleExitTracing, handleShowAllIngredients, handleForkPrompt]);
7134
+ const traceTabContent = useMemo14(() => {
7135
+ if (tracing && traceWalk) {
7136
+ return /* @__PURE__ */ jsx27(
7137
+ TraceWalkCard,
7049
7138
  {
7050
- "data-fp": "trace-entry-chip",
7051
- onClick: () => handleStartTracing(k),
7052
- title: "Where did " + k + " come from? Walk its causes on the timeline.",
7053
- style: {
7054
- border: "1px solid var(--fp-accent, #6366f1)",
7055
- background: "transparent",
7056
- color: "var(--fp-accent, #6366f1)",
7057
- borderRadius: 12,
7058
- padding: "2px 10px",
7059
- margin: "0 6px 6px 0",
7060
- fontSize: 11,
7061
- fontWeight: 600,
7062
- fontFamily: "monospace",
7063
- cursor: "pointer"
7139
+ walk: traceWalk,
7140
+ cursorRuntimeStageId: activeSnapshots[safeIdx]?.runtimeStageId ?? null,
7141
+ viaKey: activeViaKey,
7142
+ stepNumberOf,
7143
+ previewValueOf: (k) => activeSnapshots[safeIdx]?.memory?.[k],
7144
+ onFollowIngredient: handleFollowIngredient,
7145
+ onJumpToStop: navigateToStage,
7146
+ onShowAll: activeViaKey ? handleShowAllIngredients : void 0,
7147
+ onExit: handleExitTracing,
7148
+ forkChooserOpen,
7149
+ onContinueTimeOrder: handleContinueTimeOrder,
7150
+ canContinueTimeOrder: traceStopIndices.some((i) => i < safeIdx)
7151
+ }
7152
+ );
7153
+ }
7154
+ const chipStyle = {
7155
+ border: "1px solid var(--fp-accent, #6366f1)",
7156
+ background: "transparent",
7157
+ color: "var(--fp-accent, #6366f1)",
7158
+ borderRadius: 12,
7159
+ padding: "2px 10px",
7160
+ margin: "0 6px 6px 0",
7161
+ fontSize: 11,
7162
+ fontWeight: 600,
7163
+ fontFamily: "monospace",
7164
+ cursor: "pointer"
7165
+ };
7166
+ const query = traceSearch.trim().toLowerCase();
7167
+ const matchedKeys = query ? allTracedKeys.filter((k) => k.toLowerCase().includes(query)) : allTracedKeys;
7168
+ const shownKeys = matchedKeys.slice(0, 12);
7169
+ return /* @__PURE__ */ jsxs24(Fragment8, { children: [
7170
+ !isInSubflow && (shellDataTrace.frames[0]?.keysWritten?.length ?? 0) > 0 && /* @__PURE__ */ jsxs24("div", { "data-fp": "trace-entry", style: { padding: "10px 14px 0", fontSize: 12 }, children: [
7171
+ /* @__PURE__ */ jsx27("span", { style: { color: theme.textMuted, marginRight: 6 }, children: "This step wrote:" }),
7172
+ shellDataTrace.frames[0].keysWritten.map((k) => /* @__PURE__ */ jsx27(
7173
+ "button",
7174
+ {
7175
+ "data-fp": "trace-entry-chip",
7176
+ onClick: () => handleStartTracing(k),
7177
+ title: "Where did " + k + " come from? Walk its causes on the timeline.",
7178
+ style: chipStyle,
7179
+ children: k
7064
7180
  },
7065
- children: k
7066
- },
7067
- k
7068
- ))
7069
- ] }),
7070
- /* @__PURE__ */ jsx27(
7071
- DataTracePanel,
7072
- {
7073
- frames: shellDataTrace.frames,
7074
- note: shellDataTrace.readsAvailable ? void 0 : "\u26A0 reads were not recorded (readTracking off) \u2014 dependencies are unknowable, not absent.",
7075
- selectedStageId: activeSnapshots[safeIdx]?.runtimeStageId,
7076
- onFrameClick: navigateToStage,
7077
- fromStageName: activeSnapshots[safeIdx]?.stageName
7078
- }
7079
- )
7080
- ] }), [tracing, traceWalk, activeSnapshots, safeIdx, activeViaKey, stepNumberOf, handleFollowIngredient, navigateToStage, handleShowAllIngredients, handleExitTracing, handleStartTracing, isInSubflow, shellDataTrace]);
7181
+ k
7182
+ ))
7183
+ ] }),
7184
+ !isInSubflow && allTracedKeys.length > 0 && /* @__PURE__ */ jsxs24("div", { "data-fp": "trace-any", style: { padding: "6px 14px 0", fontSize: 12 }, children: [
7185
+ /* @__PURE__ */ jsx27("div", { style: { color: theme.textMuted, marginBottom: 4 }, children: "Trace any variable:" }),
7186
+ /* @__PURE__ */ jsx27(
7187
+ "input",
7188
+ {
7189
+ "data-fp": "trace-search",
7190
+ value: traceSearch,
7191
+ onChange: (e) => setTraceSearch(e.target.value),
7192
+ placeholder: "search any variable...",
7193
+ style: {
7194
+ display: "block",
7195
+ width: "100%",
7196
+ boxSizing: "border-box",
7197
+ background: theme.bgTertiary,
7198
+ border: `1px solid ${theme.border}`,
7199
+ borderRadius: 6,
7200
+ color: theme.textPrimary,
7201
+ fontSize: 11,
7202
+ fontFamily: "monospace",
7203
+ padding: "4px 8px",
7204
+ marginBottom: 6
7205
+ }
7206
+ }
7207
+ ),
7208
+ shownKeys.map((k) => /* @__PURE__ */ jsx27(
7209
+ "button",
7210
+ {
7211
+ "data-fp": "trace-any-chip",
7212
+ onClick: () => handleStartTracing(k),
7213
+ title: "Where did " + k + " come from? Walk its causes on the timeline.",
7214
+ style: chipStyle,
7215
+ children: k
7216
+ },
7217
+ k
7218
+ )),
7219
+ query === "" && matchedKeys.length > shownKeys.length && /* @__PURE__ */ jsxs24("span", { style: { color: theme.textMuted, fontSize: 11 }, children: [
7220
+ "+",
7221
+ matchedKeys.length - shownKeys.length,
7222
+ " more \u2014 type to search"
7223
+ ] })
7224
+ ] }),
7225
+ /* @__PURE__ */ jsx27(
7226
+ DataTracePanel,
7227
+ {
7228
+ frames: shellDataTrace.frames,
7229
+ note: shellDataTrace.readsAvailable ? void 0 : "\u26A0 reads were not recorded (readTracking off) \u2014 dependencies are unknowable, not absent.",
7230
+ selectedStageId: activeSnapshots[safeIdx]?.runtimeStageId,
7231
+ onFrameClick: navigateToStage,
7232
+ fromStageName: activeSnapshots[safeIdx]?.stageName
7233
+ }
7234
+ )
7235
+ ] });
7236
+ }, [tracing, traceWalk, activeSnapshots, safeIdx, activeViaKey, stepNumberOf, handleFollowIngredient, navigateToStage, handleShowAllIngredients, handleExitTracing, handleStartTracing, isInSubflow, shellDataTrace, forkChooserOpen, handleContinueTimeOrder, traceStopIndices, traceSearch, allTracedKeys]);
7081
7237
  const tabLabels = new Map(allTabs.map((t) => [t.id, t.name]));
7082
7238
  if (unstyled) {
7083
7239
  return /* @__PURE__ */ jsxs24("div", { className, style, "data-fp": "explainable-shell", children: [