agentfootprint-lens 0.72.1 → 0.73.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.
Files changed (76) hide show
  1. package/README.md +39 -8
  2. package/dist/{Lens-DaElQv4z.d.cts → Lens-dAaGjB6z.d.ts} +13 -6
  3. package/dist/{Lens-lTf_UA0N.d.ts → Lens-v6JPZWcg.d.cts} +13 -6
  4. package/dist/{chunk-WIQVUP5C.js → chunk-4XZHSL5T.js} +132 -132
  5. package/dist/chunk-4XZHSL5T.js.map +1 -0
  6. package/dist/{chunk-R6FVUU2I.js → chunk-5QKU7VQK.js} +2 -2
  7. package/dist/{chunk-PFGNLVLG.js → chunk-CGZDQEY2.js} +351 -60
  8. package/dist/chunk-CGZDQEY2.js.map +1 -0
  9. package/dist/{chunk-6YXIST3E.js → chunk-D22Q5SDX.js} +4 -11
  10. package/dist/chunk-D22Q5SDX.js.map +1 -0
  11. package/dist/{chunk-SGRWGP6H.js → chunk-G5FHO5MN.js} +4 -3
  12. package/dist/chunk-G5FHO5MN.js.map +1 -0
  13. package/dist/{chunk-EMAZMZKI.js → chunk-GH767WN4.js} +4 -4
  14. package/dist/{chunk-IBSMUKPU.js → chunk-JRPGOX3R.js} +10 -10
  15. package/dist/chunk-JRPGOX3R.js.map +1 -0
  16. package/dist/{chunk-JSGDVUYF.js → chunk-KBGPQBNO.js} +55 -39
  17. package/dist/chunk-KBGPQBNO.js.map +1 -0
  18. package/dist/{chunk-ZYVDW34A.js → chunk-NBBFRC6Z.js} +11 -11
  19. package/dist/chunk-NBBFRC6Z.js.map +1 -0
  20. package/dist/{chunk-XEAZRBOH.js → chunk-NSTKBU5V.js} +6 -20
  21. package/dist/{chunk-XEAZRBOH.js.map → chunk-NSTKBU5V.js.map} +1 -1
  22. package/dist/{chunk-DBEVLCHX.js → chunk-OYSCNQQ4.js} +696 -365
  23. package/dist/chunk-OYSCNQQ4.js.map +1 -0
  24. package/dist/{chunk-CXTWT2L6.js → chunk-QHG4HZWH.js} +5 -5
  25. package/dist/context.cjs +21 -40
  26. package/dist/context.cjs.map +1 -1
  27. package/dist/context.d.cts +4 -4
  28. package/dist/context.d.ts +4 -4
  29. package/dist/context.js +6 -6
  30. package/dist/{contextAt-Ng-DLxF3.d.cts → contextAt-BcT4UYlI.d.cts} +1 -1
  31. package/dist/{contextAt-Dj4FsdFX.d.ts → contextAt-DxDAvgZ5.d.ts} +1 -1
  32. package/dist/core.cjs +679 -389
  33. package/dist/core.cjs.map +1 -1
  34. package/dist/core.d.cts +83 -9
  35. package/dist/core.d.ts +83 -9
  36. package/dist/core.js +14 -6
  37. package/dist/{index-BT5Tsjz1.d.cts → index-9TfT51FK.d.cts} +3 -3
  38. package/dist/{index-C8c9mOYy.d.ts → index-TilViQvd.d.ts} +3 -3
  39. package/dist/index.cjs +3034 -2382
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +30 -18
  42. package/dist/index.d.ts +30 -18
  43. package/dist/index.js +64 -25
  44. package/dist/index.js.map +1 -1
  45. package/dist/{lensCursorPort-Djm3W7K1.d.ts → lensCursorPort-nD3C6iQs.d.cts} +55 -2
  46. package/dist/{lensCursorPort-Djm3W7K1.d.cts → lensCursorPort-nD3C6iQs.d.ts} +55 -2
  47. package/dist/{selectSkillFrameContext-xPr-gzFp.d.cts → selectSkillFrameContext-BZd5omLC.d.cts} +1 -1
  48. package/dist/{selectSkillFrameContext-Bs8JINIi.d.ts → selectSkillFrameContext-BbMpx_lQ.d.ts} +1 -1
  49. package/dist/skillgraph.cjs +2 -1
  50. package/dist/skillgraph.cjs.map +1 -1
  51. package/dist/skillgraph.d.cts +6 -6
  52. package/dist/skillgraph.d.ts +6 -6
  53. package/dist/skillgraph.js +5 -5
  54. package/dist/{stepForRuntimeStageId-CPb4EI74.d.cts → stepForRuntimeStageId-BLB9h1lR.d.cts} +1 -1
  55. package/dist/{stepForRuntimeStageId-zJ6H0gU1.d.ts → stepForRuntimeStageId-CKQuUOyK.d.ts} +1 -1
  56. package/dist/{tagAxis-ClhV50a2.d.ts → tagAxis-COX6o7DJ.d.cts} +9 -16
  57. package/dist/{tagAxis-CijjA_6P.d.cts → tagAxis-Cgk6vlcY.d.ts} +9 -16
  58. package/dist/{useSharedCursor-lgpWw94t.d.cts → useSharedCursor-BdzFNO_C.d.ts} +28 -7
  59. package/dist/{useSharedCursor-CdXdsXvD.d.ts → useSharedCursor-DJTQSH0y.d.cts} +28 -7
  60. package/dist/why.cjs +1237 -694
  61. package/dist/why.cjs.map +1 -1
  62. package/dist/why.d.cts +7 -7
  63. package/dist/why.d.ts +7 -7
  64. package/dist/why.js +8 -8
  65. package/package.json +4 -4
  66. package/dist/chunk-6YXIST3E.js.map +0 -1
  67. package/dist/chunk-DBEVLCHX.js.map +0 -1
  68. package/dist/chunk-IBSMUKPU.js.map +0 -1
  69. package/dist/chunk-JSGDVUYF.js.map +0 -1
  70. package/dist/chunk-PFGNLVLG.js.map +0 -1
  71. package/dist/chunk-SGRWGP6H.js.map +0 -1
  72. package/dist/chunk-WIQVUP5C.js.map +0 -1
  73. package/dist/chunk-ZYVDW34A.js.map +0 -1
  74. /package/dist/{chunk-R6FVUU2I.js.map → chunk-5QKU7VQK.js.map} +0 -0
  75. /package/dist/{chunk-EMAZMZKI.js.map → chunk-GH767WN4.js.map} +0 -0
  76. /package/dist/{chunk-CXTWT2L6.js.map → chunk-QHG4HZWH.js.map} +0 -0
