agentfootprint-lens 0.45.0 → 0.46.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 (39) hide show
  1. package/README.md +133 -0
  2. package/dist/{Lens-B039-UGw.d.cts → Lens-6elRg27j.d.cts} +22 -5
  3. package/dist/{Lens-ey1EgN9i.d.ts → Lens-Bdz-JzVm.d.ts} +22 -5
  4. package/dist/{chunk-YEJJOPOZ.js → chunk-2UOQEE4U.js} +2 -2
  5. package/dist/{chunk-LRGT7UQL.js → chunk-A4EIPCLY.js} +52 -31
  6. package/dist/chunk-A4EIPCLY.js.map +1 -0
  7. package/dist/{chunk-QNMA25X5.js → chunk-AUGRQKRL.js} +18 -6
  8. package/dist/chunk-AUGRQKRL.js.map +1 -0
  9. package/dist/{chunk-CGR5VFJU.js → chunk-SJEKKUTI.js} +113 -3
  10. package/dist/chunk-SJEKKUTI.js.map +1 -0
  11. package/dist/{chunk-5YSPWSLW.js → chunk-WN2RJKEH.js} +2 -2
  12. package/dist/core.cjs +125 -11
  13. package/dist/core.cjs.map +1 -1
  14. package/dist/core.d.cts +2 -2
  15. package/dist/core.d.ts +2 -2
  16. package/dist/core.js +6 -2
  17. package/dist/index.cjs +190 -44
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +34 -5
  20. package/dist/index.d.ts +34 -5
  21. package/dist/index.js +9 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/{scrubAxisFor-Cz214OHj.d.ts → lensStops-DAc3GB8G.d.ts} +157 -1
  24. package/dist/{scrubAxisFor-CNceBlz-.d.cts → lensStops-hQ10t3Lt.d.cts} +157 -1
  25. package/dist/skillgraph.cjs +17 -5
  26. package/dist/skillgraph.cjs.map +1 -1
  27. package/dist/skillgraph.js +2 -2
  28. package/dist/why.cjs +186 -40
  29. package/dist/why.cjs.map +1 -1
  30. package/dist/why.d.cts +5 -4
  31. package/dist/why.d.ts +5 -4
  32. package/dist/why.js +7 -3
  33. package/dist/why.js.map +1 -1
  34. package/package.json +3 -3
  35. package/dist/chunk-CGR5VFJU.js.map +0 -1
  36. package/dist/chunk-LRGT7UQL.js.map +0 -1
  37. package/dist/chunk-QNMA25X5.js.map +0 -1
  38. /package/dist/{chunk-YEJJOPOZ.js.map → chunk-2UOQEE4U.js.map} +0 -0
  39. /package/dist/{chunk-5YSPWSLW.js.map → chunk-WN2RJKEH.js.map} +0 -0
package/README.md CHANGED
@@ -808,6 +808,133 @@ const href = to.ok ? `/runs/${runId}?step=${to.step}` : undefined;
808
808
 
809
809
  ---
810
810
 
