Agent chat: real names, one row pitch and one tone on subagent, send, reaction and report rows

Merged
TE
2d ago
+461-60

Fixes the subagent, sub-session, reaction and report-batch rows of the cmux NEXT agent chat transcript where they disagreed with the transcript's row rules.

Leo, 2026-11-03: "i think we ought to audit the whole tool call/notice/send and recieve message styling esp like see what we can iterate on in the gallery too". On a capture where a finished subagent row read the generic "Subagent", a "Sent to" row showed a bare session id and the gaps were uneven, he asked for one row pitch and real names. On the activity rows: "this is pretty bad spacing and formatting" / "i thought we made this stuff smaller".

What changed

A finished sub-session's line reads its real name (subagents/SubagentDoneLine.tsx, subagents/subsessions.ts). The line took its label only from the fold record (subagentTaskLabel(agent)). A sub-session's record often names nothing once it ends, so the line read "Subagent", even though SubsessionDoneLine already reads the child's session row. The label now tries, in order: the record's task, then its name, then the child chat's title or name (never an opaque optchat id, via the new subsessionName), and only then "Subagent". The fallback came from #32176.

A "Sent to" row names a sub-session that left the session list (conversation/SubsessionCallCard.tsx, new conversation/startedNames.ts, App.tsx). target={live?.name ?? shortId(call.sessionId)} showed sub-perf… once the chat dropped off the list. send_message carries only the sessionId, so the name comes from the chat's own start_session call for that session. The pane builds the map once per snapshot and reuses it (startedNames, incremental the way sessionEdits is, and the same map while no start is added). The order is: the list's name, then the start call's name, then the short id. The id stays in the call's Raw and in the row's link. The short id came from #25047.

Activity rows' tone: base's #34523 (merged meanwhile) settled this: a line's words keep one tone and only its chevron lifts on hover. This PR takes that as is. No full-line plate on received lines (conversation/conversation.css, the .cv-received > .cv-disclosure__line hover rule): received lines had a 6% plate under the whole line on hover (#28113) that sent rows and tool rows don't have. The plate now shows only for keyboard focus, and the hover actions' fade-in uses the pane's ground. This is the rulebook's "a highlight covers only its own control". That rule sits outside the inline-message lane's lines (~2677 and .cv-received__sender). The tests that pinned the plate are updated (incoming-message-disclosure-browser.test.ts).

The reaction reply sits on the grid (conversation/conversation.css .cv-tools > .cv-reaction-reply, model.ts reactionReplyHeight). The chat's reply under a finished subagent's line had margin: 4px 0 6px on top of its own text height, and model.ts never counted any of it. That made the gap to the next row uneven and caused a shift on first paint. Under a done line it now has no margin of its own and sits on the notes' 20px lines at the rows' text column, and the row's estimate adds those lines. The reply under an opened call's output is unchanged. The margin came from #33283. Design choice: the gallery compares four looks. reply-grid is the one shipped; the others are reply-note (the run gap around it), reply-quote (a 2px rule at the text column) and reply-before (the base). I picked the grid look because it is the only one that keeps the one row pitch with nothing added.

Report batches draw one chevron (conversation/LaneNoticeBatch.tsx). Non-inbox report batches had a disclosure chevron glyph in the icon column plus the trailing chevron, so two chevrons on one line (#28749). The icon column now holds the batch's kind glyph: action.review for a batch of verdicts, otherwise agent.chat, which is the messages glyph MessageGroupHead uses. Only the trailing chevron is left. The helper that owns automated-notice collapsing confirmed its branch does not touch this file.

Gallery

  • agent-pane.subagent-done: subsession-titled (a sub-session with no task whose chat is titled), and reply-grid / reply-note / reply-quote / reply-before, each a reply under a done line followed by another row.
  • agent-pane.subsession-cards: message-after-list (a send whose target is not in the session list; its start call names it).
  • agent-pane.lane-notices (existing reports-* variants) and agent-pane.received-messages (existing) show the one chevron and the plate-free hover.

Tests

  • New webviews/test/transcript-activity-row-names.test.tsx:
    • the done line prefers the child's title, and an id never names it;
    • a send row names its target from the start call and never shows a bare id when one exists, and the map is reused while no start is added;
    • the reaction reply's estimate adds its 20px lines with no margin.
  • lane-notice-card.test.tsx: a report batch has one chevron and a glyph in its icon column.
  • incoming-message-disclosure-browser.test.ts: no plate under a hovered received line.
  • Fleet run of typecheck plus 15 touched suites: 208 pass, 0 fail.

Spec

design/chat-transcript.md: "One-line row pitch", "Subagent row anatomy" (new bullets: Real names, reply on the grid, one steady tone), "Inbox rows" (report batch one chevron, no received plate), and "Session link chip" (lit alone). Also design/transcript-spacing.md.

References: cmux/live/transcript-subagent-row-generic-name-spacing, cmux/live/transcript-activity-rows-spacing (private references repo).

Risk: small and contained. It changes web UI rows only. The new state is one memoized map built from the rows already in the snapshot.

Agent session: agent session

🤖 Generated with Claude Code