Agent chat: Claude's hover rows under every message (read receipts, Pin, Read aloud)

Merged
TE
15d ago
+1774-671

Builds on #22675 (transcript-density's action rows, merged).

Problem

Leo (2026-10-20): the rows under chat messages should match Claude Code desktop.

  • Hovering an answer drew a box around it with "6:29 PM" and Copy at the far right.
  • Under a prompt there was only a lone bookmark icon, plus "Steered · the agent read it" under steers.

Change

  • Under an answer (TurnRows.tsx TurnFooter), at the answer's left edge:
    • Copy, Fork from here, Pin, Read aloud, then the answer's age ("43 minutes ago"; the exact date and time on hover).
    • Fork's tooltip has Claude's second line: "Starts a new session from this point. This one stays as it is." (data-tooltip-detail in ui/titleTooltips.ts).
    • Retry moved to the prompt. The answer footer no longer draws a focus outline; focusing it shows its row instead.
  • Under a prompt (UserBubble.tsx), at the bubble's right edge:
    • the age, a read receipt, Copy, Edit, Retry (only on the last ended turn's prompt), Fork and Pin.
    • The read receipt is ✓ "Delivered when the current step ends" while a steer waits (steer-default's wording; it drops its own waiting marker) and ✓✓ once the agent read it; hovering ✓✓ shows "Read by Claude Code" (the harness name).
  • Hover only. Neither row shows until you hover or focus it. Each keeps its height, so nothing shifts when it appears. Hovering a prompt shows only the prompt's row, not its turn's footer too, so one row shows at a time.
  • Removed: SteerMark ("Steering…" / "Steered · the agent read it"), the bookmark icon under prompts, and the always-on row for the latest prompt and turn.
  • Pin reuses the thread minimap's bookmarks. acpmux now also accepts a turn's turn_result seq, so answers can be pinned too (hub/bookmarks.rs).
    • Pinned prompts show in the minimap as before.
    • Pinned answers aren't listed in the summary panel yet (follow-up).
  • Read aloud uses the web view's speechSynthesis (the macOS voice). It is one message at a time, and pressing it again stops.
  • New strings, in all 21 locales: message.readBy, message.readByAgent, message.delivered, message.pin, message.unpin, message.readAloud, message.stopReading, turn.forkDetail. Removed the unused steer.*, message.read and message.unread.

Observed / Not run

  • Ran scripts/cmux-next/prepush-gate.py: no new violations.
  • Not run locally: web lint, typecheck, vitest and cargo. Cosmic-gray was at load 80, so CI runs these. Tests that pinned the old rows still need updating (in progress).

Changelog

  • Changed: Chat messages show Claude-style hover action rows: Copy, Fork, Pin, Read aloud and the message's age under answers; age, read receipt, Copy, Edit, Retry and Fork under prompts.

Agent session: cosmic-gray

🤖 Generated with Claude Code


Summary by cubic

Matches Claude Code desktop's hover action rows under every chat message, on hover or focus only. Answers show Copy, Fork (with Claude's "starts a new session" tooltip), Pin, Read aloud and the message's age under their left edge; prompts show age, a read receipt, Copy, Edit, Retry, Fork and Pin under the bubble's right edge; another agent's message shows age, the receipt, Copy, Retry and Pin under its card, after its Reply. A Chief-sent prompt gets the same row as one the person sends. The receipt is one check while a steer waits and two once read ("Read by "). The Steered line, the lone bookmark icon and the always-on latest rows are gone, and no answer footer draws a focus box. This also reconciles with base's steer-as-one-turn, row-height-estimate, Reply placement and rewritten Retry model menu (RetryModelMenu.tsx) changes, which the prompt's row and a failed turn's footer now reuse; Copy reuses base's CopyButton (tooltip name, resetting check).

Bug Fixes

  • Fixes the row-height estimate to reserve a line under a steer only while it waits.
  • A pinned prompt or answer keeps its pin through a fork.
  • The waiting receipt's tooltip gets its own locale key: base's message.delivered belongs to MessageCard.