agentfootprint-lens 0.50.0 → 0.51.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 (46) hide show
  1. package/README.md +102 -0
  2. package/dist/{Lens-okdu_w0H.d.ts → Lens-Bw1em67x.d.cts} +49 -22
  3. package/dist/{Lens-DJJsMfud.d.cts → Lens-CDsSUiHJ.d.ts} +49 -22
  4. package/dist/{chunk-TBYKXQSA.js → chunk-7QSK7EJW.js} +87 -66
  5. package/dist/chunk-7QSK7EJW.js.map +1 -0
  6. package/dist/{chunk-AUGRQKRL.js → chunk-BJR32J6H.js} +2 -2
  7. package/dist/{chunk-K4CTXAB7.js → chunk-H63KQNY3.js} +2 -2
  8. package/dist/{chunk-2UOQEE4U.js → chunk-MS5XW7GH.js} +23 -13
  9. package/dist/{chunk-2UOQEE4U.js.map → chunk-MS5XW7GH.js.map} +1 -1
  10. package/dist/{chunk-LCIEJYZG.js → chunk-OXPEMAMG.js} +29 -2
  11. package/dist/chunk-OXPEMAMG.js.map +1 -0
  12. package/dist/{chunk-AIZIILHJ.js → chunk-ZZJ2OIQP.js} +2 -2
  13. package/dist/core.cjs +28 -0
  14. package/dist/core.cjs.map +1 -1
  15. package/dist/core.d.cts +5 -5
  16. package/dist/core.d.ts +5 -5
  17. package/dist/core.js +5 -3
  18. package/dist/index.cjs +84 -26
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +34 -16
  21. package/dist/index.d.ts +34 -16
  22. package/dist/index.js +8 -6
  23. package/dist/index.js.map +1 -1
  24. package/dist/{resolveNavigation-Ckr2nXHY.d.ts → lensCursor-B8HFFZgR.d.cts} +162 -1
  25. package/dist/{resolveNavigation-Ckr2nXHY.d.cts → lensCursor-B8HFFZgR.d.ts} +162 -1
  26. package/dist/{selectSkillFrameContext-DEk_qpXr.d.ts → selectSkillFrameContext-BE7lCVcB.d.cts} +1 -1
  27. package/dist/{selectSkillFrameContext-DjZDveZw.d.cts → selectSkillFrameContext-D2uHzhJJ.d.ts} +1 -1
  28. package/dist/skillgraph.cjs +48 -10
  29. package/dist/skillgraph.cjs.map +1 -1
  30. package/dist/skillgraph.d.cts +45 -10
  31. package/dist/skillgraph.d.ts +45 -10
  32. package/dist/skillgraph.js +5 -3
  33. package/dist/{tagAxis-C__DMHiD.d.ts → tagAxis-BUrsQ1fX.d.cts} +1 -1
  34. package/dist/{tagAxis-B598GxMJ.d.cts → tagAxis-Cqom3DPf.d.ts} +1 -1
  35. package/dist/why.cjs +156 -108
  36. package/dist/why.cjs.map +1 -1
  37. package/dist/why.d.cts +5 -5
  38. package/dist/why.d.ts +5 -5
  39. package/dist/why.js +6 -4
  40. package/dist/why.js.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-LCIEJYZG.js.map +0 -1
  43. package/dist/chunk-TBYKXQSA.js.map +0 -1
  44. /package/dist/{chunk-AUGRQKRL.js.map → chunk-BJR32J6H.js.map} +0 -0
  45. /package/dist/{chunk-K4CTXAB7.js.map → chunk-H63KQNY3.js.map} +0 -0
  46. /package/dist/{chunk-AIZIILHJ.js.map → chunk-ZZJ2OIQP.js.map} +0 -0
package/README.md CHANGED
@@ -1278,6 +1278,108 @@ Keep the slots object stable across renders (module scope or `useMemo`), same as
1278
1278
 
1279
1279
  ---
1280
1280
 
