agentfootprint-lens 0.54.0 → 0.56.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
@@ -812,6 +812,41 @@ const href = to.ok ? `/runs/${runId}?step=${to.step}` : undefined;
812
812
 
813
813
  ---
814
814
 
815
+ ### One cursor across several lenses: `useSharedCursor`
816
+
817
+ A debug tool that mounts Why, Flow, Context and the Skill Graph over one
818
+ recording keeps ONE cursor. The lenses draw different axes — Flow every
819
+ commit, Why and Context the agent's milestones — so what the host holds must
820
+ be an ADDRESS on the record, not a step on an axis. `useSharedCursor` holds
821
+ that address and hands each lens a `LensCursor` for the axis it draws:
822
+
823
+ ```tsx
824
+ import { useSharedCursor, Lens, ContextView } from 'agentfootprint-lens';
825
+
826
+ const shared = useSharedCursor(recorder);
827
+
828
+ // Reads the address on the milestone axis; a click here moves the address.
829
+ <ContextView runner={recording} cursor={shared.forAxis('group')} />
830
+
831
+ // The Lens on the same owner (0.56.0): it derives its own step over
832
+ // whatever axis it draws — pick tags, drill, change granularity — and every
833
+ // mover in it moves the shared address.
834
+ <Lens recorder={recorder} granularity="step" shared={shared} />
835
+
836
+ // A view still on the older step + report contract, bridged into the same
837
+ // address — so a Skill Graph transport keeps working while you migrate one
838
+ // lens at a time.
839
+ <SkillGraphDebugger step={shared.forAxis('step').at.step} onStepChange={shared.onStepChange} … />
840
+ ```
841
+
842
+ The law: a tab DERIVES its step from the address (the exact stage when its
843
+ axis has it, else the stop that contains the commit); only a MOVER changes
844
+ the address; a visit to a coarser axis and back lands on the same commit.
845
+ With no move yet the address is the run's end, from which each axis derives
846
+ its own last stop; a new recorder drops the held address. Headless pieces on
847
+ `/core`: `stepForAddress`, `cursorForAddress`, `addressOf`. Design and the
848
+ facts the fixtures taught: docs/design/2026-09-shared-cursor.md.
849
+
815
850
  ## Time travel through one port
816
851
 
817
852
  **Nothing to do — this is how the cursor already moves.** Read it if you are
@@ -3,8 +3,8 @@ import { ToolChoiceCall, ToolChoiceSummary } from 'agentfootprint/observe';
3
3
  import * as agentfootprint from 'agentfootprint';
4
4
  import { CommentaryTemplates } from 'agentfootprint';
5
5
  import React__default, { Ref } from 'react';
6
- import { a as LensCursorReading, C as CursorPosition, L as LensCursor, e as NavigationResult, h as LensRecorder } from './lensCursor-BQyMCkdo.cjs';
7
- import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-Dh-lINvw.cjs';
6
+ import { a as LensCursorReading, C as CursorPosition, L as LensCursor, h as LensRecorder, e as NavigationResult } from './lensCursor-BQyMCkdo.cjs';
7
+ import { C as ChartGroupHighlight, c as LensCursorPort, z as CursorAddress, H as Humanizer, b as BookmarkStore } from './tagAxis-4LDsCmxZ.cjs';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
10
 
@@ -241,6 +241,26 @@ interface UseLensCursorResult {
241
241
  declare function clampStep(n: number, maxStep: number): number;
242
242
  declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, positions, }: UseLensCursorArgs): UseLensCursorResult;
243
243
 
244
+ type SharedAxis = 'step' | 'group';
245
+ interface SharedCursor {
246
+ /** The held address — `undefined` until a move, when the default (the run's end) applies. */
247
+ readonly address: CursorAddress | undefined;
248
+ /** The `LensCursor` a lens drawing this axis reads; its `moveTo` moves the shared address. */
249
+ forAxis(granularity: SharedAxis, drillPath?: readonly string[]): LensCursor;
250
+ /**
251
+ * The general form (0.56.0): the `LensCursor` over ANY positions a view
252
+ * draws — a tag-filtered axis, a drilled one — read under `drillPath`.
253
+ * `forAxis` is this over the library's own two axes. Same positions, same
254
+ * address → the same object, so a memo keyed on it holds.
255
+ */
256
+ over(positions: readonly CursorPosition[], drillPath?: readonly string[]): LensCursor;
257
+ /** Move the ONE cursor to an address — what every mover ends in. */
258
+ moveTo(address: CursorAddress): void;
259
+ /** The older contract, bridged: a `<Lens onStepChange>` or a transport reporting a step lands here. */
260
+ onStepChange(step: number, at: LensCursorAt): void;
261
+ }
262
+ declare function useSharedCursor(recorder: LensRecorder | undefined): SharedCursor;
263
+
244
264
  /**
245
265
  * useLensNavigator — move the ONE cursor to a stage by its address.
246
266
  *
@@ -603,6 +623,24 @@ interface LensProps {
603
623
  * Memoize via `useCallback` to avoid downstream re-renders.
604
624
  */
