Skip to content

Feature: render session transcript as interleaved AI-comment / CLI-action bubbles #3397

@PatrickNoFilter

Description

@PatrickNoFilter

Feature: render session transcript as interleaved AI-comment / CLI-action bubbles

Summary

The session transcript view (and session replay) currently groups all
tool calls for a turn into a single assistant bubble (with tool cards
collapsed inside it), with any AI commentary emitted before/during
those tool calls also collapsed into the same bubble. The dash TUI
shows the same data chronologically: each AI text segment and each
tool call as a separate row, in the order they were actually emitted.

I'd like the WebUI to offer the same chronological / interleaved view.

Current behavior

Given a turn where the assistant thinks, calls a tool, thinks again,
calls another tool, and emits final text, the WebUI shows:

💬 user: "fix the bug"
🤖 assistant: "I'll check the file first."
[▸ terminal: cat /path/to/file]
[▸ terminal: patch -p1 < fix.patch]
"Done — here's what changed."

The "I'll check the file first." text, the two terminal tool cards,
and the "Done — here's what changed." text are all rendered inside
one assistant bubble. You have to expand each tool card to see what
it did, and the chronological relationship between the AI commentary
and the tool calls is hard to follow.

Desired behavior

A new view mode that renders the turn chronologically as separate
bubbles, in the order they were actually emitted:

💬 user: "fix the bug"
🤖 ai: "I'll check the file first."
⏵ terminal: cat /path/to/file
(output: …)
🤖 ai: "Now I'll apply the patch."
⏵ terminal: patch -p1 < fix.patch
(output: …)
🤖 ai: "Done — here's what changed."

This matches what the dash TUI already shows and is what most users
expect when they read a "what did the agent actually do" log.

Why it matters

  1. Readability — long turns with many tool calls are hard to scan
    when everything is collapsed into one bubble.
  2. Debugging — when something goes wrong mid-turn, the user has
    to expand each tool card to find which step failed and what the
    assistant was thinking at the time.
  3. Audit / log review — the chronological view IS the source of
    truth (it's how the data is stored in the journal); the current
    view is a derived layout that drops temporal information.
  4. Parity with dash TUI — users switching between the two UIs
    shouldn't have to mentally re-arrange the same data.

Proposed approach

The session message data already preserves the ordering (assistant
message has content[] with text and tool_use parts in sequence;
separate role: 'tool' messages have tool_call_id linking back).
The render layer just needs to walk those parts in order and emit
one row per part instead of merging them into a single assistant
row.

Sketch of the change, roughly localized to static/ui.js's
renderMessages function (line 6453 in current master):

// Where today (line ~475) the code does:
//   (m.tool_calls||[]).forEach(tc => { … render tool card … });
// inside an outer "render one assistant row" loop, change to:

function* walkMessageParts(m) {
  // For each assistant message, yield interleaved
  // {kind:'text', text} and {kind:'tool', toolCall} in order.
  for (const part of (m.content || [])) {
    if (part?.type === 'text' && part.text) yield {kind:'text', text: part.text};
    else if (part?.type === 'tool_use')     yield {kind:'tool',  toolCall: part};
  }
  // Then any top-level tool_calls array entries.
  for (const tc of (m.tool_calls || [])) yield {kind:'tool', toolCall: tc};
}

Then in the render loop, instead of "one row per message", walk
each emitted part and render text parts as assistant text bubbles
and tool parts as their own tool cards. role: 'tool' result
messages stay as separate bubbles, already keyed by
tool_call_id so they can be inserted adjacent to their parent
tool card (or remain grouped, depending on view mode).

Scope of change

  • static/ui.js — the renderMessages function (~700 lines). Most
    of the change is in the assistant-message rendering branch.
  • Possibly static/sessions.js — if a "view mode" toggle is added
    (collapsed / interleaved / TUI-mode), state lives here.
  • Likely a small CSS addition for the new "tui-bubble" row style.
  • No backend changes — all the needed data is already on each
    assistant message.

Open questions for maintainers

  1. Default view? Stay collapsed (current default) and add a
    per-session "tui view" toggle, or flip the default? (I'd vote
    keep collapsed default to avoid regressing long transcripts;
    offer the toggle.)
  2. Reuse / re-derive tool result bubbles? Today role:'tool'
    messages are rendered too. In interleaved mode, should the tool
    result stay adjacent to the tool card (preferred) or remain a
    separate bubble?
  3. Live streaming? Mid-stream, the assistant message is still
    being filled — render it in collapsed mode until streaming ends,
    then "promote" to interleaved on settle? (See fix: reattach SSE on session-switch return and preserve live progress (closes #2924) #3005 for related
    live-stream work.)
  4. Compaction messages? Compressed turns synthesize a single
    assistant message with no parts. Should those be skipped in
    interleaved mode, or rendered with a "summary" marker?

Related

Implementation offer

Happy to send a PR for this. Suggest splitting into two:

  1. Backend-agnostic: the walkMessageParts helper + unit test
    against a few representative session transcripts.
  2. UI: wire it into renderMessages with a view-mode toggle,
    default off, behind a per-session preference.

Reference: dash TUI's transcript renderer (in hermes-agent dash
code) is the inspiration and the reference for ordering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsprint-candidateStrong candidate for next sprintuxUser experience / visual polish

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions