Skip to content

feat(replay): the trajectory bar — always on top, collapsible, synced with the turns; the loop-row diagram removed - #104

Merged
lroolle merged 5 commits into
mainfrom
feat/trajectory-topbar
Sep 2, 2026
Merged

feat(replay): the trajectory bar — always on top, collapsible, synced with the turns; the loop-row diagram removed#104
lroolle merged 5 commits into
mainfrom
feat/trajectory-topbar

Conversation

@lroolle

@lroolle lroolle commented Sep 1, 2026

Copy link
Copy Markdown
Member

Items from Eric's 2026-08-31/09-01 batch: "make the session's trajectory bar always on top (collapsible of course); not only for the replay" — then, on the shipped 0.46.0 flowchart: "remove the diagram; it's shit and useless; figure it out like the trajectory bar should sync with the session turns."

Revision 3 — the bar is the overview (first commit)

  • #replay-bar is FRAME in the session view at all times: the strip (five lanes over wall-clock, clock ruler, idle folded to ⧸⧸ breaks, thread focus, live open-request stub) draws without a replay running and grows on every landed pair.
  • Replay's chrome — transport, veil, playhead, slice — stays hidden until body.replaying.
  • Collapsible: a ▾ chevron in the clock gutter folds the lanes to the ~13px clock row, persisted (cctrace-traj-fold); replay overrides the fold. A sessionless trace hides the bar.

Revision 4 — the diagram goes, the bar becomes the minimap (second commit)

  • The loop-row flowchart is removed#rp-now, its data layer (loopAt/nowAt/stateAt), the .rl-* CSS and both animations (−860 lines net). The autopsy is in the spec: a three-state machine never changes shape, so after one glance the drawing carried no information the strip, the beat and the conversation don't state better. The motion budget returns to ONE owner, the playhead.
  • The bar syncs with the session's turns, both ways. Every rendered turn carries data-ts; a faint reading marker (#rp-read, the playhead's quiet twin, hidden while replaying) tracks the topmost visible turn as the reader scrolls (rAF-throttled); a click on the bar outside replay jumps the conversation to the turn at that instant. Clicking no longer enters replay — an overview click is navigation, not a mode switch; ⏵/Space/arrows still replay.

Revision 5 — replay holds the reader's thread; waiting folds (third commit)