605
625
  readonly onStepChange?: (step: number, at: LensCursorAt) => void;
626
+ /**
627
+ * The HOST's one cursor across every lens it mounts (0.56.0) — the owner
628
+ * `useSharedCursor(recorder)` hands back. Pass it and this lens derives its
629
+ * own step over whatever axis it is drawing (the commit axis, the milestone
630
+ * axis, a tag-filtered one, a drilled one) from the shared ADDRESS, and
631
+ * every mover here — the strip, ◀ ▶, a chart click, `navigatorRef` — moves
632
+ * that address. `step` is ignored while `shared` is given; `onStepChange`
633
+ * still fires as an observation hook. A correction the lens makes on its
634
+ * own (`at.clamped`, an address this axis cannot hold) never rewrites the
635
+ * address: a tab derives a step, only a mover changes the address.
636
+ *
637
+ * ```tsx
638
+ * const shared = useSharedCursor(recorder);
639
+ * <Lens recorder={recorder} shared={shared} granularity="group" />
640
+ * <ContextView runner={runner} cursor={shared.forAxis('group')} />
641
+ * ```
642
+ */
643
+ readonly shared?: SharedCursor;
606
644
  /**
607
645
  * Move the cursor to a STAGE, by its address — the pointing half of the
608
646
  * cursor API. `step` / `onStepChange` let a host OWN the cursor; this lets
@@ -759,4 +797,4 @@ interface LensDetailSlotProps {
759
797
  }
760
798
  declare const Lens: React__default.FC<LensProps>;
761
799
 
762
- export { type LensProps as L, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type LensNavigator as j, type UseLensCursorResult as k, type UseLensNavigatorArgs as l, type UseToolChoiceResult as m, clampStep as n, useLensNavigator as o, useToolChoice as p, useLensCursor as u };
800
+ export { type LensProps as L, type SharedAxis as S, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type LensNavigator as j, type SharedCursor as k, type UseLensCursorResult as l, type UseLensNavigatorArgs as m, type UseToolChoiceResult as n, clampStep as o, useLensNavigator as p, useSharedCursor as q, useToolChoice as r, useLensCursor as u };
@@ -3,8 +3,8 @@ import { ToolChoiceCall, ToolChoiceSummary } from 'agentfootprint/observe';
3
3
  import * as agentfootprint from 'agentfootprint';
4
4
  import { CommentaryTemplates } from 'agentfootprint';
5
5
  import React__default, { Ref } from 'react';
6
- import { a as LensCursorReading, C as CursorPosition, L as LensCursor, e as NavigationResult, h as LensRecorder } from './lensCursor-BQyMCkdo.js';
7
- import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-DuOMNXvq.js';
6
+ import { a as LensCursorReading, C as CursorPosition, L as LensCursor, h as LensRecorder, e as NavigationResult } from './lensCursor-BQyMCkdo.js';
7
+ import { C as ChartGroupHighlight, c as LensCursorPort, z as CursorAddress, H as Humanizer, b as BookmarkStore } from './tagAxis-DyLdm6ah.js';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
10
 
@@ -241,6 +241,26 @@ interface UseLensCursorResult {
241
241
  declare function clampStep(n: number, maxStep: number): number;
242
242
  declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, positions, }: UseLensCursorArgs): UseLensCursorResult;
243
243
 
244
+ type SharedAxis = 'step' | 'group';
245
+ interface SharedCursor {
246
+ /** The held address — `undefined` until a move, when the default (the run's end) applies. */
247
+ readonly address: CursorAddress | undefined;
248
+ /** The `LensCursor` a lens drawing this axis reads; its `moveTo` moves the shared address. */
249
+ forAxis(granularity: SharedAxis, drillPath?: readonly string[]): LensCursor;
250
+ /**
251
+ * The general form (0.56.0): the `LensCursor` over ANY positions a view
252
+ * draws — a tag-filtered axis, a drilled one — read under `drillPath`.
253
+ * `forAxis` is this over the library's own two axes. Same positions, same
254
+ * address → the same object, so a memo keyed on it holds.
255
+ */
256
+ over(positions: readonly CursorPosition[], drillPath?: readonly string[]): LensCursor;
257
+ /** Move the ONE cursor to an address — what every mover ends in. */
258
+ moveTo(address: CursorAddress): void;
259
+ /** The older contract, bridged: a `<Lens onStepChange>` or a transport reporting a step lands here. */
260
+ onStepChange(step: number, at: LensCursorAt): void;
261
+ }
262
+ declare function useSharedCursor(recorder: LensRecorder | undefined): SharedCursor;
263
+
244
264
  /**
245
265
  * useLensNavigator — move the ONE cursor to a stage by its address.
246
266
  *
@@ -603,6 +623,24 @@ interface LensProps {
603
623
  * Memoize via `useCallback` to avoid downstream re-renders.
604
624
  */
