agentfootprint-lens 0.45.0 → 0.47.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 +266 -0
- package/dist/{Lens-B039-UGw.d.cts → Lens-9wZ84e70.d.ts} +23 -6
- package/dist/{Lens-ey1EgN9i.d.ts → Lens-Ccs8UUDQ.d.cts} +23 -6
- package/dist/{chunk-YEJJOPOZ.js → chunk-2UOQEE4U.js} +2 -2
- package/dist/{chunk-5YSPWSLW.js → chunk-A3NWHAJ2.js} +2 -2
- package/dist/{chunk-QNMA25X5.js → chunk-AUGRQKRL.js} +18 -6
- package/dist/chunk-AUGRQKRL.js.map +1 -0
- package/dist/{chunk-CGR5VFJU.js → chunk-E5ZRMHYY.js} +615 -4
- package/dist/chunk-E5ZRMHYY.js.map +1 -0
- package/dist/{chunk-LRGT7UQL.js → chunk-ZNH33A7V.js} +1224 -306
- package/dist/chunk-ZNH33A7V.js.map +1 -0
- package/dist/core.cjs +627 -13
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +434 -8
- package/dist/core.d.ts +434 -8
- package/dist/core.js +22 -4
- package/dist/index.cjs +2321 -782
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -10
- package/dist/index.d.ts +168 -10
- package/dist/index.js +28 -6
- package/dist/index.js.map +1 -1
- package/dist/{scrubAxisFor-Cz214OHj.d.ts → lensStops-DfNxsaH0.d.cts} +158 -2
- package/dist/{scrubAxisFor-CNceBlz-.d.cts → lensStops-Di41Rg3f.d.ts} +158 -2
- package/dist/{resolveNavigation-o4E4cM_1.d.ts → resolveNavigation-Ckr2nXHY.d.cts} +9 -0
- package/dist/{resolveNavigation-o4E4cM_1.d.cts → resolveNavigation-Ckr2nXHY.d.ts} +9 -0
- package/dist/{selectSkillFrameContext-IPsw4Pbj.d.ts → selectSkillFrameContext-DEk_qpXr.d.ts} +1 -1
- package/dist/{selectSkillFrameContext-DJkvrggG.d.cts → selectSkillFrameContext-DjZDveZw.d.cts} +1 -1
- package/dist/skillgraph.cjs +17 -5
- package/dist/skillgraph.cjs.map +1 -1
- package/dist/skillgraph.d.cts +4 -4
- package/dist/skillgraph.d.ts +4 -4
- package/dist/skillgraph.js +2 -2
- package/dist/why.cjs +1767 -244
- package/dist/why.cjs.map +1 -1
- package/dist/why.d.cts +6 -5
- package/dist/why.d.ts +6 -5
- package/dist/why.js +7 -3
- package/dist/why.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-CGR5VFJU.js.map +0 -1
- package/dist/chunk-LRGT7UQL.js.map +0 -1
- package/dist/chunk-QNMA25X5.js.map +0 -1
- /package/dist/{chunk-YEJJOPOZ.js.map → chunk-2UOQEE4U.js.map} +0 -0
- /package/dist/{chunk-5YSPWSLW.js.map → chunk-A3NWHAJ2.js.map} +0 -0
package/README.md
CHANGED
|
@@ -808,6 +808,256 @@ 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
|
+
|
|
938
|
+
## The Served tab
|
|
939
|
+
|
|
940
|
+
**At every LLM call, exactly what the model was served — provable from the log.**
|
|
941
|
+
The Why Lens's right rail has a second tab, **Served**, mounted for every run.
|
|
942
|
+
Stand on an LLM turn and it shows the request that call went out with: the system
|
|
943
|
+
prompt (piece by piece), the messages as sent, the tools as sent, the dials, the
|
|
944
|
+
cache breakpoints — and beside every field, what the record can PROVE about it.
|
|
945
|
+
|
|
946
|
+
### Why
|
|
947
|
+
|
|
948
|
+
The request a provider receives is assembled from committed pieces and is itself
|
|
949
|
+
never committed — the `call-llm` bundle holds the answer, not the ask. Every
|
|
950
|
+
earlier "what did the model see?" panel in this family read an event
|
|
951
|
+
(`llm_start`) that only some runs record, or rebuilt the prompt from the pieces
|
|
952
|
+
with no way to know whether the rebuild matched what went out.
|
|
953
|
+
|
|
954
|
+
agentfootprint 9.88.0 closes that with two halves and one law:
|
|
955
|
+
|
|
956
|
+
```
|
|
957
|
+
hash(servedAt(k)) === receiptAt(k).hash
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
`servedAt(k)` REBUILDS the request for epoch `k` (one epoch = one LLM call) from
|
|
961
|
+
the committed pieces. `receiptAt(k)` reads the **receipt** — the hashes-only
|
|
962
|
+
record the call itself committed at the stop (system hash and pieces, one hash
|
|
963
|
+
per message, tool names and schema hashes, the sampling dials, the cache
|
|
964
|
+
verdict). When the two agree the record is complete; when they disagree,
|
|
965
|
+
something reached the model that the run never wrote down. The tab renders both
|
|
966
|
+
halves at the lens's one cursor and checks them with the library's own
|
|
967
|
+
`receiptHash` / `messageDigestInput`.
|
|
968
|
+
|
|
969
|
+
### What each badge means
|
|
970
|
+
|
|
971
|
+
| badge | exactly this |
|
|
972
|
+
|---|---|
|
|
973
|
+
| **Verified** | the receipt's hash for the field EQUALS the hash of what the rebuild produced — run-salted SHA-256, computed with agentfootprint's exported `receiptHash` (over `messageDigestInput` for a message). Nothing softer earns it. |
|
|
974
|
+
| **Reconstructed** | rebuilt from the log, but nothing to check it against: this epoch committed no receipt, the receipt has no hash for this row, or a gap on the view covers the field (a declared hole, printed beside it). Tool NAMES are always here — they carry no hash. Tool SCHEMAS are here too, because the library hashes them over a canonical JSON it does not export, and a lookalike serializer would be a second copy of the rule. |
|
|
975
|
+
| **Damaged** | the record contradicts itself: the receipt's hash disagrees with the rebuild and no gap excuses it, the rebuild produced a row the receipt never witnessed (no gap says a rebuild may be LONG — the receipt is the witness in both directions), or something under the receipt key was refused as not-a-receipt (`cause: 'receipt-shape-rejected'` — the library refuses a value with no `basis.epoch`, the lens refuses one missing the containers it reads). |
|
|
976
|
+
| **Not on record** | the field is absent on both sides — a receipt-only field (model, provider, params, cache) on an epoch that minted no receipt. |
|
|
977
|
+
|
|
978
|
+
### The laws the tab keeps
|
|
979
|
+
|
|
980
|
+
- **Omit, never deny.** A field a gap covers is never rendered as "empty" or
|
|
981
|
+
"none". The gap IS the empty state: its `why` sentence, verbatim from
|
|
982
|
+
`SERVED_GAPS`, is printed beside the field it covers, and the section is
|
|
983
|
+
marked with the gap's name.
|
|
984
|
+
- **The tab writes no claim sentences of its own.** Every explanatory sentence
|
|
985
|
+
on it is the library's — `SERVED_GAPS[k].why`, `UNGAPPED_FIELDS[k]`,
|
|
986
|
+
`RECEIPT_BOUNDARY` — or is computed data: a status, a count, a hash, a diff.
|
|
987
|
+
The strings the tab owns are labels (`SERVED_LABELS`), and
|
|
988
|
+
`test/served/no-own-claims.test.ts` walks every literal in the tab's source to
|
|
989
|
+
keep it that way; its header states what such a walk cannot catch.
|
|
990
|
+
- **Authority omissions come from the fold.** A receipt never names what a
|
|
991
|
+
caller's ROLE was not allowed to see (the library's first law). The tab's
|
|
992
|
+
audience is an operator, so it MAY show the skill ids hidden from the model —
|
|
993
|
+
but it reads them from the committed state at the stop through footprintjs's
|
|
994
|
+
`stateAt`, labelled **hidden from the model**, never from the receipt.
|
|
995
|
+
- **One cursor.** The tab takes the lens's position as props and holds none of
|
|
996
|
+
its own. On an llm-turn stop it shows that epoch. On any other stop it shows
|
|
997
|
+
the nearest PRECEDING call with the note *as of call k — this stop is between
|
|
998
|
+
calls*. Clicking the call's id asks the lens to move the one cursor there.
|
|
999
|
+
A resumed leg's first call has no previous epoch IN THIS RECORDING; the tab
|
|
1000
|
+
prints that epoch's number with *Not on record*, never "no previous epoch".
|
|
1001
|
+
- **A record never takes the Lens down.** A half-shaped receipt reads
|
|
1002
|
+
*Damaged*; a commit-log row the fold cannot read is data in the FOLD
|
|
1003
|
+
section; a render throw is caught by a boundary around the tab and printed
|
|
1004
|
+
as the Damaged badge plus the message. The rest of the Lens — its one
|
|
1005
|
+
cursor, its *What happened* tab — stays mounted.
|
|
1006
|
+
|
|
1007
|
+
### The sections
|
|
1008
|
+
|
|
1009
|
+
**SERVED** — system prompt with per-piece boundaries (slot · source) and a
|
|
1010
|
+
word-level diff against the previous epoch when the text changed; messages as
|
|
1011
|
+
sent (role, tool-call ids, request-only lines marked with the mechanism that
|
|
1012
|
+
composed them); tools as sent (names, expandable schemas, the forced tool
|
|
1013
|
+
marked, `withheld` shown as the library states it). **BASIS** — epoch, call id,
|
|
1014
|
+
commit index, model, provider, params (only the dials the receipt carries — an
|
|
1015
|
+
absent dial is not rendered as a default), cache (transform, breakpoints
|
|
1016
|
+
applied). **FOLD** — `iteration`, `currentSkillId`, `stepPointer`, engagement,
|
|
1017
|
+
active injections, hidden skill ids, read from the fold at the stop; a row the
|
|
1018
|
+
fold could not read is printed there as data (skipped indices, or the fold's
|
|
1019
|
+
error), beside a Damaged badge.
|
|
1020
|
+
**OMISSIONS** — attention drops from the receipt when present, else the
|
|
1021
|
+
library's `UNGAPPED_FIELDS` sentence for the field. **GAPS** — every gap on the
|
|
1022
|
+
view: its kind, the fields it covers, its sentence verbatim, and its `cause`
|
|
1023
|
+
when the library established one (`receipt-shape-rejected` styled as damage).
|
|
1024
|
+
**SINCE PREVIOUS** — messages entered/left, tools added/removed, schemas whose
|
|
1025
|
+
receipt hashes changed, and the system-text word diff.
|
|
1026
|
+
|
|
1027
|
+
### Headless
|
|
1028
|
+
|
|
1029
|
+
```ts
|
|
1030
|
+
import {
|
|
1031
|
+
servedRowAt, servedRowForEpoch, verify, sincePrevious, foldFactsAt,
|
|
1032
|
+
} from 'agentfootprint-lens/core';
|
|
1033
|
+
|
|
1034
|
+
const snapshot = runner.getLastSnapshot();
|
|
1035
|
+
const row = servedRowAt(snapshot, { runtimeStageId: 'call-llm#18', commitIdx: 15 });
|
|
1036
|
+
if (row) {
|
|
1037
|
+
row.epoch; // 1
|
|
1038
|
+
row.betweenCalls; // false — on the call itself
|
|
1039
|
+
const checks = verify(row.view, row.receipt, row.receipt?.basis.runId ?? '');
|
|
1040
|
+
checks.system.status; // 'verified'
|
|
1041
|
+
checks.messages.map((c) => c.status); // ['verified']
|
|
1042
|
+
row.view.gaps.map((g) => g.why); // the library's sentences
|
|
1043
|
+
const prev = row.previousEpoch !== undefined
|
|
1044
|
+
? servedRowForEpoch(snapshot, row.previousEpoch) : undefined;
|
|
1045
|
+
if (prev) sincePrevious(row, prev).tools.added; // ['charge']
|
|
1046
|
+
foldFactsAt(snapshot, { runtimeStageId: 'call-llm#18', commitIdx: 15 }).hiddenSkillIds;
|
|
1047
|
+
}
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
Every function is pure and every return is frozen. `<ServedTab runner
|
|
1051
|
+
cursorRuntimeStageId commitIdx onJumpTo>` is exported for shells that hold the
|
|
1052
|
+
one cursor themselves.
|
|
1053
|
+
|
|
1054
|
+
**A recording made before agentfootprint 9.88** still renders: the rebuild works
|
|
1055
|
+
on every epoch, no receipt was minted, and the tab says so with the library's
|
|
1056
|
+
`no-receipt-on-chart` gap and `cause: 'no-receipt-committed'` — every row
|
|
1057
|
+
**Reconstructed**, never a fabricated **Verified**.
|
|
1058
|
+
|
|
1059
|
+
---
|
|
1060
|
+
|
|
811
1061
|
## Rendering your own detail pane
|
|
812
1062
|
|
|
813
1063
|
`slots.detail` replaces the CONTENT of the shipped right column. The column
|
|
@@ -1027,6 +1277,16 @@ exact `formatSlice` text the LLM tool returns. Honest absence stays honest:
|
|
|
1027
1277
|
"never written — initial state / args / a closure", and reads-off runs say
|
|
1028
1278
|
"unknowable, not absent".
|
|
1029
1279
|
|
|
1280
|
+
### `<ServedTab>` — what the model was served at the cursor's call
|
|
1281
|
+
|
|
1282
|
+
`<ServedTab runner cursorRuntimeStageId commitIdx onJumpTo?>`. The Why Lens
|
|
1283
|
+
mounts it as the right rail's second tab; exported for consumer-built shells.
|
|
1284
|
+
Renders `servedAt(k)` and `receiptAt(k)` (agentfootprint 9.88.0) at the one
|
|
1285
|
+
cursor with a **Verified / Reconstructed / Damaged / Not on record** badge per
|
|
1286
|
+
field, the library's gap sentences verbatim, the fold's hidden skill ids, and a
|
|
1287
|
+
since-previous diff. Headless: `servedRowAt` · `verify` · `sincePrevious` ·
|
|
1288
|
+
`foldFactsAt` in `/core`. See "The Served tab" above.
|
|
1289
|
+
|
|
1030
1290
|
### `<BugReportButton>` — report a bug with the run attached, consent first
|
|
1031
1291
|
|
|
1032
1292
|
A small button for a debug UI. The dialog it opens shows every selectable unit
|
|
@@ -1112,6 +1372,12 @@ so instead of drawing an empty graph.
|
|
|
1112
1372
|
/ CLI view on the same primitives — see `ChangeNotifier`'s JSDoc for adapter
|
|
1113
1373
|
snippets.
|
|
1114
1374
|
|
|
1375
|
+
The cursor is headless too: `scrubAxisFor` builds the ruler, `openLensCursor`
|
|
1376
|
+
moves along it through footprintjs's `timeTravel()` port, `resolveNavigation`
|
|
1377
|
+
turns an address into a step with every rung named, and `stepForCommitIdx`
|
|
1378
|
+
carries a position between the two axes. See
|
|
1379
|
+
[Time travel through one port](#time-travel-through-one-port).
|
|
1380
|
+
|
|
1115
1381
|
---
|
|
1116
1382
|
|
|
1117
1383
|
## Why this design
|
|
@@ -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-
|
|
7
|
-
import { C as ChartGroupHighlight, H as Humanizer } from './
|
|
6
|
+
import { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-Ckr2nXHY.js';
|
|
7
|
+
import { C as ChartGroupHighlight, a as LensCursorPort, H as Humanizer } from './lensStops-Di41Rg3f.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
|
|
182
|
-
*
|
|
183
|
-
*
|
|
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.
|
|
@@ -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-
|
|
7
|
-
import { C as ChartGroupHighlight, H as Humanizer } from './
|
|
6
|
+
import { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-Ckr2nXHY.cjs';
|
|
7
|
+
import { C as ChartGroupHighlight, a as LensCursorPort, H as Humanizer } from './lensStops-DfNxsaH0.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
|
|
182
|
-
*
|
|
183
|
-
*
|
|
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-
|
|
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-
|
|
1495
|
+
//# sourceMappingURL=chunk-2UOQEE4U.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatBytes,
|
|
3
3
|
structureGraphFromRunner
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-E5ZRMHYY.js";
|
|
5
5
|
|
|
6
6
|
// src/core/buildStepGraphFromSnapshot.ts
|
|
7
7
|
function buildStepGraphFromSnapshot(snapshot) {
|
|
@@ -1189,4 +1189,4 @@ export {
|
|
|
1189
1189
|
decisionSentence,
|
|
1190
1190
|
isConsentDecision
|
|
1191
1191
|
};
|
|
1192
|
-
//# sourceMappingURL=chunk-
|
|
1192
|
+
//# sourceMappingURL=chunk-A3NWHAJ2.js.map
|
|
@@ -160,7 +160,8 @@ var TimeTravel = ({
|
|
|
160
160
|
stepStrip = true,
|
|
161
161
|
bands,
|
|
162
162
|
snapSteps,
|
|
163
|
-
keyboard = true
|
|
163
|
+
keyboard = true,
|
|
164
|
+
stepper
|
|
164
165
|
}) => {
|
|
165
166
|
const max = Math.max(0, total - 1);
|
|
166
167
|
const banded = bands !== void 0 && bands.length > 0;
|
|
@@ -180,8 +181,19 @@ var TimeTravel = ({
|
|
|
180
181
|
if (to !== void 0) onFocusChange(to);
|
|
181
182
|
return;
|
|
182
183
|
}
|
|
184
|
+
if (stepper !== void 0) {
|
|
185
|
+
onFocusChange(delta < 0 ? stepper.back(focusSeq) : stepper.forward(focusSeq));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
183
188
|
onFocusChange(Math.min(max, Math.max(0, focusSeq + delta)));
|
|
184
189
|
};
|
|
190
|
+
const toEnd = (which) => {
|
|
191
|
+
if (stepper !== void 0) {
|
|
192
|
+
onFocusChange(which === "first" ? stepper.toFirst(focusSeq) : stepper.toLast(focusSeq));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
onFocusChange(which === "first" ? 0 : max);
|
|
196
|
+
};
|
|
185
197
|
useEffect(() => {
|
|
186
198
|
if (!keyboard) return;
|
|
187
199
|
const onKey = (e) => {
|
|
@@ -197,15 +209,15 @@ var TimeTravel = ({
|
|
|
197
209
|
step(1);
|
|
198
210
|
} else if (e.key === "Home") {
|
|
199
211
|
e.preventDefault();
|
|
200
|
-
|
|
212
|
+
toEnd("first");
|
|
201
213
|
} else if (e.key === "End") {
|
|
202
214
|
e.preventDefault();
|
|
203
|
-
|
|
215
|
+
toEnd("last");
|
|
204
216
|
}
|
|
205
217
|
};
|
|
206
218
|
window.addEventListener("keydown", onKey);
|
|
207
219
|
return () => window.removeEventListener("keydown", onKey);
|
|
208
|
-
}, [focusSeq, max, keyboard, bands, snaps]);
|
|
220
|
+
}, [focusSeq, max, keyboard, bands, snaps, stepper]);
|
|
209
221
|
const disabled = total <= 1;
|
|
210
222
|
return /* @__PURE__ */ jsxs(
|
|
211
223
|
"div",
|
|
@@ -251,7 +263,7 @@ var TimeTravel = ({
|
|
|
251
263
|
/* @__PURE__ */ jsx(
|
|
252
264
|
"button",
|
|
253
265
|
{
|
|
254
|
-
onClick: () =>
|
|
266
|
+
onClick: () => toEnd("last"),
|
|
255
267
|
disabled: isLive || disabled,
|
|
256
268
|
style: btnStyle(!isLive && total > 0),
|
|
257
269
|
title: "Jump to latest event (End)",
|
|
@@ -559,4 +571,4 @@ export {
|
|
|
559
571
|
refusalDestinationFor,
|
|
560
572
|
REFUSAL_GO_TO
|
|
561
573
|
};
|
|
562
|
-
//# sourceMappingURL=chunk-
|
|
574
|
+
//# sourceMappingURL=chunk-AUGRQKRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/theme/tokens.ts","../src/react/TimeTravel.tsx","../src/react/narrowLayout.ts","../src/doors/recordingInput.ts","../src/doors/DoorRefusalCard.tsx"],"sourcesContent":["/**\n * Lens theme tokens — consumers override via CSS variables.\n *\n * Two-tier contract:\n * 1. `--fp-*` shared design tokens (set by the consumer's app).\n * Same names as `footprint-explainable-ui` so a single token sheet\n * themes both libraries.\n * 2. `--lens-*` lens-specific overrides for things `--fp-*` doesn't\n * cover (edge colors per kind, source-of-injection chips, etc.).\n *\n * Resolution order (highest priority first):\n * --lens-X → --fp-X → hardcoded fallback\n *\n * Consumer integration (in your app's root CSS or theme provider):\n *\n * :root {\n * --fp-bg-primary: #0f172a;\n * --fp-bg-secondary: #1e293b;\n * --fp-bg-tertiary: #334155;\n * --fp-text-primary: #f8fafc;\n * --fp-text-secondary: #94a3b8;\n * --fp-text-muted: #64748b;\n * --fp-border: #334155;\n * --fp-color-primary: #6366f1;\n * }\n *\n * Then Lens picks them up automatically — no `theme=` prop needed.\n *\n * To override only Lens (not your other UI), use `--lens-*` instead.\n */\n\n/** Raw fallback colors — used when no `--fp-*` or `--lens-*` is defined. */\nexport const RAW_DEFAULTS = {\n // Surfaces\n bgPrimary: '#0f172a',\n bgSecondary: '#1e293b',\n bgTertiary: '#334155',\n bgElevated: '#1e293b',\n\n // Text\n textPrimary: '#f8fafc',\n textSecondary: '#94a3b8',\n textMuted: '#64748b',\n\n // Border\n border: '#334155',\n\n // Accent / state\n primary: '#6366f1',\n success: '#22c55e',\n error: '#ef4444',\n warning: '#f59e0b',\n\n // The active GROUP on the grouped ruler — the one accent every member of the\n // group lights with, the outline drawn around them, and its name chip. Its\n // own token (not `primary`) because a consumer will want to tune \"the place\n // I'm standing in\" without moving every other accent in the Lens.\n groupAccent: '#6366f1',\n\n // Edge kinds (control-flow graph)\n edgeUser: '#0284c7',\n edgeTool: '#059669',\n edgeDecision: '#db2777',\n\n // Injection-source chips\n srcRag: '#0284c7',\n srcSkill: '#7c3aed',\n srcMemory: '#ca8a04',\n srcInstruction: '#db2777',\n srcUser: '#059669',\n srcTool: '#0891b2',\n srcDefault: '#64748b',\n\n // Typography\n fontSans: 'ui-sans-serif, system-ui, -apple-system, sans-serif',\n fontMono: \"'JetBrains Mono', 'Fira Code', ui-monospace, monospace\",\n} as const;\n\n/**\n * `var()` chain factory: prefer `--lens-X`, then `--fp-X`, then hardcoded default.\n *\n * v('bg-primary', RAW_DEFAULTS.bgPrimary)\n * → 'var(--lens-bg-primary, var(--fp-bg-primary, #0f172a))'\n *\n * Use the returned string as a CSS value: `style={{ background: t.bgPrimary }}`.\n */\nfunction v(name: string, fallback: string): string {\n return `var(--lens-${name}, var(--fp-${name}, ${fallback}))`;\n}\n\n/**\n * Token reference — every Lens component imports `T` (or destructures from it).\n * Each property is a CSS string ready to drop into `style={{ ... }}`.\n *\n * Adding a new token: add a key here AND extend `RAW_DEFAULTS` above.\n * Consumers can override by setting either `--lens-<key>` or `--fp-<key>` on\n * a parent container — Lens picks it up automatically because every\n * reference goes through the var() chain.\n */\nexport const T = {\n // Surfaces\n bgPrimary: v('bg-primary', RAW_DEFAULTS.bgPrimary),\n bgSecondary: v('bg-secondary', RAW_DEFAULTS.bgSecondary),\n bgTertiary: v('bg-tertiary', RAW_DEFAULTS.bgTertiary),\n bgElevated: v('bg-elevated', RAW_DEFAULTS.bgElevated),\n\n // Text\n textPrimary: v('text-primary', RAW_DEFAULTS.textPrimary),\n textSecondary: v('text-secondary', RAW_DEFAULTS.textSecondary),\n textMuted: v('text-muted', RAW_DEFAULTS.textMuted),\n\n // Border\n border: v('border', RAW_DEFAULTS.border),\n\n // Accent / state\n primary: v('color-primary', RAW_DEFAULTS.primary),\n success: v('color-success', RAW_DEFAULTS.success),\n error: v('color-error', RAW_DEFAULTS.error),\n warning: v('color-warning', RAW_DEFAULTS.warning),\n\n // Group mode (see RAW_DEFAULTS.groupAccent). eui ships no `--fp-group-accent`,\n // so the middle tier is free for `theme.mode` to stamp a light variant into\n // without ever outranking a consumer's `--lens-group-accent`.\n groupAccent: v('group-accent', RAW_DEFAULTS.groupAccent),\n\n // Edge kinds — lens-only NAMES, but the same three-tier chain as the rest.\n // eui ships no `--fp-edge-*`, so the middle tier is free for `theme.mode` to\n // stamp a light variant into (see MODE_PALETTES) without ever outranking a\n // consumer's `--lens-edge-*`.\n edgeUser: v('edge-user', RAW_DEFAULTS.edgeUser),\n edgeTool: v('edge-tool', RAW_DEFAULTS.edgeTool),\n edgeDecision: v('edge-decision', RAW_DEFAULTS.edgeDecision),\n edgeDefault: v('edge-default', RAW_DEFAULTS.textMuted),\n\n // Injection-source chips — same three-tier chain, same reason.\n srcRag: v('src-rag', RAW_DEFAULTS.srcRag),\n srcSkill: v('src-skill', RAW_DEFAULTS.srcSkill),\n srcMemory: v('src-memory', RAW_DEFAULTS.srcMemory),\n srcInstruction: v('src-instruction', RAW_DEFAULTS.srcInstruction),\n srcUser: v('src-user', RAW_DEFAULTS.srcUser),\n srcTool: v('src-tool', RAW_DEFAULTS.srcTool),\n srcDefault: v('src-default', RAW_DEFAULTS.srcDefault),\n\n // Typography\n fontSans: v('font-sans', RAW_DEFAULTS.fontSans),\n fontMono: v('font-mono', RAW_DEFAULTS.fontMono),\n} as const;\n\nexport type LensTokens = typeof T;\n\n/**\n * One agent = one swatch colour, by index. The strip reads\n * `--lens-agent-color-N`; these are what it paints when nobody defined them.\n *\n * Eight hues that stay distinguishable on BOTH a dark and a light surface\n * (mid-tone, similar luminance) — the strip is the only place a run's agents\n * are told apart at a glance, and \"the consumer's design tokens own the\n * palette\" was, in practice, \"the swatches paint nothing\".\n */\nexport const AGENT_COLORS = [\n '#6366f1', // indigo\n '#059669', // emerald\n '#db2777', // pink\n '#d97706', // amber\n '#0891b2', // cyan\n '#7c3aed', // violet\n '#dc2626', // red\n '#65a30d', // lime\n] as const;\n\n/** The swatch colour for one agent row: consumer override, else the palette. */\nexport function agentColor(index: number): string {\n const fallback = AGENT_COLORS[index % AGENT_COLORS.length] ?? AGENT_COLORS[0];\n return `var(--lens-agent-color-${index}, ${fallback})`;\n}\n\n/**\n * The lens-only palettes, per mode — what `<Lens theme={{ mode }}>` stamps into\n * the `--fp-*` tier.\n *\n * Edge colours and injection-source chips have no eui cousin, so a preset can\n * never reach them: `mode: 'light'` re-themed everything eui draws and left\n * these at their dark-tuned defaults, on a white background. These are the same\n * hues, darkened for light surfaces (dark mode keeps `RAW_DEFAULTS` exactly, so\n * nothing moves for existing users).\n *\n * They go into `--fp-*`, never `--lens-*`: a consumer who set `--lens-edge-tool`\n * on a parent must still win.\n */\nexport const MODE_PALETTES: Record<'dark' | 'light', Readonly<Record<string, string>>> = {\n dark: {\n '--fp-bg-elevated': RAW_DEFAULTS.bgElevated,\n '--fp-edge-user': RAW_DEFAULTS.edgeUser,\n '--fp-edge-tool': RAW_DEFAULTS.edgeTool,\n '--fp-edge-decision': RAW_DEFAULTS.edgeDecision,\n '--fp-edge-default': RAW_DEFAULTS.textMuted,\n '--fp-src-rag': RAW_DEFAULTS.srcRag,\n '--fp-src-skill': RAW_DEFAULTS.srcSkill,\n '--fp-src-memory': RAW_DEFAULTS.srcMemory,\n '--fp-src-instruction': RAW_DEFAULTS.srcInstruction,\n '--fp-src-user': RAW_DEFAULTS.srcUser,\n '--fp-src-tool': RAW_DEFAULTS.srcTool,\n '--fp-src-default': RAW_DEFAULTS.srcDefault,\n '--fp-group-accent': RAW_DEFAULTS.groupAccent,\n },\n light: {\n // The card surface Lens's own panels sit on. eui's presets stop at\n // bg-primary/secondary/tertiary, so this is the one surface token the mode\n // switch has to supply itself.\n '--fp-bg-elevated': '#f9fafb',\n '--fp-edge-user': '#0369a1',\n '--fp-edge-tool': '#047857',\n '--fp-edge-decision': '#be185d',\n '--fp-edge-default': '#71717a',\n '--fp-src-rag': '#0369a1',\n '--fp-src-skill': '#6d28d9',\n '--fp-src-memory': '#a16207',\n '--fp-src-instruction': '#be185d',\n '--fp-src-user': '#047857',\n '--fp-src-tool': '#0e7490',\n '--fp-src-default': '#52525b',\n // A touch deeper on a white ground: the group's wash is a low-percentage\n // mix of this, and the dark-tuned indigo washes out to nothing on paper.\n '--fp-group-accent': '#4f46e5',\n },\n};\n","/**\n * TimeTravel — scrub slider.\n *\n * Pattern: frosted-pill control with ◀ ▶ ⟳Live buttons + range slider.\n * State shape: one `focusSeq` number. At `max` → live (new\n * events auto-advance). Below `max` → user pinned, auto-\n * advance off. Arrow keys scrub. Space toggles live.\n * Role: Replay past states of a run without post-walking the tree.\n * Lens filters the RunTree + EventStream to entries with\n * `seq <= focusSeq`; RunTreeFlow hides nodes whose\n * `startOffsetMs > focusRunOffsetMs`. The recorder keeps all\n * events — the view just slices.\n *\n * Why a separate component (not inlined in Lens):\n * - Reused across views (Engineer, Analyst)\n * - Keyboard bindings live here so the scope is explicit\n * - Theme/styling lives in one place\n */\n\nimport React, { useEffect, useMemo } from 'react';\nimport { T } from './theme/index.js';\nimport { bandIndexOf, type StepBand } from '../core/group/stepBands.js';\nimport { nextSnapStep, prevSnapStep, snapPositionOf } from '../core/group/snapSteps.js';\n\nexport interface TimeTravelProps {\n /** Total number of events in the log (= max seq + 1 for zero-indexed seq). */\n readonly total: number;\n /** Current focus position. Clamped to [0, total - 1]. */\n readonly focusSeq: number;\n /** Called on every scrub / step / live click. */\n readonly onFocusChange: (seq: number) => void;\n /** True when `focusSeq === total - 1`. Drives ⟳Live button visual. */\n readonly isLive: boolean;\n /**\n * Compact mode — render ONLY the ◀ ▶ ⟳Live controls + position count, NOT the\n * drag track. Used in the monitor where the \"WHAT HAPPENED\" timeline IS the\n * scrubber, so a second draggable track would be redundant. Keyboard scrubbing\n * + Live still work.\n */\n readonly compact?: boolean;\n /**\n * In compact mode, render a clickable STEP STRIP — one tick per event, every\n * step visible in a row, click any to jump (and the focused tick stands out).\n * A lightweight all-steps overview + scrubber when the drag track is omitted.\n * ON by default; pass `false` for just ◀ ▶ ⟳Live + the count.\n */\n readonly stepStrip?: boolean;\n /**\n * The GROUPED ruler: bands over the step axis (from `stepBands`). When\n * present (compact mode), the strip renders ONE segment per band — sized by\n * how many steps it covers, labelled with the band's name — and clicking a\n * band jumps to ITS first step. The bands are the GROUPING, not the unit of\n * movement: ◀ ▶ and the arrow keys still move stop by stop, so every\n * position on the axis is reachable by the transport alone (the readout\n * names both: \"Iteration 2 · stop 9 of 17\"). `snapSteps` is the one prop\n * that narrows where the step buttons land, and it says there what stays\n * reachable when it does.\n *\n * No second cursor: the position is still `focusSeq` (a step), the active\n * band is DERIVED from which range contains it, and every move still goes\n * out through `onFocusChange(step)` — this component stores nothing.\n */\n readonly bands?: readonly StepBand[];\n /**\n * SNAP STOPS — the positions ◀ ▶ and ← → are allowed to land on, as an\n * ASCENDING list of steps on the axis this transport already receives.\n *\n * Why: a hosted view shares the host's axis, which is the run's and not the\n * view's. A four-tool turn can put 73 stops on it while the view changes at\n * eight of them, so 65 presses show the reader the same picture twice. The\n * cure is NOT a second slider with its own numbers (two transports over one\n * run is the drift this package refuses) — the axis, the numbers and the one\n * cursor all stay the host's, and only the STEP BUTTONS' landing set narrows.\n *\n * What stays true when it is set:\n * • the drag track and the step strip keep the FULL axis — every position\n * is still reachable, just not by ◀ ▶;\n * • Home / End still go to the axis endpoints, because they are jumps, not\n * steps (band clicks and strip clicks are jumps too, and also unaffected);\n * • a position BETWEEN two stops resolves to the one at-or-before, and the\n * readout SAYS it is between rather than rounding down silently;\n * • nothing is stored — the position is still `focusSeq`, and every move\n * still leaves through `onFocusChange(step)`.\n *\n * Entries the axis cannot hold (negative, past `total - 1`, non-integer) are\n * ignored: a stop the axis does not have is not a stop. An empty list, or a\n * list whose entries are all off-axis, is the same as passing nothing.\n *\n * Orthogonal to `bands`: bands GROUP the axis, snaps MOVE along it. Set both\n * and the readout names the band, the stop and the raw step.\n *\n * @example\n * ```tsx\n * // ◀ ▶ walk only the steps where the skill graph's picture changes.\n * <TimeTravel total={73} focusSeq={step} onFocusChange={go} isLive={false}\n * snapSteps={[0, 9, 17, 31, 44, 58, 72]} />\n * ```\n */\n readonly snapSteps?: readonly number[];\n /**\n * Bind ← → Home End at the window. Default `true` — today's behaviour.\n *\n * Turn it OFF for a SECOND transport on the same page. Two mounted\n * transports both listen at the window and both move the ONE cursor, so a\n * single arrow press moves it twice (and by different units when the two\n * are bound to different projections of the axis). The page's primary\n * transport keeps the keys; the secondary one asks for `keyboard={false}`.\n */\n readonly keyboard?: boolean;\n /**\n * WHERE ◀ ▶ AND Home / End LAND — footprintjs 9.17's reader cursor, over\n * this axis's stops (`openLensCursor(positions)` in `/core`).\n *\n * The step arithmetic (\"one less, but not below zero\") has lived in three\n * places across this ecosystem and disagreed in two of them at the ends. It\n * is the library's now: `prev` / `next` / `first` / `last` on the port, whose\n * law is that a move outside the axis does not happen and says why. Each\n * method here takes the step the cursor is on and returns the step it should\n * be on — this component still stores nothing and still leaves through\n * `onFocusChange`.\n *\n * OMIT IT and the transport keeps its own arithmetic, unchanged. `snapSteps`\n * wins where both are set: snaps narrow the unit of movement to a SUBSET of\n * the axis, which is a question the port does not answer (see the README).\n */\n readonly stepper?: CursorStepper;\n}\n\n/**\n * The four movers a transport needs, each answered by the port and applied by\n * the one funnel. `from` is the step the cursor is on; the return is the step\n * to move to (the same step back means \"no move\", which is a true answer at\n * the ends of the axis).\n */\nexport interface CursorStepper {\n readonly back: (from: number) => number;\n readonly forward: (from: number) => number;\n readonly toFirst: (from: number) => number;\n readonly toLast: (from: number) => number;\n}\n\nexport const TimeTravel: React.FC<TimeTravelProps> = ({\n total,\n focusSeq,\n onFocusChange,\n isLive,\n compact,\n stepStrip = true,\n bands,\n snapSteps,\n keyboard = true,\n stepper,\n}) => {\n const max = Math.max(0, total - 1);\n\n // The GROUPED ruler is a pure projection: which band the cursor is in is\n // DERIVED from focusSeq on every render — never stored, so it can never\n // drift from the one cursor.\n const banded = bands !== undefined && bands.length > 0;\n const bandIdx = banded ? bandIndexOf(bands, focusSeq) : -1;\n\n // SNAP STOPS, kept honest at the door: an entry the axis cannot hold is not\n // a stop, so it is dropped here rather than clamped onto a neighbour (a\n // clamped stop would let ▶ report the same position forever). All entries\n // dropped ⇒ `undefined` ⇒ byte-identical to passing nothing.\n const snaps = useMemo(() => {\n if (snapSteps === undefined) return undefined;\n const onAxis = snapSteps.filter((s) => Number.isInteger(s) && s >= 0 && s <= max);\n return onAxis.length > 0 ? onAxis : undefined;\n }, [snapSteps, max]);\n\n const prevStop = snaps !== undefined ? prevSnapStep(snaps, focusSeq) : undefined;\n const nextStop = snaps !== undefined ? nextSnapStep(snaps, focusSeq) : undefined;\n const canBack = snaps !== undefined ? prevStop !== undefined : focusSeq > 0;\n const canFwd = snaps !== undefined ? nextStop !== undefined : focusSeq < max;\n\n // Stop by stop, banded or not: the bands are the ruler's GROUPING, never\n // its unit of movement — a ◀ ▶ that jumped whole bands would make the\n // stops inside a band unreachable by the transport alone. (Band CLICKS\n // still jump to a band's first step; that is a jump, not the step key.)\n //\n // With `snapSteps` the unit of movement narrows — and ONLY that. The step\n // buttons walk the permitted stops (strictly ahead / strictly behind, so a\n // cursor parked between two of them walks back onto the one it is standing\n // past); the drag track, the step strip, band clicks and Home/End all still\n // reach every position on the axis. No wrap at either end: `undefined` from\n // the query disables the button instead.\n const step = (delta: number): void => {\n if (snaps !== undefined) {\n const to = delta < 0 ? prevSnapStep(snaps, focusSeq) : nextSnapStep(snaps, focusSeq);\n if (to !== undefined) onFocusChange(to);\n return;\n }\n if (stepper !== undefined) {\n onFocusChange(delta < 0 ? stepper.back(focusSeq) : stepper.forward(focusSeq));\n return;\n }\n onFocusChange(Math.min(max, Math.max(0, focusSeq + delta)));\n };\n /** Home / End — jumps to the ends of the axis, the port's `first` / `last`. */\n const toEnd = (which: 'first' | 'last'): void => {\n if (stepper !== undefined) {\n onFocusChange(which === 'first' ? stepper.toFirst(focusSeq) : stepper.toLast(focusSeq));\n return;\n }\n onFocusChange(which === 'first' ? 0 : max);\n };\n\n // Arrow-key scrubbing. Bound at the window so it works regardless of\n // where focus is — but skip when the user is typing in an input.\n useEffect(() => {\n if (!keyboard) return;\n const onKey = (e: KeyboardEvent): void => {\n // `matches` is feature-detected: a keydown dispatched AT the window or\n // the document (a host that forwards keys, a test harness) has a target\n // that is not an Element, and calling it blindly throws inside the\n // listener — taking the transport out over a guard clause.\n const target = e.target as HTMLElement | null;\n if (\n target !== null &&\n typeof target.matches === 'function' &&\n target.matches('input, textarea, [contenteditable=\"true\"]')\n ) {\n return;\n }\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n step(-1);\n } else if (e.key === 'ArrowRight') {\n e.preventDefault();\n step(1);\n } else if (e.key === 'Home') {\n e.preventDefault();\n toEnd('first');\n } else if (e.key === 'End') {\n e.preventDefault();\n toEnd('last');\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusSeq, max, keyboard, bands, snaps, stepper]);\n\n const disabled = total <= 1;\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '6px 12px',\n margin: '4px 0',\n background: `color-mix(in srgb, ${T.bgElevated} 70%, transparent)`,\n backdropFilter: 'blur(10px) saturate(140%)',\n WebkitBackdropFilter: 'blur(10px) saturate(140%)',\n border: `1px solid ${T.border}`,\n borderRadius: 999,\n fontFamily: T.fontSans,\n fontSize: 12,\n boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)',\n }}\n >\n <button\n onClick={() => step(-1)}\n disabled={!canBack || disabled}\n style={btnStyle(false)}\n title={snaps !== undefined ? 'Previous stop (←)' : 'Previous step (←)'}\n aria-label={snaps !== undefined ? 'Previous stop' : 'Previous step'}\n >\n ◀\n </button>\n <button\n onClick={() => step(+1)}\n disabled={!canFwd || disabled}\n style={btnStyle(false)}\n title={snaps !== undefined ? 'Next stop (→)' : 'Next step (→)'}\n aria-label={snaps !== undefined ? 'Next stop' : 'Next step'}\n >\n ▶\n </button>\n <button\n onClick={() => toEnd('last')}\n disabled={isLive || disabled}\n style={btnStyle(!isLive && total > 0)}\n title=\"Jump to latest event (End)\"\n aria-label=\"Jump to latest\"\n >\n {isLive ? '● Live' : '⟳ Live'}\n </button>\n {/* Compact mode: a clickable STEP STRIP (every step visible, click to jump\n — including BACKWARD to any earlier step), or just a spacer if disabled.\n With `bands`, the strip is the GROUPED ruler instead: one labelled\n segment per band, sized by the steps it covers. */}\n {compact && banded ? (\n <div\n role=\"group\"\n aria-label=\"Groups\"\n style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 3, minWidth: 120, padding: '0 4px' }}\n >\n {bands.map((band, i) => {\n const here = i === bandIdx;\n const done = i < bandIdx;\n const span = band.lastStep - band.firstStep + 1;\n return (\n <button\n key={`${band.firstStep}:${band.label}`}\n onClick={() => onFocusChange(band.firstStep)}\n disabled={disabled}\n title={\n span === 1\n ? `${band.label} — step ${band.firstStep + 1} of ${total}`\n : `${band.label} — steps ${band.firstStep + 1}–${band.lastStep + 1} of ${total}`\n }\n aria-label={`Go to ${band.label}`}\n aria-current={here ? 'step' : undefined}\n style={{\n flex: span,\n minWidth: 22,\n height: here ? 20 : 15,\n padding: '0 4px',\n border: 'none',\n borderRadius: 4,\n cursor: disabled ? 'default' : 'pointer',\n background: here ? T.warning : done ? T.success : T.border,\n color: here ? '#3b2b00' : T.textSecondary,\n fontSize: 10,\n fontWeight: here ? 600 : 500,\n lineHeight: 1,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n transition: 'height 0.12s ease, background 0.12s ease',\n }}\n >\n {band.label}\n </button>\n );\n })}\n </div>\n ) : compact ? (\n stepStrip && total > 1 ? (\n <div\n role=\"group\"\n aria-label=\"Steps\"\n style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 2, minWidth: 120, padding: '0 4px' }}\n >\n {Array.from({ length: total }, (_, i) => {\n const done = i < focusSeq;\n const here = i === focusSeq;\n return (\n <button\n key={i}\n onClick={() => onFocusChange(i)}\n disabled={disabled}\n title={`Step ${i + 1} / ${total}`}\n aria-label={`Go to step ${i + 1}`}\n aria-current={here ? 'step' : undefined}\n style={{\n flex: 1,\n minWidth: 3,\n height: here ? 16 : 9,\n padding: 0,\n border: 'none',\n borderRadius: 3,\n cursor: disabled ? 'default' : 'pointer',\n background: here ? T.warning : done ? T.success : T.border,\n transition: 'height 0.12s ease, background 0.12s ease',\n }}\n />\n );\n })}\n </div>\n ) : (\n <div style={{ flex: 1 }} />\n )\n ) : (\n <div style={{ flex: 1, position: 'relative', display: 'flex', alignItems: 'center' }}>\n <input\n type=\"range\"\n min={0}\n max={max}\n value={Math.min(focusSeq, max)}\n onChange={(e) => onFocusChange(Number(e.target.value))}\n disabled={disabled}\n style={{\n flex: 1,\n accentColor: T.primary,\n minWidth: 120,\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.4 : 1,\n }}\n title={disabled ? 'Single-step run — nothing to scrub' : undefined}\n />\n </div>\n )}\n <div\n style={{\n fontSize: 11,\n color: T.textMuted,\n fontFamily: T.fontMono,\n whiteSpace: 'nowrap',\n minWidth: 60,\n textAlign: 'right',\n }}\n >\n {/* Both readouts count STOPS on the one axis. The banded one also\n names the group the cursor stands in — \"Iteration 2 · stop 9 of\n 17\" — so the grouping and the position are both visible, honestly:\n the group is where you are, the stop is how far along.\n\n SNAPPING adds the third truth. ◀ ▶ no longer reach every position,\n so the readout must say where the cursor stands among the STOPS —\n and, when it stands between two of them, that it does. It keeps the\n raw step beside it, because that number is still the one the host\n owns and the slider still moves. */}\n {snaps !== undefined\n ? `${banded && bands[bandIdx] !== undefined ? `${bands[bandIdx]!.label} · ` : ''}${snapReadout(snaps, Math.min(focusSeq, max), total)}`\n : banded\n ? `${bands[bandIdx] !== undefined ? `${bands[bandIdx]!.label} · ` : ''}stop ${Math.min(focusSeq, max) + 1} of ${total}`\n : total === 0\n ? '—'\n : total === 1\n ? '1 step'\n : `${focusSeq + 1} / ${total}`}\n </div>\n </div>\n );\n};\n\n/**\n * The snapping readout — where the cursor stands among the permitted stops,\n * and the raw step it is on.\n *\n * The four arms are four different truths, and collapsing any of them into\n * \"stop N\" would be the lie this feature exists to avoid: with ◀ ▶ narrowed to\n * the stops, a cursor the slider parked between two of them is a position the\n * step buttons cannot reproduce, and the reader has to be told.\n */\nfunction snapReadout(snaps: readonly number[], focus: number, total: number): string {\n const { index, exact } = snapPositionOf(snaps, focus);\n const raw = `${focus + 1} / ${total}`;\n if (index < 0) return `before stop 1 · ${raw}`;\n if (exact) return `stop ${index + 1} of ${snaps.length} · ${raw}`;\n if (index === snaps.length - 1) return `past stop ${index + 1} of ${snaps.length} · ${raw}`;\n return `between stops ${index + 1} and ${index + 2} of ${snaps.length} · ${raw}`;\n}\n\nfunction btnStyle(accent: boolean): React.CSSProperties {\n return {\n background: accent ? T.primary : 'transparent',\n color: accent ? '#fff' : T.textSecondary,\n border: `1px solid ${accent ? T.primary : T.border}`,\n borderRadius: 999,\n padding: '2px 10px',\n fontSize: 11,\n fontWeight: 500,\n cursor: 'pointer',\n lineHeight: 1.4,\n };\n}\n","/**\n * The narrow degrade — where the engineer view stops being two columns.\n *\n * `<Lens view=\"engineer\">` is a chart column beside a 300px-minimum inspector.\n * Measured in a real browser, that pair needs about 690px of row width before\n * the chart stops being a chart; below it the columns were CLIPPING rather than\n * reflowing — and a split panel dragged narrow measures as little as 392px.\n *\n * So below the threshold the two columns STACK. Nothing is hidden, nothing is\n * clipped; the same panes are read top to bottom instead of left to right.\n */\n\nimport { useEffect, useState, type RefObject } from 'react';\n\n/**\n * Available row width, in CSS pixels, below which the engineer view stacks its\n * columns instead of placing them side by side.\n *\n * 690 = a chart column still worth calling a chart + the inspector's own 300px\n * minimum + the collapse pills between them.\n */\nexport const LENS_NARROW_BREAKPOINT = 690;\n\n/**\n * Should this row stack? A width of `0` means \"not measured yet\" (server\n * render, a detached node, a test environment with no layout) — an unmeasured\n * row keeps the shipped side-by-side layout rather than guessing narrow.\n */\nexport function isNarrowRow(width: number): boolean {\n return width > 0 && width < LENS_NARROW_BREAKPOINT;\n}\n\n/**\n * Watch an element's width and report whether it has crossed below\n * `LENS_NARROW_BREAKPOINT`. Falls back to the shipped wide layout wherever\n * `ResizeObserver` is unavailable.\n */\nexport function useNarrowRow(ref: RefObject<HTMLElement | null>): boolean {\n const [narrow, setNarrow] = useState(false);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const apply = (width: number): void => {\n const next = isNarrowRow(width);\n setNarrow((prev) => (prev === next ? prev : next));\n };\n\n const measure = (): number =>\n typeof el.getBoundingClientRect === 'function'\n ? el.getBoundingClientRect().width\n : 0;\n\n apply(measure());\n\n const RO = (globalThis as { ResizeObserver?: typeof ResizeObserver })\n .ResizeObserver;\n if (typeof RO !== 'function') return;\n\n const observer = new RO((entries) => {\n const entry = entries[0];\n const width = entry?.contentRect?.width ?? measure();\n apply(width);\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, [ref]);\n\n return narrow;\n}\n","/**\n * The doors' branded input — what an agentfootprint RECORDING is, and a\n * plain-language name for anything that is not one.\n *\n * The two subpath doors (`agentfootprint-lens/why`,\n * `agentfootprint-lens/skillgraph`) each mount ONE thing, and each validates\n * what it is handed AT MOUNT: a wrong shape renders a teaching refusal card\n * (see `DoorRefusalCard`) instead of a blank panel or a crash. This module is\n * the shared half of that promise — the duck checks and the sentences.\n *\n * Two accepted spellings of \"a recording\":\n *\n * { snapshot, events, structure } what `recordRun(agent)` freezes\n * the envelope what `persistRecording(...)` writes —\n * format-marked `agentfootprint.recording.*`,\n * with the same recording inside it\n *\n * Everything else gets NAMED, not guessed at: a bare commit log is told where\n * the commit-trace lens lives (footprint-explainable-ui); a JSON string is\n * told to parse it; anything unrecognizable is told how to record a run.\n */\n\nimport type { Recording } from '../core/observeRecording.js';\n\n/**\n * The archivable envelope `persistRecording` writes\n * (`agentfootprint/observe`'s `RecordingEnvelope`), duck-shaped: the format\n * marker plus the recording inside it. Extra fields (producer, run, privacy)\n * ride along untouched.\n */\nexport interface RecordingEnvelopeLike {\n /** `'agentfootprint.recording.v1'` (any `agentfootprint.recording.*`). */\n readonly format: string;\n /** The recording itself — `{ snapshot, events, structure }`. */\n readonly recording: Recording;\n}\n\n/**\n * What a door's mount component accepts — the branded input. TypeScript\n * consumers fail at BUILD time on anything else (a bare `CommitBundle[]` has\n * none of a recording's parts, so it is not assignable to either arm); JS\n * consumers get the runtime refusal card instead.\n */\nexport type AgentRecordingInput = Recording | RecordingEnvelopeLike;\n\n/** Where a refused input's reader should go next. */\nexport type RefusalDestination = 'commit-trace-lens' | 'record-the-run';\n\n/** The mount-time verdict: the recording (unwrapped from its envelope when it\n * arrived in one), or the refusal's two facts — what was received, where to go. */\nexport type RecordingVerdict =\n | { readonly ok: true; readonly recording: Recording }\n | { readonly ok: false; readonly received: string; readonly goTo: RefusalDestination };\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Does this array look like a footprintjs COMMIT LOG (one bundle per stage)? */\nfunction looksLikeCommitLog(value: readonly unknown[]): boolean {\n if (value.length === 0) return false;\n const first = value[0];\n return (\n isPlainObject(first) &&\n (typeof first['runtimeStageId'] === 'string' ||\n (first['patch'] !== undefined && first['idx'] !== undefined) ||\n Array.isArray(first['trace']))\n );\n}\n\n/** Does this object carry at least one of a recording's parts? */\nfunction looksLikeRecording(value: Record<string, unknown>): boolean {\n const events = value['events'];\n const snapshot = value['snapshot'];\n return Array.isArray(events) || isPlainObject(snapshot);\n}\n\n/**\n * Is `value` something the doors can read — a recording, or the envelope\n * around one? The runtime twin of the `AgentRecordingInput` type.\n */\nexport function isAgentRecording(value: unknown): value is AgentRecordingInput {\n return readAgentRecording(value).ok;\n}\n\n/**\n * The mount-time gate both doors call: unwrap the envelope when there is one,\n * accept a recording, and NAME anything else in plain language.\n */\nexport function readAgentRecording(value: unknown): RecordingVerdict {\n // The envelope: format-marked, recording inside. Unwrap and re-check.\n if (\n isPlainObject(value) &&\n typeof value['format'] === 'string' &&\n value['format'].startsWith('agentfootprint.recording.')\n ) {\n const inner = value['recording'];\n if (isPlainObject(inner) && looksLikeRecording(inner)) {\n return { ok: true, recording: inner as Recording };\n }\n return {\n ok: false,\n received: 'a recording envelope with nothing readable inside it (no events, no snapshot)',\n goTo: 'record-the-run',\n };\n }\n\n if (isPlainObject(value) && looksLikeRecording(value)) {\n return { ok: true, recording: value as Recording };\n }\n\n return { ok: false, received: describeReceived(value), goTo: refusalDestinationFor(value) };\n}\n\n/** Plain-language name for a value that is NOT a recording. Exported so a\n * host (or a test) can say the same sentence the refusal card says. */\nexport function describeReceived(value: unknown): string {\n if (value === undefined) return 'nothing (undefined)';\n if (value === null) return 'nothing (null)';\n if (typeof value === 'string') {\n return 'a string — if it is the recording’s JSON text, parse it first (JSON.parse) and pass the object';\n }\n if (Array.isArray(value)) {\n if (looksLikeCommitLog(value)) return 'a bare commit log (an array of commit bundles)';\n return value.length === 0 ? 'an empty array' : 'an array, but not of commit bundles';\n }\n if (isPlainObject(value)) {\n if (isPlainObject(value['snapshot']) || Array.isArray(value['events'])) {\n // looksLikeRecording would have accepted it; kept for completeness.\n return 'a recording';\n }\n if (Array.isArray(value['commitLog'])) {\n return 'a footprintjs run snapshot (a commit log, with no agent events around it)';\n }\n return 'an object with none of a recording’s parts (no events, no snapshot)';\n }\n return `a ${typeof value}`;\n}\n\n/** Which teaching sentence the refusal ends on. Exported for the skillgraph\n * door, whose mount validates a RECORDER prop against the same shapes. */\nexport function refusalDestinationFor(value: unknown): RefusalDestination {\n if (Array.isArray(value) && looksLikeCommitLog(value)) return 'commit-trace-lens';\n if (isPlainObject(value) && Array.isArray(value['commitLog'])) return 'commit-trace-lens';\n return 'record-the-run';\n}\n\n/** The two \"where to go\" sentences, spelled once. */\nexport const REFUSAL_GO_TO: Record<RefusalDestination, string> = {\n 'commit-trace-lens':\n 'The commit-trace lens is footprint-explainable-ui — mount its ExplainableShell over the run’s snapshot for that reading.',\n 'record-the-run':\n 'To get a recording, record the run: recordRun(agent) from agentfootprint/observe captures exactly what this lens replays.',\n};\n","/**\n * DoorRefusalCard — the doors' honest on-screen refusal.\n *\n * When a door's mount component is handed something it cannot read, the\n * screen must SAY so — a card in the lens's own voice, never a blank panel\n * and never a crash. Three sentences, in a fixed order:\n *\n * 1. what this lens reads (the door's own sentence)\n * 2. what you passed looks like (named, not guessed at)\n * 3. where to go (the teaching half)\n *\n * Same honesty family as `<SkillGraphDebugger>`'s \"No skill graph ran here\"\n * card and `<WhereFrom>`'s \"never written\" line: absence and refusal are\n * rendered facts here, not empty space.\n */\n\nimport React from 'react';\n\nimport { T } from '../react/theme/index.js';\n\nexport interface DoorRefusalCardProps {\n /** The door's name, worn as the card's eyebrow (e.g. \"Why Lens\"). */\n readonly door: string;\n /** Sentence 1, without its full stop: \"This lens reads {reads}.\" */\n readonly reads: string;\n /** Sentence 2's object: \"What you passed looks like {received}.\" */\n readonly received: string;\n /** Sentence 3, whole: where the reader should go instead. */\n readonly goTo: string;\n}\n\nexport function DoorRefusalCard({ door, reads, received, goTo }: DoorRefusalCardProps): React.ReactElement {\n return (\n <div\n data-testid=\"door-refusal\"\n role=\"note\"\n style={{\n margin: 12,\n padding: '12px 14px',\n borderRadius: 8,\n border: `1px solid ${T.border}`,\n background: T.bgSecondary,\n color: T.textPrimary,\n fontFamily: T.fontSans,\n fontSize: 12.5,\n lineHeight: 1.6,\n maxWidth: 560,\n }}\n >\n <div\n style={{\n fontSize: 10,\n fontWeight: 700,\n letterSpacing: '0.08em',\n textTransform: 'uppercase',\n color: T.textMuted,\n marginBottom: 6,\n }}\n >\n {door} · not the input this lens reads\n </div>\n <p style={{ margin: 0 }}>This lens reads {reads}.</p>\n <p style={{ margin: '6px 0 0' }}>\n What you passed looks like <strong data-testid=\"door-refusal-received\">{received}</strong>.\n </p>\n <p style={{ margin: '6px 0 0', color: T.textSecondary }} data-testid=\"door-refusal-go-to\">\n {goTo}\n </p>\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","jsx","jsxs"]}
|