811
+ ## Time travel through one port
812
+
813
+ **Nothing to do — this is how the cursor already moves.** Read it if you are
814
+ building a second surface over the same run, or wondering why the ruler behaves
815
+ identically in the Why Lens, the Flow Lens and your own view.
816
+
817
+ ### Why
818
+
819
+ Every consumer of a footprintjs run was writing the same six lines: clamp at
820
+ zero, clamp at the end, resolve an address to a stop, decide what a miss does.
821
+ The lens had them in `<TimeTravel>`, the flow view had its own copy, and a
822
+ dashboard had a third — and they disagreed at exactly the places nobody tests: a
823
+ ▶ at the last stop, a jump to a stage that is not on this ruler, a step stored
824
+ from a longer run.
825
+
826
+ footprintjs 9.17 owns that arithmetic now. `timeTravel(snapshot, { strategy })`
827
+ is the **reader's cursor over a finished trace**: `first` / `last` / `prev` /
828
+ `next` / `jumpTo`, one position, and one law — **a refused move never moves the
829
+ cursor**, it returns the reason and the nearest stop it could name. Since
830
+ 0.46.0 the lens's movement goes through it.
831
+
832
+ **Which stops exist did not change.** The lens still computes its own axis —
833
+ milestones banded by iteration for the Why reading, one stop per executed stage
834
+ for the Flow reading, drill-scoped either way — with the same labels, the same
835
+ order and the same kinds. Those positions ARE the port's stops, through a
836
+ lens-owned `TimeTravelStrategy`. The library decides *where a move lands*; the
837
+ lens decides *what there is to land on*.
838
+
839
+ **Still one cursor.** The port instance is a calculator, not a position: it is
840
+ re-seated on the step the lens owns before every question and remembers nothing
841
+ between them. `<Lens step onStepChange>` is unchanged, and so is everything a
842
+ panel reads.
843
+
844
+ ### The port, headless
845
+
846
+ ```ts
847
+ import { scrubAxisFor, openLensCursor } from 'agentfootprint-lens/core';
848
+
849
+ const positions = scrubAxisFor(recorder, 'group'); // the ruler, as data
850
+ const cursor = openLensCursor(positions);
851
+
852
+ cursor.next(4).step; // 5
853
+ cursor.next(positions.length - 1);
854
+ // { step: <last>, moved: false, reason: 'clamped', clamped: false }
855
+
856
+ cursor.toStep(2, 9999);
857
+ // { step: <last>, moved: true, clamped: true } ← off the axis, said so
858
+
859
+ cursor.toAddress(2, 'call-llm#18');
860
+ // { ok: true, step: 7 }
861
+ cursor.toAddress(2, 'never-ran#4');
862
+ // { ok: false, reason: 'miss', nearest: <the closest stop the library
863
+ // could name — the same stage at another execution index if this axis
864
+ // holds one, otherwise the last stop whose execution index is not past
865
+ // the one you asked for> }
866
+ // the cursor is still at 2 — a miss is offered, never taken
867
+
868
+ cursor.positionAt(7);
869
+ // the LENS position: runtimeGroupId, depth, coActiveGroupIds, milestone —
870
+ // everything the port's `Stop` has no slot for, unchanged and unflattened
871
+ ```
872
+
873
+ `openLensCursor` answers **where**, never **what**: it has no `stateAt`, no
874
+ `changedSince`, no `drill`. Those are folds over the run's commit log, and a
875
+ fold wants the run's snapshot — which movement never reads. Want them over this
876
+ same ruler? Build the library's cursor directly, with the lens's stops:
877
+
878
+ ```ts
879
+ import { timeTravel } from 'footprintjs/trace';
880
+ import { scrubAxisFor, lensStopsStrategy } from 'agentfootprint-lens/core';
881
+
882
+ // A LIVE run. `runner.getLastSnapshot()` IS footprintjs's own snapshot, so it
883
+ // is already a `TimeTravelSource` and the two libraries meet with no cast.
884
+ const snapshot = runner.getLastSnapshot();
885
+ if (snapshot) {
886
+ const positions = scrubAxisFor(recorder, 'group');
887
+ const tt = timeTravel(snapshot, { strategy: lensStopsStrategy(positions) });
888
+ tt.jumpTo(7);
889
+ tt.changedSince(); // the keys this stop's slice of the log wrote
890
+ tt.stateAt().state; // state as of that stop, detached
891
+ }
892
+ ```
893
+
894
+ A **stored** recording needs one narrowing today, and the reason is honest on
895
+ both sides: the lens types `Recording.snapshot.commitLog` as `readonly
896
+ unknown[]` — a recording arrives as parsed JSON, and the lens will not claim
897
+ that what came back off disk is a `CommitBundle` — while `timeTravel` takes
898
+ `TimeTravelSource`, whose `commitLog` is `readonly CommitBundle[]`. So a
899
+ replayed run has to say so at the seam:
900
+
901
+ ```ts
902
+ import type { TimeTravelSource } from 'footprintjs/trace';
903
+
904
+ const stored = recording.snapshot as unknown as TimeTravelSource;
905
+ const tt = timeTravel(stored, { strategy: lensStopsStrategy(positions) });
906
+ ```
907
+
908
+ That cast disappears — with no change here — the day footprintjs widens
909
+ `TimeTravelSource.commitLog` to accept an unvalidated `readonly unknown[]` and
910
+ does the per-bundle narrowing where it folds, which is the only place that can
911
+ honestly do it. It is reported upstream as a port gap. **Movement never needs
912
+ this**: `openLensCursor` reads the stops and no snapshot at all, which is why
913
+ the lens's own cursor is unaffected either way.
914
+
915
+ ### Two axes, and drilling
916
+
917
+ Rebuild the port when the positions change; its identity is that list's, which
918
+ is already memoised per (recording, axis, drill level):
919
+
920
+ ```ts
921
+ const port = useMemo(() => openLensCursor(positions), [positions]);
922
+ ```
923
+
924
+ Drilling rebuilds the axis — the drilled group's own stops, which is a
925
+ different set — so it rebuilds the port. The lens does **not** use the port's
926
+ `drill()` for this: `drill()` opens a subflow's own commit log, and a lens drill
927
+ level is not always a subflow (it is a group, and a subflow whose stages commit
928
+ in their own memory scope is reconstructed from the runtime overlay). Using it
929
+ would change which stops appear at a drill level, which is the one thing this
930
+ change refuses to do.
931
+
932
+ `snapSteps` also stays the lens's. Snaps narrow ◀ ▶ to a *subset* of the axis
933
+ while the cursor may be parked between two of them — a question `jumpTo` does
934
+ not answer, since it takes an exact stop.
935
+
936
+ ---
937
+
811
938
  ## Rendering your own detail pane