605
625
  readonly onStepChange?: (step: number, at: LensCursorAt) => void;
626
+ /**
627
+ * The HOST's one cursor across every lens it mounts (0.56.0) — the owner
628
+ * `useSharedCursor(recorder)` hands back. Pass it and this lens derives its
629
+ * own step over whatever axis it is drawing (the commit axis, the milestone
630
+ * axis, a tag-filtered one, a drilled one) from the shared ADDRESS, and
631
+ * every mover here — the strip, ◀ ▶, a chart click, `navigatorRef` — moves
632
+ * that address. `step` is ignored while `shared` is given; `onStepChange`
633
+ * still fires as an observation hook. A correction the lens makes on its
634
+ * own (`at.clamped`, an address this axis cannot hold) never rewrites the
635
+ * address: a tab derives a step, only a mover changes the address.
636
+ *
637
+ * ```tsx
638
+ * const shared = useSharedCursor(recorder);
639
+ * <Lens recorder={recorder} shared={shared} granularity="group" />
640
+ * <ContextView runner={runner} cursor={shared.forAxis('group')} />
641
+ * ```
642
+ */
643
+ readonly shared?: SharedCursor;
606
644
  /**
607
645
  * Move the cursor to a STAGE, by its address — the pointing half of the
608
646
  * cursor API. `step` / `onStepChange` let a host OWN the cursor; this lets
@@ -759,4 +797,4 @@ interface LensDetailSlotProps {
759
797
  }
760
798
  declare const Lens: React__default.FC<LensProps>;
761
799
 
762
- export { type LensProps as L, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type LensNavigator as j, type UseLensCursorResult as k, type UseLensNavigatorArgs as l, type UseToolChoiceResult as m, clampStep as n, useLensNavigator as o, useToolChoice as p, useLensCursor as u };
800
+ export { type LensProps as L, type SharedAxis as S, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type LensNavigator as j, type SharedCursor as k, type UseLensCursorResult as l, type UseLensNavigatorArgs as m, type UseToolChoiceResult as n, clampStep as o, useLensNavigator as p, useSharedCursor as q, useToolChoice as r, useLensCursor as u };
@@ -2965,8 +2965,9 @@ var Lens = ({
2965
2965
  commentaryTemplates,
2966
2966
  toolChoice,
2967
2967
  granularity = "step",
2968
- step: controlledStep,
2969
- onStepChange,
2968
+ step: controlledStepProp,
2969
+ onStepChange: onStepChangeProp,
2970
+ shared,
2970
2971
  navigatorRef,
2971
2972
  slots,
2972
2973
  bookmarkStore
@@ -3079,6 +3080,15 @@ var Lens = ({
3079
3080
  },
3080
3081
  [cursorPositions]
3081
3082
  );
3083
+ const sharedOver = shared !== void 0 ? shared.over(cursorPositions, drillPath) : void 0;
3084
+ const controlledStep = sharedOver !== void 0 ? sharedOver.at.step : controlledStepProp;
3085
+ const onStepChange = useCallback6(
3086
+ (n, at) => {
3087
+ if (sharedOver !== void 0 && !at.clamped) sharedOver.moveTo(n);
3088
+ onStepChangeProp?.(n, at);
3089
+ },
3090
+ [sharedOver, onStepChangeProp]
3091
+ );
3082
3092
  const {
3083
3093
  step: focusStep,
3084
3094
  isLive,
@@ -3102,6 +3112,7 @@ var Lens = ({
3102
3112
  const prev = seatRef.current;
3103
3113
  seatRef.current = { positions: cursorPositions, step: focusStep, tagAxis };
3104
3114
  if (prev.tagAxis === tagAxis) return;
3115
+ if (shared !== void 0) return;
3105
3116
  const commit = prev.positions[prev.step]?.commitIdx;
3106
3117
  if (commit === void 0 || commit < 0) return;
3107
3118
  const target = stepForCommitIdx(cursorPositions, commit);
@@ -4775,4 +4786,4 @@ export {
4775
4786
  useBookmarkSidecar,
4776
4787
  Lens
4777
4788
  };
4778
- //# sourceMappingURL=chunk-UYL7DUOI.js.map
4789
+ //# sourceMappingURL=chunk-7LOCALIV.js.map