Eric on rev 4 in real use: waiting ate the strip; "replay is like broken now, sessions not focused, the nav bar not following".

  • The selection pins for the whole replay. The cursored rebuild used to re-resolve the selection: before the selected thread's first response it fell back to newestMainThread of whatever existed at the cursor, overwrote sessionSelKey, and the strip's axis re-ruled itself to the stranger on every tick. The intended thread now resolves ONCE against the full capture; a cursor before its first response renders "nothing on this thread's wire yet at this moment" instead of switching threads. The playhead crosses one stable ruler.
  • Enter / ⏮ / Home park on the THREAD's edges. A merged capture's tape starts 44 min inside another session and ends 8h after this one — entering replay used to park there, the whole strip behind one break column. ⏭/End stay the tape's live edge.
  • ←/→ step this conversation (the selected thread's own pairs); shift+arrow keeps every wire boundary.
  • Waiting folds like idle (threadExtent: waiting stretches the extent, never the busy union — nobody worked) and RP_IDLE_MS drops 5m → 2m. On the real trace a ~45-minute wait went from holding half the strip to a ⧸⧸ break; the work now fills the frame. Borrowed from dsh's Trajectory tab duration projection, which compresses every uncovered stretch; ours keeps the threshold so short waits still read as time.

Spec: "Revision 5: replay holds the reader's thread" in replay-stage.md. Verified end-to-end in Chrome on the 668-pair merged trace (axis stable through restart+play, scoped stepping, empty-state convo, clean exit, zero console errors); bun test 876/0.

Spec

docs/design/replay-stage.md — "Revision 3: the strip is the overview" + "Revision 4: the diagram goes, the bar becomes the minimap" (with the loop row's autopsy for the next diagram idea to read first).

Verification

  • bun test 876/0 (loop-row tests removed with the feature; new: turns carry data-ts, the reading marker exists, a bar click outside replay does not enter replay, the fold survives re-renders, a landed pair grows the strip without replay).
  • Host Chrome on the real 2026-08-28 trace (119-turn session): bar on top with the reading marker at the topmost visible turn; scrolling tracks it; a strip click called convoScrollTo with the right turn target and never entered replay (instrumented); ⏵ still enters replay with strip + transport and the stage — no loop row anywhere; zero console errors.

🤖 Generated with Claude Code

lroolle and others added 2 commits August 31, 2026 20:29
…n top, collapsible

The strip used to appear only after pressing replay; an overview that
must be asked for is not an overview. It now draws in the session view
at all times (lanes, clock ruler, idle folded, thread focus), grows on
every landed pair, and touching it enters replay at that moment.
Replay's own chrome (loop row, transport, veil, playhead, slice) still
waits for body.replaying. A chevron in the clock gutter folds the lanes
to the clock row, persisted; replay unfolds. Spec: replay-stage.md rev 3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n's turns

Eric's verdict on the shipped flowchart after real use: remove it. The
loop row, its data layer (loopAt/nowAt/stateAt) and both its animations
are deleted — a three-state machine never changes shape, so the drawing
restated what the strip, the beat and the convo already say. Motion
returns to one owner, the playhead.

In its place the bar becomes the session's MINIMAP: every turn carries
data-ts, a faint reading marker tracks the topmost visible turn as the
reader scrolls (hidden while replaying), and a click outside replay
jumps the conversation to that instant instead of entering replay.
Spec: replay-stage.md 'Revision 4'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lroolle lroolle changed the title feat(replay): the trajectory bar is the session's overview — always on top, collapsible feat(replay): the trajectory bar — always on top, collapsible, synced with the turns; the loop-row diagram removed Sep 1, 2026
lroolle and others added 3 commits September 1, 2026 00:09
… like idle

Eric on rev 4 in real use: waiting eats the strip; replay is broken —
sessions not focused, the bar not following. Root cause of the second:
with replay active the session rebuilds as of the cursor, and showSession
re-resolved the selection against that cursored subset — before the
selected thread's first response it fell back to newestMainThread,
OVERWROTE sessionSelKey, and the strip's axis (the selected thread's
extent) re-ruled itself to the stranger on every tick.

- The selection pins for the whole replay: resolved once against the
  full capture; a cursor before the thread's first response renders
  "nothing on this thread's wire yet" instead of switching threads
- enterReplay (reading pages) parks at the selected thread's last pair;
  rpHome() (⏮ / Home / play-at-the-end) seeks its first — a merged
  capture's tape edges can sit hours inside other sessions. ⏭/End stay
  the tape's live edge
- ←/→ step the selected thread's own pairs; shift+arrow keeps every
  wire boundary
- threadExtent: waiting spans stretch the extent but leave the busy
  union — a call-less harness wait compresses like idle (the dsh
  Trajectory tab's duration projection folds every uncovered stretch;
  ours keeps a threshold), RP_IDLE_MS 5m -> 2m
- rpQueueSyncRead falls back to a direct call in a hidden tab (rAF
  never fires there; the read marker was one frame stale on return)

Verified end-to-end in Chrome on a real 668-pair merged trace: axis
stable through restart+play, scoped stepping, empty-state convo, clean
exit, zero console errors. bun test 876/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ens, a vertical facet rail

The Context view's details open in ONE place now: a right panel beside
the deck, opened by a pick (an icicle node, a stream record, an event
row), closed by × / Esc, absent otherwise. The window deck's pane sat
UNDER the icicle in the same scroll and pushed the graph off the sheet;
the stream's inspector was always there, 40% of the deck spent on
"pick a record"; events had no detail at all.

Inside, a VERTICAL rail of facets — the questions the wire can answer
about the pick, and only those (a tab that would say n/a is not listed):
content (the block rendered with the detail panel's own renderers, the
picked fold open), schema (the tool's declaration in the carrying
request, its weight and rank among that request's tools), origin (the
step it entered with and the CARRY — how many requests re-sent it and
what that weighs; exact through the pinned step for an icicle item,
counted to the next compaction boundary and saying so for a record),
wire (the carrying request in brief, both links out). Events open too:
the injected text, a compaction's before → after by category, a bump's
cause with its TTL gap or failed previous status.

Esc peels the inspector first. The panel repaints alone on a pick
(never the overview under the cursor); its scroll survives a same-pick
live repaint and resets when the pick or facet changes.

Also: an image-bearing tool result (a browser screenshot) had no label
under its icicle group — only string content was read. The label reads
the array now, [image] for the picture.

Data layer: ctxCarrySpan in src/context.ts, pure, inlined, unit-tested.
Grammar tests cover the panel, the rail and the events picks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The bar's human lane drew a 2px point per prompt: a target nobody could
hit, unnumbered below full depth, saying nothing about the turn it
started. It is the TURNS lane now: one clickable block per working
loop, from the instant the prompt hit the wire (the block's accent edge,
exactly where the point was) to the loop's last reply, numbered the way
the rail numbers it. The number shows at any zoom the block can hold it
(the number IS the point); the prompt's words wait for full depth. Hover
gives the turn's tally — steps, calls, agents spawned, clock and
duration, the prompt, compactions carried and failed requests that fell
inside it — all from the lanes already built. Click jumps the
conversation to the turn's HEAD (a block carries data-rpj, the prompt's
own instant; the seek time would land on the first reply); replaying,
it seeks where the prompt became visible like every span. The block
under the conversation's reading position lights as you scroll — the
sync, on the block itself. A harness-started loop is a turn too, drawn
faint and saying who started it; subagent loops stay on the agents
lane. lanes.human stays as data: the thread extent and soFar read it.

Data: sessionLanes().turns in src/replay.ts, unit-tested; the strip
grammar tests name the new lane.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@lroolle
lroolle merged commit f94dd38 into main Sep 2, 2026
1 check passed
@lroolle
lroolle deleted the feat/trajectory-topbar branch September 2, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant