agentfootprint-lens 0.49.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.
- package/README.md +138 -7
- package/dist/{Lens-_hExvOXu.d.ts → Lens-Bw1em67x.d.cts} +103 -25
- package/dist/{Lens-_IjGLEGt.d.cts → Lens-CDsSUiHJ.d.ts} +103 -25
- package/dist/{chunk-KS4AIWQJ.js → chunk-7QSK7EJW.js} +152 -86
- package/dist/chunk-7QSK7EJW.js.map +1 -0
- package/dist/{chunk-AUGRQKRL.js → chunk-BJR32J6H.js} +2 -2
- package/dist/{chunk-K4CTXAB7.js → chunk-H63KQNY3.js} +2 -2
- package/dist/{chunk-2UOQEE4U.js → chunk-MS5XW7GH.js} +23 -13
- package/dist/{chunk-2UOQEE4U.js.map → chunk-MS5XW7GH.js.map} +1 -1
- package/dist/{chunk-LCIEJYZG.js → chunk-OXPEMAMG.js} +29 -2
- package/dist/chunk-OXPEMAMG.js.map +1 -0
- package/dist/{chunk-AIZIILHJ.js → chunk-ZZJ2OIQP.js} +2 -2
- package/dist/core.cjs +28 -0
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +5 -5
- package/dist/core.d.ts +5 -5
- package/dist/core.js +5 -3
- package/dist/index.cjs +149 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -16
- package/dist/index.d.ts +34 -16
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/{resolveNavigation-Ckr2nXHY.d.ts → lensCursor-B8HFFZgR.d.cts} +162 -1
- package/dist/{resolveNavigation-Ckr2nXHY.d.cts → lensCursor-B8HFFZgR.d.ts} +162 -1
- package/dist/{selectSkillFrameContext-DEk_qpXr.d.ts → selectSkillFrameContext-BE7lCVcB.d.cts} +1 -1
- package/dist/{selectSkillFrameContext-DjZDveZw.d.cts → selectSkillFrameContext-D2uHzhJJ.d.ts} +1 -1
- package/dist/skillgraph.cjs +48 -10
- package/dist/skillgraph.cjs.map +1 -1
- package/dist/skillgraph.d.cts +45 -10
- package/dist/skillgraph.d.ts +45 -10
- package/dist/skillgraph.js +5 -3
- package/dist/{tagAxis-C__DMHiD.d.ts → tagAxis-BUrsQ1fX.d.cts} +1 -1
- package/dist/{tagAxis-B598GxMJ.d.cts → tagAxis-Cqom3DPf.d.ts} +1 -1
- package/dist/why.cjs +221 -128
- package/dist/why.cjs.map +1 -1
- package/dist/why.d.cts +5 -5
- package/dist/why.d.ts +5 -5
- package/dist/why.js +6 -4
- package/dist/why.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KS4AIWQJ.js.map +0 -1
- package/dist/chunk-LCIEJYZG.js.map +0 -1
- /package/dist/{chunk-AUGRQKRL.js.map → chunk-BJR32J6H.js.map} +0 -0
- /package/dist/{chunk-K4CTXAB7.js.map → chunk-H63KQNY3.js.map} +0 -0
- /package/dist/{chunk-AIZIILHJ.js.map → chunk-ZZJ2OIQP.js.map} +0 -0
package/README.md
CHANGED
|
@@ -940,7 +940,9 @@ not answer, since it takes an exact stop.
|
|
|
940
940
|
## The Served tab
|
|
941
941
|
|
|
942
942
|
**At every LLM call, exactly what the model was served — provable from the log.**
|
|
943
|
-
The Why Lens's right rail has a second tab, **Served**, mounted for every run
|
|
943
|
+
The Why Lens's right rail has a second tab, **Served**, mounted for every run —
|
|
944
|
+
including for a host that supplies `slots.detail`, whose own pane is the tab
|
|
945
|
+
beside it (0.50.0).
|
|
944
946
|
Stand on an LLM turn and it shows the request that call went out with: the system
|
|
945
947
|
prompt (piece by piece), the messages as sent, the tools as sent, the dials, the
|
|
946
948
|
cache breakpoints — and beside every field, what the record can PROVE about it.
|
|
@@ -1108,7 +1110,8 @@ likely to rot — and the question above is answered without it.
|
|
|
1108
1110
|
## Bookmarks, and scrubbing by declared tag
|
|
1109
1111
|
|
|
1110
1112
|
**The reader's marks, kept beside the recording — and the author's tags, as a
|
|
1111
|
-
ruler.** Since 0.48.0 the Why Lens's right rail has a third tab, **Bookmarks
|
|
1113
|
+
ruler.** Since 0.48.0 the Why Lens's right rail has a third tab, **Bookmarks**
|
|
1114
|
+
(a fourth when a host fills `slots.detail`),
|
|
1112
1115
|
and the grouped ruler carries a **Tags** strip.
|
|
1113
1116
|
|
|
1114
1117
|
### Why
|
|
@@ -1222,9 +1225,17 @@ unavailable*, and nothing else changes. The peer ranges are untouched.
|
|
|
1222
1225
|
|
|
1223
1226
|
## Rendering your own detail pane
|
|
1224
1227
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
+
**A slot fills a PANE, never the CHROME around it.** The right rail's tab strip,
|
|
1229
|
+
the one cursor and the collapse pill belong to the library, because chrome is how
|
|
1230
|
+
the library adds capability over time — a consumer that fills a slot must still
|
|
1231
|
+
receive every reading the rail gains later. That is why `slots.detail` is ONE TAB
|
|
1232
|
+
and not a takeover, and why `detailOnly` is an opt-out rather than the default.
|
|
1233
|
+
|
|
1234
|
+
`slots.detail` renders the right column's FIRST tab. The column itself — its
|
|
1235
|
+
width, its border, its collapse pill, its cursor — is unchanged, and so is the
|
|
1236
|
+
strip: your pane is selected by default, with *What happened*, *Served* and
|
|
1237
|
+
*Bookmarks* one click away beside it. Omit `slots` and the built-in timeline
|
|
1238
|
+
renders exactly as before.
|
|
1228
1239
|
|
|
1229
1240
|
```tsx
|
|
1230
1241
|
const Detail: React.FC<LensDetailSlotProps> = ({ step, cursorRuntimeStageId, node, onNavigate }) => (
|
|
@@ -1235,7 +1246,8 @@ const Detail: React.FC<LensDetailSlotProps> = ({ step, cursorRuntimeStageId, nod
|
|
|
1235
1246
|
</div>
|
|
1236
1247
|
);
|
|
1237
1248
|
|
|
1238
|
-
<Lens recorder={recorder} runner={runner} slots={{ detail: Detail }} />
|
|
1249
|
+
<Lens recorder={recorder} runner={runner} slots={{ detail: Detail, detailLabel: 'My bands' }} />
|
|
1250
|
+
// rail: [ My bands* | What happened | Served | Bookmarks ]
|
|
1239
1251
|
```
|
|
1240
1252
|
|
|
1241
1253
|
The slot receives the cursor in every unit (`step`, `totalSteps`,
|
|
@@ -1244,11 +1256,130 @@ and the ones that ran inside its scope (`node`, `relatedNodes`), the `recorder`
|
|
|
1244
1256
|
for anything else, and `onNavigate` — the same funnel every built-in mover uses,
|
|
1245
1257
|
so your pane moves the ONE cursor rather than starting a second one.
|
|
1246
1258
|
|
|
1259
|
+
| key | what it does |
|
|
1260
|
+
|---|---|
|
|
1261
|
+
| `detail` | your pane, as the rail's first tab, selected by default |
|
|
1262
|
+
| `detailLabel` | what that tab is CALLED. Default `"Details"` — the library's generic word for someone else's pane. Name it and the strip reads in your reader's words. |
|
|
1263
|
+
| `detailOnly` | opt OUT of the strip: your pane takes the whole rail, as it did before 0.50.0. For a full-height custom layout that would read as a second chrome under the library's strip. The cost: nothing the rail gains later can reach you. |
|
|
1264
|
+
|
|
1265
|
+
**Switching tabs moves nothing and remounts nothing.** The cursor stays where it
|
|
1266
|
+
was, and your pane is hidden rather than unmounted — a reader who glances at
|
|
1267
|
+
*Served* and comes back finds their bands open and their scroll where they left
|
|
1268
|
+
it.
|
|
1269
|
+
|
|
1270
|
+
**The worked example (0.50.0).** An app supplies `detail` + `detailLabel` and
|
|
1271
|
+
changes nothing else: it sees its own pane first and selected, exactly as before,
|
|
1272
|
+
and it GAINS Served, the Served graph and Bookmarks on the version bump. Before
|
|
1273
|
+
0.50.0 that same app lost the entire rail — the tab strip included — and could
|
|
1274
|
+
not reach any of them.
|
|
1275
|
+
|
|
1247
1276
|
Keep the slots object stable across renders (module scope or `useMemo`), same as
|
|
1248
1277
|
`<TraceExplorerShell slots>`.
|
|
1249
1278
|
|
|
1250
1279
|
---
|
|
1251
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
|
+
|
|
1252
1383
|
## Theming
|
|
1253
1384
|
|
|
1254
1385
|
**Lens inherits theme tokens from your app via CSS variables.** Set `--fp-*`
|
|
@@ -1377,7 +1508,7 @@ graph in one call. Returns an unsubscribe. Call it once per run.
|
|
|
1377
1508
|
| `step` | `number?` | Controlled cursor. **Omit it and the lens is self-driving, exactly as before.** Pass it and you own the position; out-of-range values are clamped and reported. See [Driving the cursor](#driving-the-cursor-from-your-app). |
|
|
1378
1509
|
| `onStepChange` | `(step, at) => void?` | Fires on every cursor move — required for movement in controlled mode, an observation hook otherwise. `at` carries `runtimeStageId`, `commitIdx`, `label`, `kind` and `clamped`. |
|
|
1379
1510
|
| `navigatorRef` | `Ref<LensNavigator>?` | Move the cursor to a stage **by its `runtimeStageId`**. `ref.current.navigateTo(id)` returns `{ ok: true, step, match, label }` or `{ ok: false, reason, message, nearest? }` — a miss never moves. See [Pointing at a step](#pointing-at-a-step-navigatorref). |
|
|
1380
|
-
| `slots` | `LensSlots?` | Slot overrides. `slots.detail` renders your content
|
|
1511
|
+
| `slots` | `LensSlots?` | Slot overrides. `slots.detail` renders your content as the right rail's FIRST TAB (`slots.detailLabel` names it; `slots.detailOnly` takes the whole rail instead). The strip stays the library's. Omit for the built-in timeline. See [Rendering your own detail pane](#rendering-your-own-detail-pane). |
|
|
1381
1512
|
|
|
1382
1513
|
### `<LensFlow>` — the chart canvas on its own
|
|
1383
1514
|
|
|
@@ -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 {
|
|
7
|
-
import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-
|
|
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.
|
|
@@ -634,16 +641,67 @@ interface LensProps {
|
|
|
634
641
|
*
|
|
635
642
|
* Same idea as `<TraceExplorerShell slots>`: keep the shipped layout and the
|
|
636
643
|
* shipped cursor, replace what one pane renders.
|
|
644
|
+
*
|
|
645
|
+
* THE LAW: **a slot fills a PANE, never the CHROME around it.** The rail's tab
|
|
646
|
+
* strip, the one cursor and the collapse pill belong to the library, because
|
|
647
|
+
* chrome is how the library adds capability over time — a consumer that fills a
|
|
648
|
+
* slot must still receive every capability the rail gains later. That is why
|
|
649
|
+
* `detail` is ONE TAB and not a takeover, and why `detailOnly` is an opt-out
|
|
650
|
+
* rather than the default.
|
|
637
651
|
*/
|
|
638
652
|
interface LensSlots {
|
|
639
653
|
/**
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
654
|
+
* Render the right column's FIRST TAB — your own pane, beside the library's.
|
|
655
|
+
*
|
|
656
|
+
* Your component renders inside the shipped column (its width, borders,
|
|
657
|
+
* collapse pill and scroll behaviour are unchanged) and inside the shipped
|
|
658
|
+
* rail: the tab strip stays, your pane becomes the first tab and is selected
|
|
659
|
+
* by default, and "What happened", "Served" and "Bookmarks" stay reachable
|
|
660
|
+
* beside it. Switching tabs never moves the cursor and never unmounts your
|
|
661
|
+
* pane — it keeps its own state.
|
|
662
|
+
*
|
|
663
|
+
* Before 0.50.0 a supplied slot replaced the whole rail, tab strip included,
|
|
664
|
+
* so a slot consumer could not reach anything the rail gained afterwards.
|
|
665
|
+
* That was the defect this shape fixes; `detailOnly` is the way back.
|
|
643
666
|
*
|
|
644
667
|
* Omit it and the built-in timeline renders exactly as before.
|
|
668
|
+
*
|
|
669
|
+
* ```tsx
|
|
670
|
+
* <Lens recorder={recorder} slots={{ detail: MyBands, detailLabel: 'Bands' }} />
|
|
671
|
+
* // rail: [ Bands* | What happened | Served | Bookmarks ]
|
|
672
|
+
* ```
|
|
645
673
|
*/
|
|
646
674
|
readonly detail?: React__default.ComponentType<LensDetailSlotProps>;
|
|
675
|
+
/**
|
|
676
|
+
* What the `detail` tab is CALLED. Default: `"Details"`.
|
|
677
|
+
*
|
|
678
|
+
* WHY it exists: the tab strip names each reading in the reader's own words,
|
|
679
|
+
* and "Details" is the library's generic word for someone else's pane. An app
|
|
680
|
+
* whose pane is a stack of SEO bands should say so — the strip is read left to
|
|
681
|
+
* right and an unnamed tab makes the app's own reading the anonymous one.
|
|
682
|
+
*
|
|
683
|
+
* A plain string, rendered as the tab's text; it is the accessible name too.
|
|
684
|
+
* Ignored when no `detail` slot is supplied.
|
|
685
|
+
*
|
|
686
|
+
* ```tsx
|
|
687
|
+
* slots={{ detail: MyBands, detailLabel: 'SEO bands' }}
|
|
688
|
+
* ```
|
|
689
|
+
*/
|
|
690
|
+
readonly detailLabel?: string;
|
|
691
|
+
/**
|
|
692
|
+
* OPT OUT of the tab strip: the `detail` slot takes the whole rail, as it did
|
|
693
|
+
* before 0.50.0. Default `false`.
|
|
694
|
+
*
|
|
695
|
+
* For the app with a full-height custom layout of its own — a pane that owns
|
|
696
|
+
* its scroll and its header and would read as a second chrome under the
|
|
697
|
+
* library's strip. The cost is stated plainly: nothing the rail gains later
|
|
698
|
+
* (Served, Bookmarks, whatever follows) can reach that consumer, and neither
|
|
699
|
+
* can the library's own timeline. Prefer the tabs; take this only when the
|
|
700
|
+
* layout genuinely demands it.
|
|
701
|
+
*
|
|
702
|
+
* Ignored when no `detail` slot is supplied.
|
|
703
|
+
*/
|
|
704
|
+
readonly detailOnly?: boolean;
|
|
647
705
|
}
|
|
648
706
|
/**
|
|
649
707
|
* What the `detail` slot receives — the cursor, in the units a detail pane
|
|
@@ -670,6 +728,26 @@ interface LensDetailSlotProps {
|
|
|
670
728
|
readonly recorder: LensRecorder;
|
|
671
729
|
/** Move the ONE cursor — same funnel every built-in mover uses. */
|
|
672
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;
|
|
673
751
|
}
|
|
674
752
|
declare const Lens: React__default.FC<LensProps>;
|
|
675
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 {
|
|
7
|
-
import { C as ChartGroupHighlight, c as LensCursorPort, H as Humanizer, b as BookmarkStore } from './tagAxis-
|
|
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.
|
|
@@ -634,16 +641,67 @@ interface LensProps {
|
|
|
634
641
|
*
|
|
635
642
|
* Same idea as `<TraceExplorerShell slots>`: keep the shipped layout and the
|
|
636
643
|
* shipped cursor, replace what one pane renders.
|
|
644
|
+
*
|
|
645
|
+
* THE LAW: **a slot fills a PANE, never the CHROME around it.** The rail's tab
|
|
646
|
+
* strip, the one cursor and the collapse pill belong to the library, because
|
|
647
|
+
* chrome is how the library adds capability over time — a consumer that fills a
|
|
648
|
+
* slot must still receive every capability the rail gains later. That is why
|
|
649
|
+
* `detail` is ONE TAB and not a takeover, and why `detailOnly` is an opt-out
|
|
650
|
+
* rather than the default.
|
|
637
651
|
*/
|
|
638
652
|
interface LensSlots {
|
|
639
653
|
/**
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
654
|
+
* Render the right column's FIRST TAB — your own pane, beside the library's.
|
|
655
|
+
*
|
|
656
|
+
* Your component renders inside the shipped column (its width, borders,
|
|
657
|
+
* collapse pill and scroll behaviour are unchanged) and inside the shipped
|
|
658
|
+
* rail: the tab strip stays, your pane becomes the first tab and is selected
|
|
659
|
+
* by default, and "What happened", "Served" and "Bookmarks" stay reachable
|
|
660
|
+
* beside it. Switching tabs never moves the cursor and never unmounts your
|
|
661
|
+
* pane — it keeps its own state.
|
|
662
|
+
*
|
|
663
|
+
* Before 0.50.0 a supplied slot replaced the whole rail, tab strip included,
|
|
664
|
+
* so a slot consumer could not reach anything the rail gained afterwards.
|
|
665
|
+
* That was the defect this shape fixes; `detailOnly` is the way back.
|
|
643
666
|
*
|
|
644
667
|
* Omit it and the built-in timeline renders exactly as before.
|
|
668
|
+
*
|
|
669
|
+
* ```tsx
|
|
670
|
+
* <Lens recorder={recorder} slots={{ detail: MyBands, detailLabel: 'Bands' }} />
|
|
671
|
+
* // rail: [ Bands* | What happened | Served | Bookmarks ]
|
|
672
|
+
* ```
|
|
645
673
|
*/
|
|
646
674
|
readonly detail?: React__default.ComponentType<LensDetailSlotProps>;
|
|
675
|
+
/**
|
|
676
|
+
* What the `detail` tab is CALLED. Default: `"Details"`.
|
|
677
|
+
*
|
|
678
|
+
* WHY it exists: the tab strip names each reading in the reader's own words,
|
|
679
|
+
* and "Details" is the library's generic word for someone else's pane. An app
|
|
680
|
+
* whose pane is a stack of SEO bands should say so — the strip is read left to
|
|
681
|
+
* right and an unnamed tab makes the app's own reading the anonymous one.
|
|
682
|
+
*
|
|
683
|
+
* A plain string, rendered as the tab's text; it is the accessible name too.
|
|
684
|
+
* Ignored when no `detail` slot is supplied.
|
|
685
|
+
*
|
|
686
|
+
* ```tsx
|
|
687
|
+
* slots={{ detail: MyBands, detailLabel: 'SEO bands' }}
|
|
688
|
+
* ```
|
|
689
|
+
*/
|
|
690
|
+
readonly detailLabel?: string;
|
|
691
|
+
/**
|
|
692
|
+
* OPT OUT of the tab strip: the `detail` slot takes the whole rail, as it did
|
|
693
|
+
* before 0.50.0. Default `false`.
|
|
694
|
+
*
|
|
695
|
+
* For the app with a full-height custom layout of its own — a pane that owns
|
|
696
|
+
* its scroll and its header and would read as a second chrome under the
|
|
697
|
+
* library's strip. The cost is stated plainly: nothing the rail gains later
|
|
698
|
+
* (Served, Bookmarks, whatever follows) can reach that consumer, and neither
|
|
699
|
+
* can the library's own timeline. Prefer the tabs; take this only when the
|
|
700
|
+
* layout genuinely demands it.
|
|
701
|
+
*
|
|
702
|
+
* Ignored when no `detail` slot is supplied.
|
|
703
|
+
*/
|
|
704
|
+
readonly detailOnly?: boolean;
|
|
647
705
|
}
|
|
648
706
|
/**
|
|
649
707
|
* What the `detail` slot receives — the cursor, in the units a detail pane
|
|
@@ -670,6 +728,26 @@ interface LensDetailSlotProps {
|
|
|
670
728
|
readonly recorder: LensRecorder;
|
|
671
729
|
/** Move the ONE cursor — same funnel every built-in mover uses. */
|
|
672
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;
|
|
673
751
|
}
|
|
674
752
|
declare const Lens: React__default.FC<LensProps>;
|
|
675
753
|
|