812
939
 
813
940
  `slots.detail` replaces the CONTENT of the shipped right column. The column
@@ -1112,6 +1239,12 @@ so instead of drawing an empty graph.
1112
1239
  / CLI view on the same primitives — see `ChangeNotifier`'s JSDoc for adapter
1113
1240
  snippets.
1114
1241
 
1242
+ The cursor is headless too: `scrubAxisFor` builds the ruler, `openLensCursor`
1243
+ moves along it through footprintjs's `timeTravel()` port, `resolveNavigation`
1244
+ turns an address into a step with every rung named, and `stepForCommitIdx`
1245
+ carries a position between the two axes. See
1246
+ [Time travel through one port](#time-travel-through-one-port).
1247
+
1115
1248
  ---
1116
1249
 
1117
1250
  ## Why this design
@@ -4,7 +4,7 @@ import * as agentfootprint from 'agentfootprint';
4
4
  import { CommentaryTemplates } from 'agentfootprint';
5
5
  import React__default, { Ref } from 'react';
6
6
  import { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-o4E4cM_1.cjs';
7
- import { C as ChartGroupHighlight, H as Humanizer } from './scrubAxisFor-CNceBlz-.cjs';
7
+ import { C as ChartGroupHighlight, a as LensCursorPort, H as Humanizer } from './lensStops-hQ10t3Lt.cjs';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
10
 
@@ -153,6 +153,7 @@ declare const LensFlow: React__default.FC<LensFlowProps>;
153
153
  * Omit `controlledStep` and nothing changes: the hook keeps today's internal
154
154
  * state and today's auto-advance, byte for byte.
155
155
  */
156
+
156
157
  /**
157
158
  * Where the cursor landed, in all three units the lens knows — handed to
158
159
  * `onStepChange` alongside the step so a host never has to reverse-engineer
@@ -178,9 +179,13 @@ interface LensCursorAt {
178
179
  /** What kind of stop this is. Absent when the axis is empty. */
179
180
  readonly kind?: string;
180
181
  /**
181
- * `true` when this call is Lens CORRECTING an out-of-range `step` you
182
- * passed (or one an internal jump produced), not a move someone made.
183
- * Store the corrected value and the two cursors agree again.
182
+ * `true` when this call is Lens CORRECTING a step that is not a position on
183
+ * the axis it is now read against, not a move someone made. That is an
184
+ * out-of-range `step` you passed, one an internal jump produced, OR — when
185
+ * you passed no `step` at all — the lens's own remembered step after the
186
+ * axis shrank under it. Uncontrolled snaps are reported too, so this flag
187
+ * means the same thing in both modes. Store the corrected value and the two
188
+ * cursors agree again.
184
189
  */
185
190
  readonly clamped: boolean;
186
191
  }
@@ -201,6 +206,18 @@ interface UseLensCursorArgs {
201
206
  readonly maxStep: number;
202
207
  /** Resolve a step to its address. Called only when a move is reported. */
203
208
  readonly describe: (step: number) => LensCursorPlace;
209
+ /**
210
+ * The MOVEMENT port — footprintjs 9.17's reader cursor over the Lens's own
211
+ * stops (`openLensCursor(positions)`). When present, every move this funnel
212
+ * makes is decided by the library: where a step lands, what happens at the
213
+ * ends of the axis, and what an out-of-range ask does. Omit it and the
214
+ * funnel keeps its own arithmetic, unchanged — which is what the hook's own
215
+ * tests drive, so the two readings can be compared.
216
+ *
217
+ * It is NOT a second cursor: the port is re-seated on the step this hook
218
+ * owns before every question and remembers nothing between them.
219
+ */
220
+ readonly port?: LensCursorPort | undefined;
204
221
  }
205
222
  interface UseLensCursorResult {
206
223
  /** The cursor to render. */
@@ -215,7 +232,7 @@ interface UseLensCursorResult {
215
232
  * are not positions, so they clamp too — and the clamp is always reported.
216
233
  */
217
234
  declare function clampStep(n: number, maxStep: number): number;
218
- declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, }: UseLensCursorArgs): UseLensCursorResult;
235
+ declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, }: UseLensCursorArgs): UseLensCursorResult;
219
236
 
