Transcript: one live line that holds the latest step and keeps its clock

TE
5d ago
+401-41

Leo, 2026-11-01, on a screenshot of "Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' …" with "Thinking 15s · ↓ 284 out ↑ 342.6K in" on its own line under it:

this should be replacing the thinking area and its shimmer across things

like the thinking or whatever it should just keep going

and shouldn't flicker

What changed

A running turn now has one live line at the bottom, and that line is the current activity:

momentbeforeafter
before any outputThinking 15sThinking 15s
a call runsRunning grep … 0s (the clock started again at the call)Running grep … 15s · tokens
the call is donerow "Ran grep …", then "Thinking 18s · tokens" under itRan grep … 18s · tokens (one line, shimmering)
the agent thinks after itas aboveRan grep … 21s · tokens
the next call runsRunning sed … 0sRunning sed … 24s · tokens, and "Ran grep" moves into the run above
  • conversation/turns.ts codexLiveTurn / liftedStep: between calls the line names the latest finished step instead of going back to "Thinking". That step leaves its run so it reads once. Failed, backgrounded, message, sub-session, render and edit calls keep their own rows and do not go on the line.
  • conversation/Thinking.tsx: one clock, the turn's time from the prompt, for every step, so the time no longer starts again at each call. The turn's tokens now show beside a call too. A finished step reads "Ran …" and offers no ⌃B.
  • The line keeps its one id (thinking-live) and the same element tree for every state. Only its text changes, so React never remounts it and the CSS shimmer never starts again.

Spec: leo-workspace design/chat-transcript.md, "The live line" (added alongside this PR).

Proof: per-frame trace

webviews/test/transcript-live-line-continuous-browser.test.ts (real Chromium, cmuxAfterPaint reads every painted frame). A turn steps through Thinking → grep running → grep done → a thought → sed running → sed done, 12 frames each. Across every frame it asserts:

  • one line element and one shimmer band element;
  • the band's animation time only moves forward (no restart);
  • words and a time on the line in every frame (no blank frame);
  • the turn's clock (never under the 15s already elapsed, never going back);
  • "Ran grep" and "Ran sed" between calls, never "Thinking".

The trace it prints is below.

Head (green), every painted frame:

72 frames
frame | step | line el | band el | band t (ms) | text | time
0 | thinking | #1 | #2 | 0.0 | Thinking | 15s
1 | thinking | #1 | #2 | 0.0 | Thinking | 15s
2 | thinking | #1 | #2 | 16.6 | Thinking | 15s
3 | thinking | #1 | #2 | 33.4 | Thinking | 15s
4 | thinking | #1 | #2 | 50.0 | Thinking | 15s
5 | thinking | #1 | #2 | 66.7 | Thinking | 15s
6 | thinking | #1 | #2 | 83.3 | Thinking | 15s
7 | thinking | #1 | #2 | 100.0 | Thinking | 15s
8 | thinking | #1 | #2 | 116.7 | Thinking | 15s
9 | thinking | #1 | #2 | 133.3 | Thinking | 15s
10 | thinking | #1 | #2 | 150.0 | Thinking | 15s
11 | thinking | #1 | #2 | 166.7 | Thinking | 15s
12 | grep running | #1 | #2 | 183.3 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
13 | grep running | #1 | #2 | 200.0 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
14 | grep running | #1 | #2 | 216.7 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
15 | grep running | #1 | #2 | 233.3 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
16 | grep running | #1 | #2 | 250.0 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
17 | grep running | #1 | #2 | 266.7 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
18 | grep running | #1 | #2 | 283.3 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
19 | grep running | #1 | #2 | 300.0 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
20 | grep running | #1 | #2 | 316.7 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
21 | grep running | #1 | #2 | 333.4 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
22 | grep running | #1 | #2 | 350.0 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
23 | grep running | #1 | #2 | 366.6 | Running grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
24 | grep done | #1 | #2 | 383.3 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
25 | grep done | #1 | #2 | 400.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
26 | grep done | #1 | #2 | 416.6 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
27 | grep done | #1 | #2 | 433.3 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
28 | grep done | #1 | #2 | 450.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
29 | grep done | #1 | #2 | 466.7 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
30 | grep done | #1 | #2 | 483.3 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
31 | grep done | #1 | #2 | 500.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
32 | grep done | #1 | #2 | 516.7 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
33 | grep done | #1 | #2 | 533.4 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
34 | grep done | #1 | #2 | 550.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
35 | grep done | #1 | #2 | 566.6 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
36 | thought | #1 | #2 | 583.3 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
37 | thought | #1 | #2 | 600.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
38 | thought | #1 | #2 | 616.6 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
39 | thought | #1 | #2 | 633.3 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
40 | thought | #1 | #2 | 650.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
41 | thought | #1 | #2 | 666.6 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
42 | thought | #1 | #2 | 683.3 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
43 | thought | #1 | #2 | 700.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
44 | thought | #1 | #2 | 716.6 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
45 | thought | #1 | #2 | 733.4 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
46 | thought | #1 | #2 | 750.0 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
47 | thought | #1 | #2 | 766.7 | Ran grep -n -E 'environ|GH_QUOTA_[A-Z_]+|low_water' ~/.local/bi… | 15s
48 | sed running | #1 | #2 | 783.3 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
49 | sed running | #1 | #2 | 800.0 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
50 | sed running | #1 | #2 | 816.7 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
51 | sed running | #1 | #2 | 833.3 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
52 | sed running | #1 | #2 | 850.0 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
53 | sed running | #1 | #2 | 866.6 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
54 | sed running | #1 | #2 | 883.3 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
55 | sed running | #1 | #2 | 900.0 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
56 | sed running | #1 | #2 | 916.6 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
57 | sed running | #1 | #2 | 933.3 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
58 | sed running | #1 | #2 | 950.0 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
59 | sed running | #1 | #2 | 966.6 | Running sed -n 1,40p ~/.local/bin/gh-quota | 15s
60 | sed done | #1 | #2 | 983.3 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 15s
61 | sed done | #1 | #2 | 1000.0 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
62 | sed done | #1 | #2 | 1016.6 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
63 | sed done | #1 | #2 | 1033.3 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
64 | sed done | #1 | #2 | 1049.9 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
65 | sed done | #1 | #2 | 1066.6 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
66 | sed done | #1 | #2 | 1083.3 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
67 | sed done | #1 | #2 | 1099.9 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
68 | sed done | #1 | #2 | 1116.6 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
69 | sed done | #1 | #2 | 1133.3 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
70 | sed done | #1 | #2 | 1150.0 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s
71 | sed done | #1 | #2 | 1166.6 | Ran sed -n 1,40p ~/.local/bin/gh-quota | 16s

Base source (red), first and last frame of each step:

49 | sed running | #1 | #2 | 800.0 | Running sed -n 1,40p ~/.local/bin/gh-quota | 0s
59 | sed running | #1 | #2 | 966.6 | Running sed -n 1,40p ~/.local/bin/gh-quota | 0s
60 | sed done | #1 | #2 | 983.3 | Thinking | 15s
71 | sed done | #1 | #2 | 1166.6 | Thinking | 16s

Tests

Base fails and head passes:

  • the trace test, red on base (the clock reads "0s" at the call, the line reads "Thinking" after a call);
  • bun test test/transcript-codex-live-line.test.tsx test/transcript-running-live-line.test.tsx test/transcript-working-line.test.tsx: 46 pass on head. These tests encoded the 2026-10-20 spec ("Thinking -> Running -> Thinking", a clock per call) and now encode Leo's new one.

Observed / Not run

  • Observed: every unit test file that touches the live line or the Thinking component passes when run on its own (32 files). vp fmt and vp lint (with type checks) are clean on the changed files.
  • The browser trace runs through cmux-ci, because this VM skips browser lanes. Result: 1 pass at head (run 41264916394). With the source reverted to base it fails, 0 pass and 1 fail (run 41265476768): the clock reads 0s at each call and the line reads "Thinking" after a call.
  • Not run: a DEV build. That is the next step after this lands.

Follow-ups (from review)

  • The lifted run's version (10v+3) can equal the ended open fold's (5v'+3) if one snapshot ends the turn and doubles that run's version. It needs a run state of its own, and the minimap test's pinned versions have to move in the same PR.
  • Find counts a match on the call the line names but cannot reveal it until the next step. Range copy has the same gap.
  • When a run held only the lifted call, its row leaves and comes back at the next call.
  • The no-plan and merged-run fixes from review are in (joined, no lift with a todo list).

Changelog

  • Changed: A running turn shows one live line that names the latest step (Running/Ran …) with the turn's time and tokens, instead of a separate Thinking line.

risk:normal

Agent session: agent session

🤖 Generated with Claude Code