1281
+ ## One address, one cursor — for every view
1282
+
1283
+ > **A stage id is an ADDRESS, not a POSITION.** It says WHICH stage, never WHERE
1284
+ > on an axis. Only an axis can answer that, and it may honestly answer *"not
1285
+ > here"*.
1286
+
1287
+ Until 0.51.0 the same one cursor reached three views in three vocabularies, and
1288
+ every new view invented a fourth:
1289
+
1290
+ | view | handed | reads |
1291
+ |---|---|---|
1292
+ | skill graph | `cursorRuntimeStageId` + `onJumpTo` | the EVENT record |
1293
+ | a consumer's data graph | `{ step, total, stepOf(id), onStep }` | the COMMIT record |
1294
+ | Served tab / graph | `cursorRuntimeStageId` + `commitIdx` | the COMMIT record |
1295
+
1296
+ `LensCursor` is their superset, and every view now gets exactly it. It is three
1297
+ things and deliberately nothing more:
1298
+
1299
+ ```ts
1300
+ interface LensCursor {
1301
+ readonly at: LensCursorReading; // step · totalSteps · runtimeStageId · commitIdx · label · kind
1302
+ readonly total: number;
1303
+ resolve(runtimeStageId: string): NavigationResult; // an ADDRESS, answered honestly
1304
+ moveTo(step: number): void; // the ONE funnel
1305
+ }
1306
+ ```
1307
+
1308
+ **It holds no position of its own**, and no view may gain one: `at` is derived
1309
+ from the axis and the step on every render, `resolve` is a pure query, and
1310
+ `moveTo` is the same funnel the step strip, the ◀ ▶ buttons and the live
1311
+ auto-advance go through. Read it; never store it.
1312
+
1313
+ ### Adopting it — one line
1314
+
1315
+ ```tsx
1316
+ <Lens
1317
+ recorder={recorder}
1318
+ slots={{ detail: (p) => <SkillGraphDebugger recorder={p.recorder} cursor={p.cursor} /> }}
1319
+ />
1320
+ ```
1321
+
1322
+ `cursor` arrives on `LensDetailSlotProps` beside every prop that was already
1323
+ there, and `<SkillGraphDebugger cursor>` / `<ServedTab cursor>` are the narrow
1324
+ form of the props they already took. Nothing was removed; an explicit prop still
1325
+ wins wherever both are passed.
1326
+
1327
+ ### Placing an element by address
1328
+
1329
+ This is the behaviour the packet generalises. A real consumer's data-graph view
1330
+ took `stepOf(id)` and drew an element `unplaced` when the answer came back below
1331
+ zero — a number every view had to invent a meaning for. Written against
1332
+ `resolve`, the meaning *and the words* come from the library:
1333
+
1334
+ ```tsx
1335
+ function Element({ cursor, runtimeStageId, name }) {
1336
+ const to = cursor.resolve(runtimeStageId);
1337
+ if (!to.ok) {
1338
+ // Drawn, never hidden — omit never deny — and the sentence is the library's.
1339
+ return <li data-unplaced title={to.message}>{name}</li>;
1340
+ }
1341
+ return (
1342
+ <li data-enclosing={to.match === 'enclosing'} onClick={() => cursor.moveTo(to.step)}>
1343
+ {to.label}
1344
+ </li>
1345
+ );
1346
+ }
1347
+ ```
1348
+
1349
+ ### The three cases the contract names
1350
+
1351
+ Each is a real run, and each has a test measured on a frozen fixture:
1352
+
1353
+ 1. **The axis does not stop there.** A filtered or tag axis over an untagged
1354
+ stage. `resolve` refuses `'not-on-axis'`, carries a printable `message`, and
1355
+ OFFERS the nearest earlier stop in `nearest` — an offer, never a move.
1356
+ 2. **The id belongs to an inner log.** A subflow's stages commit into their own
1357
+ isolated log, so the axis holds the MOUNT rather than the stage. The ladder's
1358
+ `'enclosing'` rung answers `{ ok: true, match: 'enclosing' }` whose
1359
+ `runtimeStageId` is the mount's — a true landing at the granularity this axis
1360
+ has. Branch on `match` when you must tell "on it" from "inside it", and offer
1361
+ a drill to go deeper.
1362
+ 3. **The event has no stage at all.** Run start, run end. There is no id;
1363
+ `resolve('')` refuses `'no-id'` and your view says so rather than inventing
1364
+ an address.
1365
+
1366
+ ### Headless
1367
+
1368
+ ```ts
1369
+ import { scrubAxisFor, lensCursorFrom } from 'agentfootprint-lens/core';
1370
+
1371
+ const positions = scrubAxisFor(recorder, 'step');
1372
+ const cursor = lensCursorFrom(positions, 0, (step) => setStep(step));
1373
+ const to = cursor.resolve('llm#7');
1374
+ const href = to.ok ? `/run/${runId}?step=${to.step}` : undefined;
1375
+ ```
1376
+
1377
+ `stepForRuntimeStageId` still ships and still behaves byte for byte; it is
1378
+ **deprecated** in favour of `resolveNavigation` / `cursor.resolve`, the same
1379
+ ladder with its rungs named instead of flattened to `-1`.
1380
+
1381
+ ---
1382
+
1281
1383
  ## Theming