220
237
  /**
221
238
  * useLensNavigator — move the ONE cursor to a stage by its address.
@@ -4,7 +4,7 @@ import * as agentfootprint from 'agentfootprint';
4
4
  import { CommentaryTemplates } from 'agentfootprint';
5
5
  import React__default, { Ref } from 'react';
6
6
  import { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-o4E4cM_1.js';
7
- import { C as ChartGroupHighlight, H as Humanizer } from './scrubAxisFor-Cz214OHj.js';
7
+ import { C as ChartGroupHighlight, a as LensCursorPort, H as Humanizer } from './lensStops-DAc3GB8G.js';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
10
 
@@ -153,6 +153,7 @@ declare const LensFlow: React__default.FC<LensFlowProps>;
153
153
  * Omit `controlledStep` and nothing changes: the hook keeps today's internal
154
154
  * state and today's auto-advance, byte for byte.
155
155
  */
156
+
156
157
  /**
157
158
  * Where the cursor landed, in all three units the lens knows — handed to
158
159
  * `onStepChange` alongside the step so a host never has to reverse-engineer
@@ -178,9 +179,13 @@ interface LensCursorAt {
178
179
  /** What kind of stop this is. Absent when the axis is empty. */
179
180
  readonly kind?: string;
180
181
  /**
181
- * `true` when this call is Lens CORRECTING an out-of-range `step` you
182
- * passed (or one an internal jump produced), not a move someone made.
183
- * Store the corrected value and the two cursors agree again.
182
+ * `true` when this call is Lens CORRECTING a step that is not a position on
183
+ * the axis it is now read against, not a move someone made. That is an
184
+ * out-of-range `step` you passed, one an internal jump produced, OR — when
185
+ * you passed no `step` at all — the lens's own remembered step after the
186
+ * axis shrank under it. Uncontrolled snaps are reported too, so this flag
187
+ * means the same thing in both modes. Store the corrected value and the two
188
+ * cursors agree again.
184
189
  */
185
190
  readonly clamped: boolean;
186
191
  }
