Follow a live session: watch a running agent, and come back correct after a break #66
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/claude-code-query!66
Loading…
Reference in a new issue
No description provided.
Delete branch "cc-chat"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Slice 03 of the cc-chat effort — the third and last local slice on this branch. Slices 01 and 02 shipped in #64 and #65; this one is
project-management/efforts/cc-chat/slices/03-follow-live-session/.The card (frozen)
Goal. I can leave a page open on a Claude Code session I am working in and watch what happens as it happens — and when the connection breaks, the page comes back showing what actually happened rather than a gap.
Fit test. Michal opens the watcher on his own running session, works in it for a stretch, deliberately breaks the connection, and reads the page: nothing he knows happened is missing, nothing is doubled, no row from before the break has changed, and "would I keep this open instead of reading the terminal?" is answered yes.
Fit passed on his read, against a real break: he changed location, the tunnel he reads through dropped, the page said it was not following and was trying, and it reconnected on its own when the tunnel came back.
What it ships
Following a growing file.
ForwardLineReaderreads the complete lines appended after an offset and holds back one the writer has not finished;BackwardLineReader.lastLineEndis the hand-over point from a tail read to a follow;ZioConversationArchive.followputs them over the archive's own vendor-then-mirror resolution. Everything a follow remembers is one byte offset.The stable point, decided by comparison.
TranscriptFollowrenders the transcript and compares the new rendering with the last one, so the page is told the smallest tail that will do. It is not a prediction about which rows may move, so it stays true whatever the rendering's rules become — and the one producer stays on the JVM, per CC-D3.A follow operation on the contract.
TranscriptEndpoint.followPath, a Tapir description over the consumer's ownStreams, andTranscriptTailas the message: a count line and the markup after it. Deliberately not JSON — reading two fields with a Scala JSON stack took the bundle from 12 750 to 151 128 bytes gzipped.The element follows.
<cc-chat session="…" follow>subscribes, replaces only the tail, and keeps the settled rows as the very same DOM nodes — with them the reader's place and whatever they have opened. A reader at the newest row stays there; a reader who scrolled back is left alone. What scrolls is whatever the host made scrollable, the page otherwise.The page says whether it is following, and says it from what it hears rather than from what its socket reports. A connection can go silent while it is still open — a frozen host, a wedged proxy — and the browser never mentions it, so the host beats every 5 s and 15 s of silence is a page that has stopped. The page reopens a connection nobody else can see is dead.
A test module for
ui.jsunder jsdom, which the parked item asked for: the element is driven in a real DOM against a page whose subscription, timer and clock belong to the test.Verified in a browser, on a live session
Review
Eight reviewers (style, testing, security, scala3, zio, architecture, api, composition). Two criticals, both fixed in-loop with a failing test first and each new test proved fallible by mutation:
PageSourceMoved, what the reading path already says for a cursor whose source moved.Also taken: one home for the line decoder, a warning logged when a running follow fails, the port saying where following lives and why it is not on it,
Cache-Control: no-storeand the beat stated on the follow endpoint, and a test pinning that rendered rows never end in a newline. Full report:project-management/efforts/cc-chat/slices/03-follow-live-session/review-slice-2026-08-26.md.One review claim was measured and rejected: the host binds
127.0.0.1, not every interface.Follow-ups (not in this PR)
TestClockrewrite would make them deterministic.blockinghelper in the ZIO archive./followas a noun) keeps its existing trigger.Lessons
Triaged with Michal before this PR: two promoted to kanon (notes #185 and #186), one fixed at source. #185 carries a measurement made at triage — reading a JSON message costs 171 bytes through
js.JSON.parsebehind a facade, 19 KB through jsoniter-scala, 151 KB through upickle and 166 KB through circe, so a Scala JSON stack belongs in a page only to share real codecs with the JVM.Numbers
Bundle 19 127 bytes gzipped against a 20 000 gate (raised twice here, each time with the measurement recorded). 908 tests green, no warnings, scalafix and format clean.