package/README.md CHANGED
@@ -30,6 +30,11 @@ agentfootprint = **2 primitives (LLM, Agent) + 3 compositions (Sequence, Paralle
30
30
  npm install agentfootprint agentfootprint-lens
31
31
  ```
32
32
 
33
+ Lens requires `footprint-explainable-ui >=0.34.0 <1.0.0`. Earlier versions
34
+ lack renderer APIs used by Lens even if an older peer range accepted them.
35
+ The minimum and current tested renderer versions are checked with packed
36
+ installs on both React 18 and React 19; an import failure fails the check.
37
+
33
38
  ```tsx
34
39
  import { useMemo } from 'react';
35
40
  import { Agent } from 'agentfootprint';
@@ -174,6 +179,26 @@ One mental model. The runner does the work; Lens watches.
174
179
 
175
180
  ---
176
181
 
182
+ ## Trust boundaries and source prefixes
183
+
184
+ `TrustBoundaryView` reads the optional version-1 `TrustBoundaries` recorder bundle. It shows observed permission, policy, middleware, and credential facts with retained/dropped counts. It does not infer missing decisions from a finished execution tree or certify security or regulatory compliance.
185
+
186
+ Share the same cursor with execution replay to inspect a fact's actual source location:
187
+
188
+ ```tsx
189
+ const shared = useSharedCursor(recorder, recording.snapshot);
190
+ return <>
191
+ <TrustBoundaryView snapshot={recording.snapshot} shared={shared} />
192
+ <Lens recorder={recorder} shared={shared} />
193
+ </>;
194
+ ```
195
+
196
+ Import these components and the hook from `agentfootprint-lens`. A fact's `sourcePosition` identifies its exact log, complete runtime subflow path, and inclusive committed prefix (`-1` means the base before any commit). Source navigation does not substitute an enclosing stage or the final state. Missing positions remain **Unplaced**; absent, incompatible, or damaged logs remain unavailable.
197
+
198
+ Metadata-only exporters should mark their snapshot or selected child context with `stateValuesWithheld: true`. Lens then distinguishes a known source location from unavailable state values. For full-content snapshots, prefix folding requires the recorded base and intact prefix; a missing base never falls back to `globalContext`. **Show final execution step** explicitly leaves source-prefix mode. Older recorder bundles and manually supplied legacy cursors remain readable without source navigation.
199
+
200
+ Headless consumers can import `readTrustBoundaries`, `readSourcePosition`, and `resolveSourcePrefix` from `agentfootprint-lens/core`. See [the reader contract](src/core/trustBoundaries/README.md) and [cursor ownership](src/core/cursor/README.md) for limits and failure codes.
201
+
177
202
  ## Watching a run that already finished
178
203
 
179
204
  A recording is a run you kept. It is exactly THREE things — miss one and one
@@ -852,6 +877,15 @@ its own last stop; a new recorder drops the held address. Headless pieces on
852
877
  `/core`: `stepForAddress`, `cursorForAddress`, `addressOf`. Design and the
853
878
  facts the fixtures taught: docs/design/2026-09-shared-cursor.md.
854
879
 
880
+ If the held address has no position on a child or filtered axis, Lens says so
881
+ instead of displaying that axis's first stage. It does not fold stage state or
882
+ render a final answer for that missing position. Return to the parent view to
883
+ recover the same address, or explicitly choose a recorded stop to move it.
884
+ With a shared cursor, **Latest** is a jump to the current last stop, not a
885
+ subscription to future stops. Before any explicit selection, the shared cursor
886
+ continues to derive the growing end. The older numeric `step` contract keeps
887
+ its existing clamp and live-follow behavior.
888
+
855
889
  ## Time travel through one port
856
890
 
857
891
  **Nothing to do — this is how the cursor already moves.** Read it if you are
@@ -1512,14 +1546,9 @@ legend.source; // 'structure' | 'log'
1512
1546
  legend.entries.map((e) => [e.label, e.hits]); // [['Iteration', 2], ['LLM turn', 2], ['audit', 0], …]
1513
1547
 
1514
1548
  const axis = tagAxisPositions(snapshot, ['milestone:llm-turn'], scrubAxisFor(recorder, 'group'));
1515
- axis?.map((p) => p.label); // ['Run · start', 'LLM turn 1', 'LLM turn 2', 'Run · end']
1549
+ axis.map((p) => p.label); // ['Run · start', 'LLM turn 1', 'LLM turn 2', 'Run · end']
1516
1550
  ```
1517
1551
 
1518
- **Older peers.** `tagStops` (footprintjs 9.21) and `milestoneFromTags`
1519
- (agentfootprint 9.90) are read off their module namespaces at call time: on a
1520
- peer without them the legend prints raw names, the strip says *tag axis
1521
- unavailable*, and nothing else changes. The peer ranges are untouched.
1522
-
1523
1552
  ---
1524
1553
 
1525
1554
  ## Rendering your own detail pane
@@ -1895,12 +1924,14 @@ store's availability is a label. Headless: `bookmarkKey` · `toSidecar` ·
1895
1924
 
1896
1925
  ### `<TagPicker>` — the declared-tag legend and picker
1897
1926
 
1898
- `<TagPicker legend picked onPick available>`. The engineer view mounts it above
1927
+ `<TagPicker legend picked onPick available?>`. The engineer view mounts it above
1899
1928
  the chart on `granularity="group"` at the root level; `legend` is
1900
1929
  `tagLegend(structure, snapshot)` (`source: 'structure' | 'log'`), a pick is a
1901
1930
  list of tag names the lens hands to `tagAxisPositions(snapshot, picked, base)`
1902
1931
  (footprintjs 9.21's `tagStops`). Picking never moves the cursor; it changes the
1903
- list the one cursor is a step into.
1932
+ list the one cursor is a step into. `available` defaults to `true`; `false` holds
1933
+ the strip read-only — the legend still shows, every chip is disabled, and the
1934
+ strip says so (`TAG_PICKER_LABELS.tagAxisUnavailable`).
1904
1935
 
1905
1936
  ### `<BugReportButton>` — report a bug with the run attached, consent first
1906
1937
 
@@ -3,11 +3,11 @@ import { ToolChoiceCall, ToolChoiceSummary, AnswerAccount, AnswerAccountShownLea
3
3
  import * as agentfootprint from 'agentfootprint';
4
4
  import { CommentaryTemplates } from 'agentfootprint';
5
5
  import React__default, { Ref } from 'react';
6
- import { h as NavigationResult, C as CursorPosition, m as LensRecorder, a as LensCursor } from './lensCursorPort-Djm3W7K1.cjs';
7
- import { C as ChartGroupHighlight, H as Humanizer, b as BookmarkStore } from './tagAxis-CijjA_6P.cjs';
6
+ import { h as NavigationResult, C as CursorPosition, n as LensRecorder, a as LensCursor } from './lensCursorPort-nD3C6iQs.js';
7
+ import { C as ChartGroupHighlight, H as Humanizer, b as BookmarkStore } from './tagAxis-Cgk6vlcY.js';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
- import { L as LensCursorAt, S as SharedCursor } from './useSharedCursor-lgpWw94t.cjs';
10
+ import { L as LensCursorAt, S as SharedCursor } from './useSharedCursor-BdzFNO_C.js';
11
11
 
12
12
  /**
13
13
  * LensFlow — the canonical Lens chart renderer.
@@ -504,9 +504,10 @@ interface LensProps {
504
504
  * axis, a tag-filtered one, a drilled one) from the shared ADDRESS, and
505
505
  * every mover here — the strip, ◀ ▶, a chart click, `navigatorRef` — moves
506
506
  * that address. `step` is ignored while `shared` is given; `onStepChange`
507
- * still fires as an observation hook. A correction the lens makes on its
508
- * own (`at.clamped`, an address this axis cannot hold) never rewrites the
509
- * address: a tab derives a step, only a mover changes the address.
507
+ * still fires for explicit moves. An address this axis cannot hold shows
508
+ * no position; it is never corrected to a neighbouring stop. Axis changes
509
+ * and drills preserve the address. The transport's Latest button jumps to
510
+ * the current end; that held address stays fixed if the recording grows.
510
511
  *
511
512
  * ```tsx
512
513
  * const shared = useSharedCursor(recorder);
@@ -680,6 +681,12 @@ interface LensDetailSlotProps {
680
681
  */
681
682
  readonly cursor?: LensCursor;
682
683
  }
684
+ /**
685
+ * Source prefixes are not stage stops. Keep them outside the legacy stage
686
+ * shell so its clamp, final-answer slots and state folds cannot show a future
687
+ * state while an exact source prefix (including -1) is held by the host.
688
+ * Entering this mode resets local visual drill state, not the shared target.
689
+ */
683
690
  declare const Lens: React__default.FC<LensProps>;
684
691
 
685
692
  export { type LensProps as L, type ToolChoiceSource as T, type UseLensNavigatorArgs as U, Lens as a, type LensDetailSlotProps as b, type LensSlots as c, type LensTheme as d, type LensView as e, LensFlow as f, type LensFlowProps as g, type LensNavigator as h, type UseToolChoiceResult as i, useToolChoice as j, useLensNavigator as u };
@@ -3,11 +3,11 @@ import { ToolChoiceCall, ToolChoiceSummary, AnswerAccount, AnswerAccountShownLea
3
3
  import * as agentfootprint from 'agentfootprint';
4
4
  import { CommentaryTemplates } from 'agentfootprint';
5
5
  import React__default, { Ref } from 'react';
6
- import { h as NavigationResult, C as CursorPosition, m as LensRecorder, a as LensCursor } from './lensCursorPort-Djm3W7K1.js';
7
- import { C as ChartGroupHighlight, H as Humanizer, b as BookmarkStore } from './tagAxis-ClhV50a2.js';
6
+ import { h as NavigationResult, C as CursorPosition, n as LensRecorder, a as LensCursor } from './lensCursorPort-nD3C6iQs.cjs';
7
+ import { C as ChartGroupHighlight, H as Humanizer, b as BookmarkStore } from './tagAxis-COX6o7DJ.cjs';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
- import { L as LensCursorAt, S as SharedCursor } from './useSharedCursor-CdXdsXvD.js';
10
+ import { L as LensCursorAt, S as SharedCursor } from './useSharedCursor-DJTQSH0y.cjs';
11
11
 
12
12
  /**
13
13
  * LensFlow — the canonical Lens chart renderer.
@@ -504,9 +504,10 @@ interface LensProps {
504
504
  * axis, a tag-filtered one, a drilled one) from the shared ADDRESS, and
505
505
  * every mover here — the strip, ◀ ▶, a chart click, `navigatorRef` — moves
506
506
  * that address. `step` is ignored while `shared` is given; `onStepChange`
507
- * still fires as an observation hook. A correction the lens makes on its
508
- * own (`at.clamped`, an address this axis cannot hold) never rewrites the
509
- * address: a tab derives a step, only a mover changes the address.
507
+ * still fires for explicit moves. An address this axis cannot hold shows
508
+ * no position; it is never corrected to a neighbouring stop. Axis changes
509
+ * and drills preserve the address. The transport's Latest button jumps to
510
+ * the current end; that held address stays fixed if the recording grows.
510
511
  *
511
512
  * ```tsx
512
513
  * const shared = useSharedCursor(recorder);
@@ -680,6 +681,12 @@ interface LensDetailSlotProps {
680
681
  */
681
682
  readonly cursor?: LensCursor;
682
683
  }
684
+ /**
685
+ * Source prefixes are not stage stops. Keep them outside the legacy stage
686
+ * shell so its clamp, final-answer slots and state folds cannot show a future
687
+ * state while an exact source prefix (including -1) is held by the host.
688
+ * Entering this mode resets local visual drill state, not the shared target.
689
+ */
683
690
  declare const Lens: React__default.FC<LensProps>;
684
691
 
685
692
  export { type LensProps as L, type ToolChoiceSource as T, type UseLensNavigatorArgs as U, Lens as a, type LensDetailSlotProps as b, type LensSlots as c, type LensTheme as d, type LensView as e, LensFlow as f, type LensFlowProps as g, type LensNavigator as h, type UseToolChoiceResult as i, useToolChoice as j, useLensNavigator as u };
@@ -1,3 +1,130 @@
1
+ // src/core/group/resolveNavigation.ts
2
+ function execIndex(runtimeStageId) {
3
+ const hash = runtimeStageId.lastIndexOf("#");
4
+ if (hash < 0) return void 0;
5
+ const n = Number(runtimeStageId.slice(hash + 1));
6
+ return Number.isInteger(n) && n >= 0 ? n : void 0;
7
+ }
8
+ function resolveNavigation(positions, runtimeStageId) {
9
+ if (typeof runtimeStageId !== "string" || runtimeStageId === "") {
10
+ return {
11
+ ok: false,
12
+ reason: "no-id",
13
+ message: `No address was given. navigateTo needs a runtimeStageId \u2014 footprintjs's own address for a stage, like "llm#3" or "sf-tools/search#7".`
14
+ };
15
+ }
16
+ if (positions.length === 0) {
17
+ return {
18
+ ok: false,
19
+ reason: "empty-axis",
20
+ message: "This ruler has no stops yet, so there is nowhere to move. A run that has not committed a stage has no positions to scrub."
21
+ };
22
+ }
23
+ const exact = positions.findIndex((p) => p.runtimeStageId === runtimeStageId);
24
+ if (exact >= 0) {
25
+ return {
26
+ ok: true,
27
+ step: exact,
28
+ runtimeStageId,
29
+ match: "exact",
30
+ label: positions[exact]?.label ?? ""
31
+ };
32
+ }
33
+ const target = execIndex(runtimeStageId);
34
+ if (target === void 0) {
35
+ return {
36
+ ok: false,
37
+ reason: "not-on-axis",
38
+ message: `"${runtimeStageId}" is not a stop on this ruler, and it carries no #executionIndex, so no nearby stop can be worked out from it either. A runtimeStageId looks like "llm#3".`
39
+ };
40
+ }
41
+ const scopes = [];
42
+ const base = runtimeStageId.split("#")[0] ?? "";
43
+ for (let cut = base.lastIndexOf("/"); cut > 0; cut = base.lastIndexOf("/", cut - 1)) {
44
+ scopes.push(base.slice(0, cut));
45
+ }
46
+ for (const scope of scopes) {
47
+ let best = -1;
48
+ let bestIdx = -1;
49
+ for (let i = 0; i < positions.length; i += 1) {
50
+ const id = positions[i]?.runtimeStageId ?? "";
51
+ if ((id.split("#")[0] ?? "") !== scope) continue;
52
+ const idx = execIndex(id);
53
+ if (idx === void 0 || idx > target || idx <= bestIdx) continue;
54
+ best = i;
55
+ bestIdx = idx;
56
+ }
57
+ if (best >= 0) {
58
+ return {
59
+ ok: true,
60
+ step: best,
61
+ runtimeStageId: positions[best]?.runtimeStageId ?? "",
62
+ match: "enclosing",
63
+ label: positions[best]?.label ?? ""
64
+ };
65
+ }
66
+ }
67
+ let prev = -1;
68
+ let prevIdx = -1;
69
+ for (let i = 0; i < positions.length; i += 1) {
70
+ const idx = execIndex(positions[i]?.runtimeStageId ?? "");
71
+ if (idx === void 0 || idx > target || idx <= prevIdx) continue;
72
+ prev = i;
73
+ prevIdx = idx;
74
+ }
75
+ if (prev >= 0) {
76
+ const at = positions[prev];
77
+ return {
78
+ ok: false,
79
+ reason: "not-on-axis",
80
+ message: `"${runtimeStageId}" is not a stop on this ruler. The nearest stop before it is "${at.runtimeStageId}" (step ${prev}${at.label ? `, ${at.label}` : ""}) \u2014 offered, not taken: moving there is your call.`,
81
+ nearest: { runtimeStageId: at.runtimeStageId, step: prev, label: at.label }
82
+ };
83
+ }
84
+ return {
85
+ ok: false,
86
+ reason: "before-first-stop",
87
+ message: `"${runtimeStageId}" comes before this ruler's first stop, so there is nothing at or before it to stand on. The cursor did not move.`
88
+ };
89
+ }
90
+
91
+ // src/core/cursor/foldAt.ts
92
+ function foldCommitIdxOf(position) {
93
+ const opening = position.kind === "group-start" || position.kind === "user-in";
94
+ return opening && position.depth === 0 ? -1 : position.commitIdx;
95
+ }
96
+ function foldCursorOf(at) {
97
+ return { runtimeStageId: at.runtimeStageId, commitIdx: at.foldCommitIdx ?? at.commitIdx };
98
+ }
99
+
100
+ // src/core/cursor/lensCursor.ts
101
+ var EMPTY_READING = Object.freeze({
102
+ step: 0,
103
+ totalSteps: 0,
104
+ runtimeStageId: "",
105
+ commitIdx: -1,
106
+ foldCommitIdx: -1,
107
+ label: ""
108
+ });
109
+ function lensCursorFrom(positions, step, moveTo) {
110
+ const here = positions[step];
111
+ const at = here === void 0 ? Object.freeze({ ...EMPTY_READING, step, totalSteps: positions.length }) : Object.freeze({
112
+ step,
113
+ totalSteps: positions.length,
114
+ runtimeStageId: here.runtimeStageId,
115
+ commitIdx: here.commitIdx,
116
+ foldCommitIdx: foldCommitIdxOf(here),
117
+ label: here.label,
118
+ ...here.kind !== void 0 ? { kind: here.kind } : {}
119
+ });
120
+ return Object.freeze({
121
+ at,
122
+ total: positions.length,
123
+ resolve: (runtimeStageId) => resolveNavigation(positions, runtimeStageId),
124
+ moveTo
125
+ });
126
+ }
127
+
1
128
  // src/core/group/groupDisplayName.ts
2
129
  function groupDisplayName(label) {
3
130
  const named = label.subflowName ?? label.compositionName ?? label.primitiveKind;
@@ -222,133 +349,6 @@ function buildGroups(boundaryIndex) {
222
349
  return result;
223
350
  }
224
351
 
225
- // src/core/group/resolveNavigation.ts
226
- function execIndex(runtimeStageId) {
227
- const hash = runtimeStageId.lastIndexOf("#");
228
- if (hash < 0) return void 0;
229
- const n = Number(runtimeStageId.slice(hash + 1));
230
- return Number.isInteger(n) && n >= 0 ? n : void 0;
231
- }
232
- function resolveNavigation(positions, runtimeStageId) {
233
- if (typeof runtimeStageId !== "string" || runtimeStageId === "") {
234
- return {
235
- ok: false,
236
- reason: "no-id",
237
- message: `No address was given. navigateTo needs a runtimeStageId \u2014 footprintjs's own address for a stage, like "llm#3" or "sf-tools/search#7".`
238
- };
239
- }
240
- if (positions.length === 0) {
241
- return {
242
- ok: false,
243
- reason: "empty-axis",
244
- message: "This ruler has no stops yet, so there is nowhere to move. A run that has not committed a stage has no positions to scrub."
245
- };
246
- }
247
- const exact = positions.findIndex((p) => p.runtimeStageId === runtimeStageId);
248
- if (exact >= 0) {
249
- return {
250
- ok: true,
251
- step: exact,
252
- runtimeStageId,
253
- match: "exact",
254
- label: positions[exact]?.label ?? ""
255
- };
256
- }
257
- const target = execIndex(runtimeStageId);
258
- if (target === void 0) {
259
- return {
260
- ok: false,
261
- reason: "not-on-axis",
262
- message: `"${runtimeStageId}" is not a stop on this ruler, and it carries no #executionIndex, so no nearby stop can be worked out from it either. A runtimeStageId looks like "llm#3".`
263
- };
264
- }
265
- const scopes = [];
266
- const base = runtimeStageId.split("#")[0] ?? "";
267
- for (let cut = base.lastIndexOf("/"); cut > 0; cut = base.lastIndexOf("/", cut - 1)) {
268
- scopes.push(base.slice(0, cut));
269
- }
270
- for (const scope of scopes) {
271
- let best = -1;
272
- let bestIdx = -1;
273
- for (let i = 0; i < positions.length; i += 1) {
274
- const id = positions[i]?.runtimeStageId ?? "";
275
- if ((id.split("#")[0] ?? "") !== scope) continue;
276
- const idx = execIndex(id);
277
- if (idx === void 0 || idx > target || idx <= bestIdx) continue;
278
- best = i;
279
- bestIdx = idx;
280
- }
281
- if (best >= 0) {
282
- return {
283
- ok: true,
284
- step: best,
285
- runtimeStageId: positions[best]?.runtimeStageId ?? "",
286
- match: "enclosing",
287
- label: positions[best]?.label ?? ""
288
- };
289
- }
290
- }
291
- let prev = -1;
292
- let prevIdx = -1;
293
- for (let i = 0; i < positions.length; i += 1) {
294
- const idx = execIndex(positions[i]?.runtimeStageId ?? "");
295
- if (idx === void 0 || idx > target || idx <= prevIdx) continue;
296
- prev = i;
297
- prevIdx = idx;
298
- }
299
- if (prev >= 0) {
300
- const at = positions[prev];
301
- return {
302
- ok: false,
303
- reason: "not-on-axis",
304
- message: `"${runtimeStageId}" is not a stop on this ruler. The nearest stop before it is "${at.runtimeStageId}" (step ${prev}${at.label ? `, ${at.label}` : ""}) \u2014 offered, not taken: moving there is your call.`,
305
- nearest: { runtimeStageId: at.runtimeStageId, step: prev, label: at.label }
306
- };
307
- }
308
- return {
309
- ok: false,
310
- reason: "before-first-stop",
311
- message: `"${runtimeStageId}" comes before this ruler's first stop, so there is nothing at or before it to stand on. The cursor did not move.`
312
- };
313
- }
314
-
315
- // src/core/cursor/foldAt.ts
316
- function foldCommitIdxOf(position) {
317
- const opening = position.kind === "group-start" || position.kind === "user-in";
318
- return opening && position.depth === 0 ? -1 : position.commitIdx;
319
- }
320
- function foldCursorOf(at) {
321
- return { runtimeStageId: at.runtimeStageId, commitIdx: at.foldCommitIdx ?? at.commitIdx };
322
- }
323
-
324
- // src/core/cursor/lensCursor.ts
325
- var EMPTY_READING = Object.freeze({
326
- step: 0,
327
- totalSteps: 0,
328
- runtimeStageId: "",
329
- commitIdx: -1,
330
- foldCommitIdx: -1,
331
- label: ""
332
- });
333
- function lensCursorFrom(positions, step, moveTo) {
334
- const here = positions[step];
335
- const at = here === void 0 ? Object.freeze({ ...EMPTY_READING, step, totalSteps: positions.length }) : Object.freeze({
336
- step,
337
- totalSteps: positions.length,
338
- runtimeStageId: here.runtimeStageId,
339
- commitIdx: here.commitIdx,
340
- foldCommitIdx: foldCommitIdxOf(here),
341
- label: here.label,
342
- ...here.kind !== void 0 ? { kind: here.kind } : {}
343
- });
344
- return Object.freeze({
345
- at,
346
- total: positions.length,
347
- resolve: (runtimeStageId) => resolveNavigation(positions, runtimeStageId),
348
- moveTo
349
- });
350
- }
351
-
352
352
  // src/core/group/groupForRuntimeStageId.ts
353
353
  function pathOf(rid) {
354
354
  if (rid.length === 0) return [];
@@ -763,6 +763,10 @@ function scrubAxisFor(recorder, granularity, drillPath = []) {
763
763
  }
764
764
 
765
765
  export {
766
+ resolveNavigation,
767
+ foldCommitIdxOf,
768
+ foldCursorOf,
769
+ lensCursorFrom,
766
770
  groupDisplayName,
767
771
  groupDisplayNameForLabel,
768
772
  groupContainsCommit,
@@ -775,14 +779,10 @@ export {
775
779
  nextSnapStep,
776
780
  prevSnapStep,
777
781
  buildGroups,
778
- resolveNavigation,
779
- foldCommitIdxOf,
780
- foldCursorOf,
781
- lensCursorFrom,
782
782
  groupForRuntimeStageId,
783
783
  buildCommitSyncMap,
784
784
  commitAxisPositions,
785
785
  cursorPositionsAtDrill,
786
786
  scrubAxisFor
787
787
  };
788
- //# sourceMappingURL=chunk-WIQVUP5C.js.map
788
+ //# sourceMappingURL=chunk-4XZHSL5T.js.map