1282
1384
 
1283
1385
  **Lens inherits theme tokens from your app via CSS variables.** Set `--fp-*`
@@ -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 { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-Ckr2nXHY.js';
7
- import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-C__DMHiD.js';
6
+ import { a as LensCursorReading, C as CursorPosition, L as LensCursor, e as NavigationResult, h as LensRecorder } from './lensCursor-B8HFFZgR.cjs';
7
+ import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-BUrsQ1fX.cjs';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
10
 
@@ -158,26 +158,12 @@ declare const LensFlow: React__default.FC<LensFlowProps>;
158
158
  * Where the cursor landed, in all three units the lens knows — handed to
159
159
  * `onStepChange` alongside the step so a host never has to reverse-engineer
160
160
  * the lens's axis.
161
+ *
162
+ * It is a `LensCursorReading` (`/core`) plus the one thing a MOVE REPORT needs
163
+ * and a reading does not: `clamped`. One declaration of the reading's fields,
164
+ * so the report and the `LensCursor` every view is handed cannot drift.
161
165
  */
162
- interface LensCursorAt {
163
- /** The new cursor position, on the lens's step axis. Same number the
164
- * `step` prop takes. */
165
- readonly step: number;
166
- /** How many positions the axis has right now. Valid steps are
167
- * `0 … totalSteps - 1`. GROWS during a live run. */
168
- readonly totalSteps: number;
169
- /** The cursor's address in footprintjs's address space
170
- * (`[subflowPath/]stageId#executionIndex`) — the same string
171
- * `<TraceExplorerShell>` and `<RunSlider>` call `selectedRuntimeStageId` /
172
- * `cursorRuntimeStageId`. `''` when the axis is empty. */
173
- readonly runtimeStageId: string;
174
- /** The commit-log index this position anchors to. `-1` when unknown. */
175
- readonly commitIdx: number;
176
- /** The position's human label, as the step strip and the timeline spell it
177
- * ("Iteration 2", "Context 3", "Run · start"). */
178
- readonly label: string;
179
- /** What kind of stop this is. Absent when the axis is empty. */
180
- readonly kind?: string;
166
+ interface LensCursorAt extends LensCursorReading {
181
167
  /**
182
168
  * `true` when this call is Lens CORRECTING a step that is not a position on
183
169
  * the axis it is now read against, not a move someone made. That is an
@@ -218,6 +204,18 @@ interface UseLensCursorArgs {
218
204
  * owns before every question and remembers nothing between them.
219
205
  */
220
206
  readonly port?: LensCursorPort | undefined;
207
+ /**
208
+ * The ACTIVE scrub axis — the same list the lens draws its ruler from.
209
+ *
210
+ * Supplied only so this hook can also hand back the `LensCursor` every view
211
+ * receives (`cursor` below), which needs the axis to answer an ADDRESS. It
212
+ * is never consulted for movement: `maxStep`, `describe` and `port` decide
213
+ * that, exactly as they did before 0.51.0.
214
+ *
215
+ * Omit it and `cursor` is a cursor over an EMPTY axis — `resolve` refuses
216
+ * `'empty-axis'`, which is the honest answer for a caller that gave none.
217
+ */
218
+ readonly positions?: readonly CursorPosition[] | undefined;
221
219
  }
222
220
  interface UseLensCursorResult {
223
221
  /** The cursor to render. */
@@ -226,13 +224,22 @@ interface UseLensCursorResult {
226
224
  readonly isLive: boolean;
227
225
  /** The ONE way anything inside the lens moves the cursor. */
228
226
  readonly moveTo: (n: number) => void;
227
+ /**
228
+ * The same one cursor in the shape EVERY view is handed (0.51.0): a READING
229
+ * (`at`), the honest address query (`resolve`) and this very `moveTo`.
230
+ *
231
+ * Added BESIDE the three fields above — nothing moved, nothing was removed.
232
+ * It holds no position: it is rebuilt from (`positions`, `step`) on every
233
+ * render, so a view cannot end up rendering a stale copy.
234
+ */
235
+ readonly cursor: LensCursor;
229
236
  }
230
237
  /**
231
238
  * Snap a host-supplied step onto the axis. Non-finite and fractional values
232
239
  * are not positions, so they clamp too — and the clamp is always reported.
233
240
  */
234
241
  declare function clampStep(n: number, maxStep: number): number;
235
- declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, }: UseLensCursorArgs): UseLensCursorResult;
242
+ declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, positions, }: UseLensCursorArgs): UseLensCursorResult;
236
243
 