@@ -201,6 +206,18 @@ interface UseLensCursorArgs {
201
206
  readonly maxStep: number;
202
207
  /** Resolve a step to its address. Called only when a move is reported. */
203
208
  readonly describe: (step: number) => LensCursorPlace;
209
+ /**
210
+ * The MOVEMENT port — footprintjs 9.17's reader cursor over the Lens's own
211
+ * stops (`openLensCursor(positions)`). When present, every move this funnel
212
+ * makes is decided by the library: where a step lands, what happens at the
213
+ * ends of the axis, and what an out-of-range ask does. Omit it and the
214
+ * funnel keeps its own arithmetic, unchanged — which is what the hook's own
215
+ * tests drive, so the two readings can be compared.
216
+ *
217
+ * It is NOT a second cursor: the port is re-seated on the step this hook
218
+ * owns before every question and remembers nothing between them.
219
+ */
220
+ readonly port?: LensCursorPort | undefined;
204
221
  }
205
222
  interface UseLensCursorResult {
206
223
  /** The cursor to render. */
@@ -215,7 +232,7 @@ interface UseLensCursorResult {
215
232
  * are not positions, so they clamp too — and the clamp is always reported.
216
233
  */
217
234
  declare function clampStep(n: number, maxStep: number): number;
218
- declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, }: UseLensCursorArgs): UseLensCursorResult;
235
+ declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, }: UseLensCursorArgs): UseLensCursorResult;
219
236
 
