Chat: keep the conversation to what was actually said (#46)#53
Merged
Conversation
A step the agent spent entirely on tools still drew a row: a glyph, a clock, three action buttons and a work pill with no sentence beside them. A long task put half a dozen of those between one reply and the next. Those steps no longer get a row. The list holds their ids until a message that does speak comes along and hands them to it, so the reply's pill counts the whole stretch and opens the trace at the *start* of it rather than at the reply's own last command. Nothing is hidden — the rail still holds every call, the turn strip still counts them, and a trace row or a search hit aimed at a suppressed step now lands on the reply that speaks for it instead of scrolling to an element that is not there. The carried ids travel as a joined string, and the bubble subscribes to the steps it carries: a tool call can report its duration after its own message has closed, and the pill that inherited it has to hear about it. Both stay inside the per-message subscription tier, so a streaming turn still re-renders one bubble rather than the list. Kept: a reply that has opened and produced nothing yet still shows its caret. That is an answer about to arrive, not machinery. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the chat transcript rendering so that assistant “steps” which contain only tool calls / reasoning and no written content no longer render as standalone rows, while preserving full trace visibility and making the next spoken reply “carry” the suppressed work (including trace navigation to the start of that stretch).
Changes:
- Suppress transcript rows for assistant messages with no visible (non-tool/non-thinking) content, and carry their ids forward to the next visible reply for pill counts and trace targeting.
- Update trace/search-to-transcript navigation so suppressed steps resolve to the visible row that stands for them (
rowFor). - Add/extend unit and browser E2E coverage for silent-step suppression, carried work pill counts, and trace focusing behavior; increase browser virtual-time budget accordingly.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/client/shell/chat/MessageBubble.tsx | Defines the “visible row” rule, carries silent-step ids into work summarization, and targets the work pill at the earliest counted event. |
| src/client/shell/chat/MessageList.tsx | Computes displayed rows per turn (rowsOf) and attaches carried silent-step ids to the next visible message bubble. |
| src/client/shell/chat/ChatView.tsx | Introduces rowFor and updates reveal/scroll behavior so suppressed steps scroll to the correct visible row. |
| test/chat-messages.test.js | Adds unit tests for suppressing tool-only rows, preserving caret behavior, and counting carried work. |
| test/chat-view.test.js | Adds direct tests for rowFor resolution behavior across silent steps and trailing machinery. |
| test/browser/checks.ts | Adds an end-to-end browser check validating row suppression + trace visibility + pill focus (snapshot and live streaming). |
| test/browser/run.js | Increases --virtual-time-budget to accommodate the expanded browser suite. |
| CHANGELOG.md | Documents the user-facing transcript behavior change and the added browser coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+337
to
+342
| export function hasVisibleContent(message: ChatMessage): boolean { | ||
| if (message.role === 'user') return true; | ||
| if (visibleBlocks(message) > 0) return true; | ||
| // Opened and still empty: the caret. See the early return in the bubble. | ||
| return message.blocks.length === 0 && Boolean(message.streaming); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #46. Targets
chore/version-bump-5.2.0(PR #50, the 5.2.0 container), notmain.The problem
A step the agent spent entirely on tools still drew a row in the conversation: a glyph, a clock, three action buttons and a work pill with no sentence beside them. A long task put half a dozen of those between one reply and the next, so skimming the chat meant stepping over rows that said nothing.
What changed
MessageBubblerenders nothing for a non-user message that would paint no block — independent of the display settings, and while it is still streaming too (the live ribbon is what says the agent is working).MessageListholds the suppressed ids until a message that does speak comes along and hands them to it. That reply's pill counts the whole stretch —3 commands · 1 reasoning · 8.1s— and opening it lands on the trace at the start of the stretch rather than at the reply's own last command.rowFor) instead of scrolling to an element that is no longer in the document —TranscriptSearchmatches tool output, so that was reachable.Performance shape is unchanged: the carried ids travel as a joined string rather than an array so
React.memostill bites, and the bubble subscribes to the steps it carries (a tool call can report its duration after its own message has closed) — still the per-message tier, never the whole list.Acceptance criteria
Verification
npm test— 1381 passing (new cases inchat-messages.test.js,chat-view.test.js)npm run typecheck— cleannpm run test:browser— 298 PASS / 0 FAIL, including a new end-to-end check that mounts the realChatView, counts the rows, clicks the pill and asserts the trace expanded the first silent step, then repeats the case live over streamed events. The suite's virtual-time budget was raised: a run that outgrows it reports no results at all rather than a failure.🤖 Generated with Claude Code