You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The tool calls are not rendered and presented properly to the end-user. (see video)
display_tool_calls.mov
I think presenting tool calls in a comprehensible way to the end-user not only enhances the user experience, but also makes it easier to debug, perform QA, and create good snapshot tests. For these reasons, I did a bit of scoping on how we could enable that.
Background
Tool calls (Bash, FileEditor, Grep, MCP, etc.) currently render through a single generic markdown-based path. The UI shows readable but unattractive output: bold labels, raw JSON dumps for MCP, one-line backtick commands for Bash, and no diff view for file edits.
The rendering path today:
src/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.tsx:31 calls getEventContent(event) and feeds the result into GenericEventMessage.
src/components/features/chat/generic-event-message.tsx is the collapsible card. Its details prop already accepts string | React.ReactNode, but in practice nearly everything passes markdown.
src/components/conversation-events/chat/event-content-helpers/get-action-content.ts and get-observation-content.ts are two large switch (kind) blocks that build markdown strings per tool.
The fallback (shared.ts:5) is JSON.stringify(event, null, 2) inside a fenced block.
There is precedent for React-component rendering: TaskTrackerObservation already returns <TaskTrackingObservationContent> instead of markdown (get-event-content.tsx:296). This issue generalizes that pattern.
Worst offenders today
Tool
Current rendering
MCPToolAction (get-action-content.ts:103)
Raw JSON.stringify(action.data, null, 2) inside a code block
ExecuteBashAction (get-action-content.ts:89)
Command:\n\${cmd}`` — single line, no syntax highlight, no copy
FileEditorAction (get-action-content.ts:68)
Only create shows file content; edit / str_replace / view show nothing useful
Add one import and one entry in the ALL array in index.ts.
Add <name>.test.tsx using renderVisualizer with fixtures.
TypeScript enforces the rest: actionKind autocompletes from the Action union, the Body component receives a narrowed event type, and typos in kinds are compile errors.
Testing
Co-locate *.test.tsx next to each visualizer.
Reuse fixtures already in __tests__/components/conversation-events/chat/.
One snapshot per (action, observation) pair, plus error states for Bash (non-zero exit) and FileEditor (failed edit).
Existing tests in __tests__/components/conversation-events/chat/event-message-*.test.tsx and __tests__/utils/handle-event-for-ui.test.ts must continue to pass for unmigrated tools (fallback path).
Risks and edge cases
The markdown strings are also consumed by ACP tool-call rendering (get-event-content.tsx:305), which builds details "the same way" as bash output. The dispatcher must only intercept the action/observation details body, not the title pipeline or the ACP path.
correspondingAction may be undefined for an observation event (e.g., resumed conversation, missing action). The dispatcher falls back to markdown in that case.
Streaming: action arrives before observation. Visualizers render the action card immediately; the observation card appears later. Two-card layout means no coordination needed.
Acceptance criteria
tool-visualizers/ directory exists with define.ts, index.ts, dispatcher.ts, test-utils.tsx, and the five primitives.
Bash, FileEditor (all four commands), and Grep/Glob render via the new visualizers, both action and observation cards.
All other tools render unchanged (markdown fallback).
Snapshot tests cover the migrated visualizers, including error states.
Existing tests in __tests__/ pass without modification.
README or in-file comment in tool-visualizers/index.ts documents the three-step recipe for adding a new visualizer.
Problem
The tool calls are not rendered and presented properly to the end-user. (see video)
display_tool_calls.mov
I think presenting tool calls in a comprehensible way to the end-user not only enhances the user experience, but also makes it easier to debug, perform QA, and create good snapshot tests. For these reasons, I did a bit of scoping on how we could enable that.
Background
Tool calls (Bash, FileEditor, Grep, MCP, etc.) currently render through a single generic markdown-based path. The UI shows readable but unattractive output: bold labels, raw JSON dumps for MCP, one-line backtick commands for Bash, and no diff view for file edits.
The rendering path today:
src/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.tsx:31callsgetEventContent(event)and feeds the result intoGenericEventMessage.src/components/features/chat/generic-event-message.tsxis the collapsible card. Itsdetailsprop already acceptsstring | React.ReactNode, but in practice nearly everything passes markdown.src/components/conversation-events/chat/event-content-helpers/get-action-content.tsandget-observation-content.tsare two largeswitch (kind)blocks that build markdown strings per tool.shared.ts:5) isJSON.stringify(event, null, 2)inside a fenced block.There is precedent for React-component rendering:
TaskTrackerObservationalready returns<TaskTrackingObservationContent>instead of markdown (get-event-content.tsx:296). This issue generalizes that pattern.Worst offenders today
MCPToolAction(get-action-content.ts:103)JSON.stringify(action.data, null, 2)inside a code blockExecuteBashAction(get-action-content.ts:89)Command:\n\${cmd}`` — single line, no syntax highlight, no copyFileEditorAction(get-action-content.ts:68)createshows file content;edit/str_replace/viewshow nothing usefulGrepAction/GlobAction(get-action-content.ts:50)shared.ts:5)Goals
Non-goals
Proposed architecture
Type-safe registry via a
defineVisualizerhelperA single helper that narrows
Action/Observationby their literalkindso visualizer bodies receive correctly typed props without casts:Explicit barrel registration
Auto-discovery via
import.meta.globwas considered and rejected: it hurts tree-shaking, breaks test mocking, and hides what is wired.Dispatch seam
get-event-content.tsxchecks the registry before falling through to the existing markdown helpers:If
resolveVisualizerreturns null, the current markdown pipeline runs unchanged.Shared primitives
Dumb, presentation-only components in
tool-visualizers/primitives/. No knowledge of event types. Each is a few dozen lines of Tailwind:CodeBlock— monospace, language hint, copy button, expand when long.FilePathChip— monospace path with copy icon and optional line range.DiffView— unified diff forstr_replace-style edits.OutputPane— stdout / stderr split, exit-code badge, collapsible.KeyValueGrid— for simple key/value displays.Example visualizer (full file)
Folder layout
Scope of this issue (Tier 1)
Migrate the three highest-traffic, ugliest-today tools, each as an action + observation pair:
ExecuteBashActionExecuteBashObservationFileEditorAction,StrReplaceEditorActionFileEditorObservation,StrReplaceEditorObservationGrepAction,GlobActionGrepObservation,GlobObservationVisual targets
CodeBlock(bash) for command,OutputPanewith stdout/stderr split and exit-code badgecreateFilePathChip+CodeBlockwith detected languagestr_replaceDiffViewof old vs newviewFilePathChip+ line-range badgeHow to add a new visualizer (post-merge)
Documented in
tool-visualizers/index.ts:tool-visualizers/<name>/<name>.tsxexportingdefineVisualizer({ ... }).ALLarray inindex.ts.<name>.test.tsxusingrenderVisualizerwith fixtures.TypeScript enforces the rest:
actionKindautocompletes from theActionunion, theBodycomponent receives a narrowed event type, and typos in kinds are compile errors.Testing
*.test.tsxnext to each visualizer.__tests__/components/conversation-events/chat/.__tests__/components/conversation-events/chat/event-message-*.test.tsxand__tests__/utils/handle-event-for-ui.test.tsmust continue to pass for unmigrated tools (fallback path).Risks and edge cases
get-event-content.tsx:305), which builds details "the same way" as bash output. The dispatcher must only intercept the action/observationdetailsbody, not the title pipeline or the ACP path.correspondingActionmay be undefined for an observation event (e.g., resumed conversation, missing action). The dispatcher falls back to markdown in that case.Acceptance criteria
tool-visualizers/directory exists withdefine.ts,index.ts,dispatcher.ts,test-utils.tsx, and the five primitives.__tests__/pass without modification.tool-visualizers/index.tsdocuments the three-step recipe for adding a new visualizer.Follow-ups (not in this issue)