237
244
  /**
238
245
  * useLensNavigator — move the ONE cursor to a stage by its address.
@@ -721,6 +728,26 @@ interface LensDetailSlotProps {
721
728
  readonly recorder: LensRecorder;
722
729
  /** Move the ONE cursor — same funnel every built-in mover uses. */
723
730
  readonly onNavigate: (step: number) => void;
731
+ /**
732
+ * THE CURSOR, in the one vocabulary every view now shares (0.51.0) — the
733
+ * reading above (`cursor.at`), the axis's honest answer to an ADDRESS
734
+ * (`cursor.resolve(id)`), and the same funnel as `onNavigate`
735
+ * (`cursor.moveTo`).
736
+ *
737
+ * Everything else on this interface stays exactly as it was; this is the
738
+ * shape to reach for when your pane places elements BY ADDRESS, because the
739
+ * axis may honestly not stop where an address points:
740
+ *
741
+ * ```tsx
742
+ * const to = p.cursor.resolve(node.runtimeStageId);
743
+ * if (!to.ok) return <li data-unplaced title={to.message}>{node.name}</li>;
744
+ * return <li onClick={() => p.cursor.moveTo(to.step)}>{to.label}</li>;
745
+ * ```
746
+ *
747
+ * It holds no position of its own — it is rebuilt every render. Read it;
748
+ * never store it.
749
+ */
750
+ readonly cursor: LensCursor;
724
751
  }
725
752
  declare const Lens: React__default.FC<LensProps>;
726
753
 
