Skip to content

feat(panel): keep huge sessions navigable — transcript windowing + delegated collapse - #317

Merged
upsetbit merged 3 commits into
masterfrom
feat/panel-transcript-windowing
Jul 9, 2026
Merged

feat(panel): keep huge sessions navigable — transcript windowing + delegated collapse#317
upsetbit merged 3 commits into
masterfrom
feat/panel-transcript-windowing

Conversation

@upsetbit

@upsetbit upsetbit commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

Opening a very large session in the panel (e.g. 1.9B tokens, hundreds of thousands of turns) froze the browser tab. Two compounding causes:

  1. The side panel rendered the entire transcript at onceSessions.Get had no turn windowing (SELECT … FROM turns with no LIMIT), so every turn landed in the DOM.
  2. Every bubble was a live Alpine component with its own x-data and a window-level listener; tool/thinking groups too. Thousands of turns meant thousands of reactive components initialized on load, and "Collapse all" / in-transcript search dispatched synchronous O(N) event storms.

What changed

API (Sessions.Get) — new optional turn_limit / turn_offset / turn_tail request fields window the chronological turn stream server-side; the response now carries total_turns and message_turns. turn_limit = 0 keeps the old return-everything behavior, so existing callers are unaffected.

Transcript paging (panel) — the side panel requests only the last 200 turns (turnPageSize) and opens on the tail of the conversation. A "↑ load earlier turns (N more)" anchor at the top of the transcript HTMX-swaps in the preceding 200-turn page (GET /sessions/<id>/turns?before=N), stacking older pages above the ones on screen until turn 0. The "turns" KPI uses the server-side message_turns count, so it stays whole-session-accurate regardless of the window.

Delegated collapse (panel) — per-bubble collapse, tool/thinking group disclosure and "Collapse all" are now plain classes + [hidden] flipped by delegated listeners in assets/transcript.js: a constant number of listeners per document instead of one Alpine component + one window listener per message. The in-transcript search keeps working through the same messages-toggle-all / transcript-expand-groups events it already dispatched. Bubble visuals are unchanged.

Validation

Exercised end-to-end against a disposable Postgres/MinIO stack with a synthetic 1,260-turn session (plus a small control session):

  • Initial render: ~1.6k DOM nodes and 1 Alpine component in the panel (the search box), vs. the full transcript before.
  • "Collapse all" over 115 loaded bubbles: 2.9 ms.
  • Paged backwards 6 times to turn 0; anchor updates its count each page and disappears at the start.
  • Search expands all groups and highlights matches; zero console errors.
  • Small sessions (< 200 turns) render fully with no anchor.
  • go test ./..., go vet, golangci-lint clean; screenshots compared against the current bubble design — pixel-identical.

Docs (docs/panel/screens.md, components.md, design brief, panel skill + reviewer agent) updated to describe the windowing and the no-Alpine-per-turn rule.

Note: the in-transcript search only sees loaded turns; server-side transcript search is a natural follow-up if that bites in practice.

🤖 Generated with Claude Code

upsetbit and others added 3 commits July 7, 2026 23:13
Get now accepts turn_limit, turn_offset and turn_tail so clients can
read a bounded slice of a session's chronological turn stream instead
of every row. The response carries total_turns and message_turns so a
windowed client can place its slice and keep whole-session KPIs exact.
A zero turn_limit preserves the previous return-everything behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The session side panel now renders only the last 200 turns
(turnPageSize) and opens on the tail of the conversation. A 'load
earlier turns (N more)' anchor at the top of the transcript swaps in
the preceding 200-turn page via HTMX (GET /sessions/<id>/turns) until
turn 0 is reached. The turns KPI uses the server-side message_turns
count so it covers the whole session regardless of the window.

Bubble collapse, tool/thinking group disclosure and 'Collapse all' are
now plain classes + [hidden] flipped by delegated listeners in
assets/transcript.js — a constant number of listeners per document
instead of one Alpine component and one window listener per message,
which froze the tab on sessions with tens of thousands of turns. The
in-transcript search keeps working through the same window events it
already dispatched. Bubble visuals are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
screens.md and components.md now describe the 200-turn window, the
'load earlier turns' anchor and the transcript.js delegated collapse
mechanism; the panel skill and reviewer agent state that repeated
per-turn elements never get Alpine components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@upsetbit
upsetbit merged commit 3fecffd into master Jul 9, 2026
4 checks passed
@upsetbit
upsetbit deleted the feat/panel-transcript-windowing branch July 9, 2026 15:12
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