Skip to content

Web UI: tail-paginate session transcripts with a progress banner#288

Merged
edwin-zvs merged 1 commit into
mainfrom
webui-tail-paginate-transcript-v2
May 31, 2026
Merged

Web UI: tail-paginate session transcripts with a progress banner#288
edwin-zvs merged 1 commit into
mainfrom
webui-tail-paginate-transcript-v2

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Problem

Opening a session in the web UI used to fetch the entire transcript in one session.transcript round trip and render every event in a single synchronous main-thread loop. For an ultracode-shaped session (1.5 GB transcript on disk, ~3.9M events) that meant several seconds of wire wait, then a UI freeze while every event parsed and built DOM. The only feedback was a static skeleton shimmer that appeared after 150 ms and never updated.

Fix

Tail-first: the first round trip returns just the most recent N events. Older pages then fill in upward without blocking the UI.

Server

  • TranscriptParams gains tail: Option<usize>. When set, from / limit are ignored and the response is the last N events.
  • Storage::read_transcript_tail(id, n) seeks backward in 64 KiB chunks until it has at least n + 1 newlines (so a partial first line never chops a real event), then parses only those lines. O(n × line size), not O(file size).
  • SessionManager::transcript takes total from the live transcript_count atomic, so even a multi-GB transcript responds with bounded latency.

Web UI (crates/daemon/assets/index.html)

  • Fetches { session_id, tail: 500 } first and renders the tail immediately, scrolled to the bottom — the user sees the live tail almost as fast as the previous skeleton would have appeared.
  • If total > tail.length, pins a sticky progress banner ("Loading older history… 1,200 / 12,400 (10%)") to the top of the pane and walks forward from from: 0 in 500-event pages, prepending each batch above the tail. Scroll position is preserved relative to the bottom so prepends never make the visible content jump.
  • Yields between pages with requestAnimationFrame so the page stays responsive.

Client::transcript keeps its current shape (passes tail: None) so the zarvis / MCP / TUI Rust callers don't need to opt in.

Test

  • Storage: 5 unit tests on read_transcript_tail — last-N order, chunk-boundary safety (lines longer than the chunk size), n > total, missing file, n = 0.
  • Session manager: transcript_tail_returns_last_n_with_live_total asserts total comes from the live counter and the slice is the right tail.
cargo test -p agentd --bin agentd   # 60 passed (55 prior + 5 new)
cargo build                          # clean

🤖 Generated with Claude Code

Opening a session in the web UI used to fetch the entire transcript in
one `session.transcript` round trip and render every event in a single
synchronous main-thread loop. For an `ultracode`-shaped session (1.5 GB
transcript on disk, millions of events) that meant several seconds of
wire wait, then a UI freeze while ~3.9M events parsed and built DOM,
covered only by a static skeleton shimmer.

Switch the first round trip to a tail page: a new `tail: Option<usize>`
field on `TranscriptParams` tells the daemon to return just the most
recent N events, and `read_transcript_tail` seeks backward through
`pty.log` style 64 KiB chunks to read those N events without scanning
the whole file. `total` comes from the live `transcript_count` counter,
so even on a multi-GB transcript the response is bounded and instant.

Then the web UI:

- Renders the tail (500 events) immediately, scrolled to the bottom —
  the user sees the live tail almost as fast as the previous skeleton
  would have appeared.
- If `total > tail.length`, pins a sticky progress banner
  ("Loading older history… 1,200 / 12,400 (10%)") to the top of the
  pane and walks forward from `from: 0` in 500-event pages, prepending
  each batch above the tail. Scroll position is preserved relative to
  the bottom so prepends never make the visible content jump.
- Yields between pages with `requestAnimationFrame` so the page stays
  responsive while history fills in.

The existing skeleton still flashes if the first tail fetch is slow
enough to notice (>150 ms).

Tests:
- Storage: 5 cases for `read_transcript_tail` (last-N order,
  chunk-boundary safety, n>total, missing file, n=0).
- Session manager: `transcript(.., tail)` returns `total` from the live
  counter and the correct trailing slice.

`Client::transcript` keeps its current shape (passes `tail: None`) so
the zarvis / MCP / TUI Rust callers don't need to opt in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@edwin-zvs
edwin-zvs merged commit f0c06f3 into main May 31, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the webui-tail-paginate-transcript-v2 branch May 31, 2026 00:22
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