220
237
  /**
221
238
  * useLensNavigator — move the ONE cursor to a stage by its address.
@@ -6,7 +6,7 @@ import {
6
6
  describeReceived,
7
7
  refusalDestinationFor,
8
8
  useNarrowRow
9
- } from "./chunk-QNMA25X5.js";
9
+ } from "./chunk-AUGRQKRL.js";
10
10
  import {
11
11
  selectSkillBeatAt,
12
12
  selectSkillBeats,
@@ -1492,4 +1492,4 @@ export {
1492
1492
  SKILL_GRAPH_READS,
1493
1493
  SkillGraphDebugger
1494
1494
  };
1495
- //# sourceMappingURL=chunk-YEJJOPOZ.js.map
1495
+ //# sourceMappingURL=chunk-2UOQEE4U.js.map
@@ -6,16 +6,17 @@ import {
6
6
  cursorProvenance,
7
7
  isFrameworkChartNode,
8
8
  makeTeachingHumanizer,
9
+ openLensCursor,
9
10
  selectHops,
10
11
  selectToolChoiceCall,
11
12
  structureGraphFromRunner
12
- } from "./chunk-CGR5VFJU.js";
13
+ } from "./chunk-SJEKKUTI.js";
13
14
  import {
14
15
  MODE_PALETTES,
15
16
  T,
16
17
  TimeTravel,
17
18
  useNarrowRow
18
- } from "./chunk-QNMA25X5.js";
19
+ } from "./chunk-AUGRQKRL.js";
19
20
  import {
20
21
  bandChartGroup,
21
22
  bandIndexOf,
@@ -914,14 +915,15 @@ function useLensCursor({
914
915
  controlledStep,
915
916
  onStepChange,
916
917
  maxStep,
917
- describe
918
+ describe,
919
+ port
918
920
  }) {
919
921
  const isControlled = controlledStep !== void 0;
920
922
  const [internalStep, setInternalStep] = useState(0);
921
923
  const [autoAdvance, setAutoAdvance] = useState(
922
924
  () => controlledStep === void 0 || clampStep(controlledStep, maxStep) >= maxStep
923
925
  );
924
- const step = isControlled ? clampStep(controlledStep, maxStep) : internalStep;
926
+ const step = clampStep(isControlled ? controlledStep : internalStep, maxStep);
925
927
  const isLive = autoAdvance && step >= maxStep;
926
928
  const describeRef = useRef(describe);
927
929
  describeRef.current = describe;
@@ -931,6 +933,8 @@ function useLensCursor({
931
933
  maxRef.current = maxStep;
932
934
  const stepRef = useRef(step);
933
935
  stepRef.current = step;
936
+ const portRef = useRef(port);
937
+ portRef.current = port;
934
938
  const notify = useCallback2((n, clamped) => {
935
939
  const cb = onChangeRef.current;
936
940
  if (!cb) return;
@@ -946,10 +950,12 @@ function useLensCursor({
946
950
  });
947
951
  }, []);
948
952
  const moveTo = useCallback2((n) => {
949
- if (controlledStep === void 0) setInternalStep(n);
950
- setAutoAdvance(n >= maxRef.current);
951
- if (n === stepRef.current) return;
952
- notify(n, false);
953
+ const from = stepRef.current;
954
+ const to = portRef.current !== void 0 ? portRef.current.toStep(from, n) : { step: n, moved: n !== from, clamped: false };
955
+ if (controlledStep === void 0) setInternalStep(to.step);
956
+ setAutoAdvance(to.step >= maxRef.current);
957
+ if (to.step === from && !to.clamped) return;
958
+ notify(to.step, to.clamped);
953
959
  }, [controlledStep, notify]);
954
960
  useEffect(() => {
955
961
  if (controlledStep === void 0) return;
@@ -963,18 +969,20 @@ function useLensCursor({
963
969
  }, [maxStep, autoAdvance, controlledStep, notify]);
964
970
  const warnedRef = useRef(void 0);
965
971
  useEffect(() => {
966
- if (controlledStep === void 0) return;
967
- const snapped = clampStep(controlledStep, maxStep);
968
- if (snapped === controlledStep) return;
969
- const key = `${controlledStep}/${maxStep}`;
970
- if (warnedRef.current !== key) {
971
- warnedRef.current = key;
972
- console.warn(
973
- `[agentfootprint-lens] <Lens step={${controlledStep}}> is not a position in this run: the cursor axis holds ${maxStep + 1} step${maxStep === 0 ? "" : "s"} (0\u2026${maxStep}). Lens moved to step ${snapped} and called onStepChange(${snapped}, { clamped: true }) so your state can follow. The axis GROWS as the run does, so store the value the callback hands you rather than a remembered number.`
974
- );
972
+ const owned = controlledStep !== void 0 ? controlledStep : internalStep;
973
+ const snapped = clampStep(owned, maxStep);
974
+ if (snapped === owned) return;
975
+ if (controlledStep !== void 0) {
976
+ const key = `${controlledStep}/${maxStep}`;
977
+ if (warnedRef.current !== key) {
978
+ warnedRef.current = key;
979
+ console.warn(
980
+ `[agentfootprint-lens] <Lens step={${controlledStep}}> is not a position in this run: the cursor axis holds ${maxStep + 1} step${maxStep === 0 ? "" : "s"} (0\u2026${maxStep}). Lens moved to step ${snapped} and called onStepChange(${snapped}, { clamped: true }) so your state can follow. The axis GROWS as the run does, so store the value the callback hands you rather than a remembered number.`
981
+ );
982
+ }
975
983
  }
976
984
  notify(snapped, true);
977
- }, [controlledStep, maxStep, notify]);
985
+ }, [controlledStep, internalStep, maxStep, notify]);
978
986
  return { step, isLive, moveTo };
979
987
  }
980
988
 
@@ -2631,6 +2639,16 @@ var Lens = ({
2631
2639
  );
2632
2640
  const stepCount = Math.max(1, cursorPositions.length);
2633
2641
  const maxStep = Math.max(0, stepCount - 1);
2642
+ const cursorPort = useMemo7(() => openLensCursor(cursorPositions), [cursorPositions]);
2643
+ const stepper = useMemo7(
2644
+ () => ({
2645
+ back: (from) => cursorPort.prev(from).step,
2646
+ forward: (from) => cursorPort.next(from).step,
2647
+ toFirst: (from) => cursorPort.first(from).step,
2648
+ toLast: (from) => cursorPort.last(from).step
2649
+ }),
2650
+ [cursorPort]
2651
+ );
2634
2652
  const describeStep = useCallback5(
2635
2653
  (n) => {
2636
2654
  const p = cursorPositions[n];
@@ -2651,7 +2669,8 @@ var Lens = ({
2651
2669
  controlledStep,
2652
2670
  onStepChange,
2653
2671
  maxStep,
2654
- describe: describeStep
2672
+ describe: describeStep,
2673
+ port: cursorPort
2655
2674
  });
2656
2675
  useLensNavigator({
2657
2676
  positions: cursorPositions,
@@ -2701,6 +2720,7 @@ var Lens = ({
2701
2720
  focusSeq: focusStep,
2702
2721
  onFocusChange: handleFocusChange,
2703
2722
  isLive,
2723
+ stepper,
2704
2724
  liveStreamLine
2705
2725
  }
2706
2726
  )
@@ -2722,6 +2742,8 @@ var Lens = ({
2722
2742
  focusStep,
2723
2743
  onFocusChange: handleFocusChange,
2724
2744
  isLive,
2745
+ stepper,
2746
+ cursorPort,
2725
2747
  stepStrip,
2726
2748
  showSummary,
2727
2749
  liveStreamLine,
@@ -2809,6 +2831,8 @@ var EngineerView = ({
2809
2831
  cursorRuntimeStageId,
2810
2832
  toolChoice,
2811
2833
  granularity,
2834
+ stepper,
2835
+ cursorPort,
2812
2836
  slots
2813
2837
  }) => {
2814
2838
  const bands = useMemo7(
@@ -3048,16 +3072,10 @@ var EngineerView = ({
3048
3072
  const DetailSlot = slots?.detail;
3049
3073
  const jumpToRuntimeStageId = useCallback5(
3050
3074
  (runtimeStageId) => {
3051
- const exact = cursorPositions.findIndex((p) => p.runtimeStageId === runtimeStageId);
3052
- if (exact >= 0) {
3053
- onFocusChange(exact);
3054
- return;
3055
- }
3056
- const stagePart = runtimeStageId.split("#")[0];
3057
- const byStage = cursorPositions.findIndex((p) => p.runtimeStageId.split("#")[0] === stagePart);
3058
- if (byStage >= 0) onFocusChange(byStage);
3075
+ const to = cursorPort.toAddress(focusStep, runtimeStageId);
3076
+ if (to.ok && to.step !== void 0) onFocusChange(to.step);
3059
3077
  },
3060
- [cursorPositions, onFocusChange]
3078
+ [cursorPort, focusStep, onFocusChange]
3061
3079
  );
3062
3080
  return /* @__PURE__ */ jsxs8(
3063
3081
  "div",
@@ -3109,7 +3127,8 @@ var EngineerView = ({
3109
3127
  total,
3110
3128
  focusSeq: focusStep,
3111
3129
  onFocusChange,
3112
- isLive
3130
+ isLive,
3131
+ stepper
3113
3132
  }
3114
3133
  ),
3115
3134
  granularity === "group" && frameworkHiddenCount > 0 && /* @__PURE__ */ jsxs8(
@@ -3899,6 +3918,7 @@ var AnalystView = ({
3899
3918
  focusSeq,
3900
3919
  onFocusChange,
3901
3920
  isLive,
3921
+ stepper,
3902
3922
  liveStreamLine
3903
3923
  }) => {
3904
3924
  return /* @__PURE__ */ jsxs8("div", { style: { display: "grid", gap: 16 }, children: [
@@ -3909,7 +3929,8 @@ var AnalystView = ({
3909
3929
  total,
3910
3930
  focusSeq,
3911
3931
  onFocusChange,
3912
- isLive
3932
+ isLive,
3933
+ stepper
3913
3934
  }
3914
3935
  ),
3915
3936
  /* @__PURE__ */ jsx9(Card, { title: "Commentary", children: /* @__PURE__ */ jsxs8("div", { style: { fontSize: 13, lineHeight: 1.6, fontFamily: T.fontSans }, children: [
@@ -4172,4 +4193,4 @@ export {
4172
4193
  WhereFrom,
4173
4194
  Lens
4174
4195
  };
4175
- //# sourceMappingURL=chunk-LRGT7UQL.js.map
4196
+ //# sourceMappingURL=chunk-A4EIPCLY.js.map