@@ -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 { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-Ckr2nXHY.cjs';
7
- import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-B598GxMJ.cjs';
6
+ import { a as LensCursorReading, C as CursorPosition, L as LensCursor, e as NavigationResult, h as LensRecorder } from './lensCursor-B8HFFZgR.js';
7
+ import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-Cqom3DPf.js';
8
8
  import { NodeTypes } from '@xyflow/react';
9
9
  import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
10
10
 
@@ -158,26 +158,12 @@ declare const LensFlow: React__default.FC<LensFlowProps>;
158
158
  * Where the cursor landed, in all three units the lens knows — handed to
159
159
  * `onStepChange` alongside the step so a host never has to reverse-engineer
160
160
  * the lens's axis.
161
+ *
162
+ * It is a `LensCursorReading` (`/core`) plus the one thing a MOVE REPORT needs
163
+ * and a reading does not: `clamped`. One declaration of the reading's fields,
164
+ * so the report and the `LensCursor` every view is handed cannot drift.
161
165
  */
162
- interface LensCursorAt {
163
- /** The new cursor position, on the lens's step axis. Same number the
164
- * `step` prop takes. */
165
- readonly step: number;
166
- /** How many positions the axis has right now. Valid steps are
167
- * `0 … totalSteps - 1`. GROWS during a live run. */
168
- readonly totalSteps: number;
169
- /** The cursor's address in footprintjs's address space
170
- * (`[subflowPath/]stageId#executionIndex`) — the same string
171
- * `<TraceExplorerShell>` and `<RunSlider>` call `selectedRuntimeStageId` /
172
- * `cursorRuntimeStageId`. `''` when the axis is empty. */
173
- readonly runtimeStageId: string;
174
- /** The commit-log index this position anchors to. `-1` when unknown. */
175
- readonly commitIdx: number;
176
- /** The position's human label, as the step strip and the timeline spell it
177
- * ("Iteration 2", "Context 3", "Run · start"). */
178
- readonly label: string;
179
- /** What kind of stop this is. Absent when the axis is empty. */
180
- readonly kind?: string;
166
+ interface LensCursorAt extends LensCursorReading {
181
167
  /**
182
168
  * `true` when this call is Lens CORRECTING a step that is not a position on
183
169
  * the axis it is now read against, not a move someone made. That is an
@@ -218,6 +204,18 @@ interface UseLensCursorArgs {
218
204
  * owns before every question and remembers nothing between them.
219
205
  */
220
206
  readonly port?: LensCursorPort | undefined;
207
+ /**
208
+ * The ACTIVE scrub axis — the same list the lens draws its ruler from.
209
+ *
210
+ * Supplied only so this hook can also hand back the `LensCursor` every view
211
+ * receives (`cursor` below), which needs the axis to answer an ADDRESS. It
212
+ * is never consulted for movement: `maxStep`, `describe` and `port` decide
213
+ * that, exactly as they did before 0.51.0.
214
+ *
215
+ * Omit it and `cursor` is a cursor over an EMPTY axis — `resolve` refuses
216
+ * `'empty-axis'`, which is the honest answer for a caller that gave none.
217
+ */
218
+ readonly positions?: readonly CursorPosition[] | undefined;
221
219
  }
222
220
  interface UseLensCursorResult {
223
221
  /** The cursor to render. */
@@ -226,13 +224,22 @@ interface UseLensCursorResult {
226
224
  readonly isLive: boolean;
227
225
  /** The ONE way anything inside the lens moves the cursor. */
228
226
  readonly moveTo: (n: number) => void;
227
+ /**
228
+ * The same one cursor in the shape EVERY view is handed (0.51.0): a READING
229
+ * (`at`), the honest address query (`resolve`) and this very `moveTo`.
230
+ *
231
+ * Added BESIDE the three fields above — nothing moved, nothing was removed.
232
+ * It holds no position: it is rebuilt from (`positions`, `step`) on every
233
+ * render, so a view cannot end up rendering a stale copy.
234
+ */
235
+ readonly cursor: LensCursor;
229
236
  }
230
237
  /**
231
238
  * Snap a host-supplied step onto the axis. Non-finite and fractional values
232
239
  * are not positions, so they clamp too — and the clamp is always reported.
233
240
  */
234
241
  declare function clampStep(n: number, maxStep: number): number;
235
- declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, }: UseLensCursorArgs): UseLensCursorResult;
242
+ declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, port, positions, }: UseLensCursorArgs): UseLensCursorResult;
236
243
 
237
244
  /**
238
245
  * useLensNavigator — move the ONE cursor to a stage by its address.
@@ -721,6 +728,26 @@ interface LensDetailSlotProps {
721
728
  readonly recorder: LensRecorder;
722
729
  /** Move the ONE cursor — same funnel every built-in mover uses. */
723
730
  readonly onNavigate: (step: number) => void;
731
+ /**
732
+ * THE CURSOR, in the one vocabulary every view now shares (0.51.0) — the
733
+ * reading above (`cursor.at`), the axis's honest answer to an ADDRESS
734
+ * (`cursor.resolve(id)`), and the same funnel as `onNavigate`
735
+ * (`cursor.moveTo`).
736
+ *
737
+ * Everything else on this interface stays exactly as it was; this is the
738
+ * shape to reach for when your pane places elements BY ADDRESS, because the
739
+ * axis may honestly not stop where an address points:
740
+ *
741
+ * ```tsx
742
+ * const to = p.cursor.resolve(node.runtimeStageId);
743
+ * if (!to.ok) return <li data-unplaced title={to.message}>{node.name}</li>;
744
+ * return <li onClick={() => p.cursor.moveTo(to.step)}>{to.label}</li>;
745
+ * ```
746
+ *
747
+ * It holds no position of its own — it is rebuilt every render. Read it;
748
+ * never store it.
749
+ */
750
+ readonly cursor: LensCursor;
724
751
  }
725
752
  declare const Lens: React__default.FC<LensProps>;
726
753