From b05a4add77941265581e3508ce50429143e6a8fa Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sat, 14 Mar 2026 23:12:36 -0400 Subject: [PATCH] fix(ui): render nested tools consistently across states Prior to this, nested tool calls would start in one color and finish in another. --- internal/ui/chat/agent.go | 4 ++-- internal/ui/chat/bash.go | 6 +++--- internal/ui/chat/diagnostics.go | 2 +- internal/ui/chat/fetch.go | 6 +++--- internal/ui/chat/file.go | 10 +++++----- internal/ui/chat/generic.go | 2 +- internal/ui/chat/lsp_restart.go | 2 +- internal/ui/chat/mcp.go | 2 +- internal/ui/chat/references.go | 2 +- internal/ui/chat/search.go | 8 ++++---- internal/ui/chat/todos.go | 2 +- internal/ui/chat/tools.go | 8 ++++++-- internal/ui/styles/styles.go | 18 +++++++++--------- 13 files changed, 38 insertions(+), 34 deletions(-) diff --git a/internal/ui/chat/agent.go b/internal/ui/chat/agent.go index e8840cc53be358010dc006aef6961290902b5983..882971290d7af78e21add7663f993731c618a967 100644 --- a/internal/ui/chat/agent.go +++ b/internal/ui/chat/agent.go @@ -101,7 +101,7 @@ type AgentToolRenderContext struct { func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.agent.nestedTools) == 0 { - return pendingTool(sty, "Agent", opts.Anim) + return pendingTool(sty, "Agent", opts.Anim, opts.Compact) } var params agent.AgentParams @@ -232,7 +232,7 @@ type agenticFetchParams struct { func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if !opts.ToolCall.Finished && !opts.IsCanceled() && len(r.fetch.nestedTools) == 0 { - return pendingTool(sty, "Agentic Fetch", opts.Anim) + return pendingTool(sty, "Agentic Fetch", opts.Anim, opts.Compact) } var params agenticFetchParams diff --git a/internal/ui/chat/bash.go b/internal/ui/chat/bash.go index 18be27ee01b4fcc21749789fc65ec0b71c2b0d4b..a6c2554768bd8dcbc986f0aa616ed4dcbc5ff046 100644 --- a/internal/ui/chat/bash.go +++ b/internal/ui/chat/bash.go @@ -41,7 +41,7 @@ type BashToolRenderContext struct{} func (b *BashToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Bash", opts.Anim) + return pendingTool(sty, "Bash", opts.Anim, opts.Compact) } var params tools.BashParams @@ -123,7 +123,7 @@ type JobOutputToolRenderContext struct{} func (j *JobOutputToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Job", opts.Anim) + return pendingTool(sty, "Job", opts.Anim, opts.Compact) } var params tools.JobOutputParams @@ -174,7 +174,7 @@ type JobKillToolRenderContext struct{} func (j *JobKillToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Job", opts.Anim) + return pendingTool(sty, "Job", opts.Anim, opts.Compact) } var params tools.JobKillParams diff --git a/internal/ui/chat/diagnostics.go b/internal/ui/chat/diagnostics.go index 68d2ac4a00dc880c27904468008fb8f6b2fcf9c5..e31d31832ed34ffa38226b64dd35818f7bf6cd0f 100644 --- a/internal/ui/chat/diagnostics.go +++ b/internal/ui/chat/diagnostics.go @@ -37,7 +37,7 @@ type DiagnosticsToolRenderContext struct{} func (d *DiagnosticsToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Diagnostics", opts.Anim) + return pendingTool(sty, "Diagnostics", opts.Anim, opts.Compact) } var params tools.DiagnosticsParams diff --git a/internal/ui/chat/fetch.go b/internal/ui/chat/fetch.go index e3f3a809550385dfd0ec557e98151ffc731acc93..55c730305e150b5feb3266ea1bce5a91c2013c6c 100644 --- a/internal/ui/chat/fetch.go +++ b/internal/ui/chat/fetch.go @@ -36,7 +36,7 @@ type FetchToolRenderContext struct{} func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Fetch", opts.Anim) + return pendingTool(sty, "Fetch", opts.Anim, opts.Compact) } var params tools.FetchParams @@ -111,7 +111,7 @@ type WebFetchToolRenderContext struct{} func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Fetch", opts.Anim) + return pendingTool(sty, "Fetch", opts.Anim, opts.Compact) } var params tools.WebFetchParams @@ -165,7 +165,7 @@ type WebSearchToolRenderContext struct{} func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Search", opts.Anim) + return pendingTool(sty, "Search", opts.Anim, opts.Compact) } var params tools.WebSearchParams diff --git a/internal/ui/chat/file.go b/internal/ui/chat/file.go index 3b1fef8530506be70e512a3cb801ed34a81e0c62..14fc5169aec1b0a238cad177a0be5bf4d6db27b0 100644 --- a/internal/ui/chat/file.go +++ b/internal/ui/chat/file.go @@ -39,7 +39,7 @@ type ViewToolRenderContext struct{} func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "View", opts.Anim) + return pendingTool(sty, "View", opts.Anim, opts.Compact) } var params tools.ViewParams @@ -125,7 +125,7 @@ type WriteToolRenderContext struct{} func (w *WriteToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Write", opts.Anim) + return pendingTool(sty, "Write", opts.Anim, opts.Compact) } var params tools.WriteParams @@ -180,7 +180,7 @@ type EditToolRenderContext struct{} func (e *EditToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { // Edit tool uses full width for diffs. if opts.IsPending() { - return pendingTool(sty, "Edit", opts.Anim) + return pendingTool(sty, "Edit", opts.Anim, opts.Compact) } var params tools.EditParams @@ -243,7 +243,7 @@ type MultiEditToolRenderContext struct{} func (m *MultiEditToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { // MultiEdit tool uses full width for diffs. if opts.IsPending() { - return pendingTool(sty, "Multi-Edit", opts.Anim) + return pendingTool(sty, "Multi-Edit", opts.Anim, opts.Compact) } var params tools.MultiEditParams @@ -311,7 +311,7 @@ type DownloadToolRenderContext struct{} func (d *DownloadToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Download", opts.Anim) + return pendingTool(sty, "Download", opts.Anim, opts.Compact) } var params tools.DownloadParams diff --git a/internal/ui/chat/generic.go b/internal/ui/chat/generic.go index 6b0ac433028daf7a06c57f85c7799250e9652f6f..ae4c99758cf7ea9d311019a9e46676c0f565620e 100644 --- a/internal/ui/chat/generic.go +++ b/internal/ui/chat/generic.go @@ -35,7 +35,7 @@ func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opt name := genericPrettyName(opts.ToolCall.Name) if opts.IsPending() { - return pendingTool(sty, name, opts.Anim) + return pendingTool(sty, name, opts.Anim, opts.Compact) } var params map[string]any diff --git a/internal/ui/chat/lsp_restart.go b/internal/ui/chat/lsp_restart.go index a75dce932ed5fc2da1757e4a139a93d0d7d3fab4..cd06674c335b43c25c714e79c0a9f4f7be589c2b 100644 --- a/internal/ui/chat/lsp_restart.go +++ b/internal/ui/chat/lsp_restart.go @@ -32,7 +32,7 @@ type LSPRestartToolRenderContext struct{} func (r *LSPRestartToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Restart LSP", opts.Anim) + return pendingTool(sty, "Restart LSP", opts.Anim, opts.Compact) } var params tools.LSPRestartParams diff --git a/internal/ui/chat/mcp.go b/internal/ui/chat/mcp.go index c4d124e7381a9ddaa39f56750367d3f2cf4d207f..33d72d6007f5d159d9c1983f09fb25b5e8586388 100644 --- a/internal/ui/chat/mcp.go +++ b/internal/ui/chat/mcp.go @@ -46,7 +46,7 @@ func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *T name := fmt.Sprintf("%s %s %s", mcpName, sty.Tool.MCPArrow.String(), toolName) if opts.IsPending() { - return pendingTool(sty, name, opts.Anim) + return pendingTool(sty, name, opts.Anim, opts.Compact) } var params map[string]any diff --git a/internal/ui/chat/references.go b/internal/ui/chat/references.go index 2d7efe8df3ed38bf3768d7ae13c433fc05c17418..8ae30f3d070be15cf3855eb98eee80c99a186d82 100644 --- a/internal/ui/chat/references.go +++ b/internal/ui/chat/references.go @@ -33,7 +33,7 @@ type ReferencesToolRenderContext struct{} func (r *ReferencesToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Find References", opts.Anim) + return pendingTool(sty, "Find References", opts.Anim, opts.Compact) } var params tools.ReferencesParams diff --git a/internal/ui/chat/search.go b/internal/ui/chat/search.go index 2342f671fdaed3bfdcf56619864bd3b60987d8a6..d3f8ece9c2dd4e56cb3f0fc11e2bba00f2d85421 100644 --- a/internal/ui/chat/search.go +++ b/internal/ui/chat/search.go @@ -37,7 +37,7 @@ type GlobToolRenderContext struct{} func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Glob", opts.Anim) + return pendingTool(sty, "Glob", opts.Anim, opts.Compact) } var params tools.GlobParams @@ -96,7 +96,7 @@ type GrepToolRenderContext struct{} func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Grep", opts.Anim) + return pendingTool(sty, "Grep", opts.Anim, opts.Compact) } var params tools.GrepParams @@ -161,7 +161,7 @@ type LSToolRenderContext struct{} func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "List", opts.Anim) + return pendingTool(sty, "List", opts.Anim, opts.Compact) } var params tools.LSParams @@ -221,7 +221,7 @@ type SourcegraphToolRenderContext struct{} func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "Sourcegraph", opts.Anim) + return pendingTool(sty, "Sourcegraph", opts.Anim, opts.Compact) } var params tools.SourcegraphParams diff --git a/internal/ui/chat/todos.go b/internal/ui/chat/todos.go index 5678d0e47f4c3a808c13c1dc6209f9194e9f9482..421964c286f0a2ae51d2bf44c8da488b92dfc6ed 100644 --- a/internal/ui/chat/todos.go +++ b/internal/ui/chat/todos.go @@ -41,7 +41,7 @@ type TodosToolRenderContext struct{} func (t *TodosToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string { cappedWidth := cappedMessageWidth(width) if opts.IsPending() { - return pendingTool(sty, "To-Do", opts.Anim) + return pendingTool(sty, "To-Do", opts.Anim, opts.Compact) } var params tools.TodosParams diff --git a/internal/ui/chat/tools.go b/internal/ui/chat/tools.go index be82a6e197b0505d83abe0c8d679b0469ed6b93a..1342cfba2f6cc1c608298e9695578ae351726160 100644 --- a/internal/ui/chat/tools.go +++ b/internal/ui/chat/tools.go @@ -424,9 +424,13 @@ func (t *baseToolMessageItem) HandleKeyEvent(key tea.KeyMsg) (bool, tea.Cmd) { } // pendingTool renders a tool that is still in progress with an animation. -func pendingTool(sty *styles.Styles, name string, anim *anim.Anim) string { +func pendingTool(sty *styles.Styles, name string, anim *anim.Anim, nested bool) string { icon := sty.Tool.IconPending.Render() - toolName := sty.Tool.NameNormal.Render(name) + nameStyle := sty.Tool.NameNormal + if nested { + nameStyle = sty.Tool.NameNested + } + toolName := nameStyle.Render(name) var animView string if anim != nil { diff --git a/internal/ui/styles/styles.go b/internal/ui/styles/styles.go index 0870f62674f8241cd874d61532f18c3bc58dc22f..bbffb2568ba6ef5505674135a9a54b650fa0babf 100644 --- a/internal/ui/styles/styles.go +++ b/internal/ui/styles/styles.go @@ -254,18 +254,18 @@ type Styles struct { // Tool - styles for tool call rendering Tool struct { // Icon styles with tool status - IconPending lipgloss.Style // Pending operation icon - IconSuccess lipgloss.Style // Successful operation icon - IconError lipgloss.Style // Error operation icon - IconCancelled lipgloss.Style // Cancelled operation icon + IconPending lipgloss.Style + IconSuccess lipgloss.Style + IconError lipgloss.Style + IconCancelled lipgloss.Style // Tool name styles - NameNormal lipgloss.Style // Normal tool name - NameNested lipgloss.Style // Nested tool name + NameNormal lipgloss.Style // Top-level tool name + NameNested lipgloss.Style // Nested child tool name (inside Agent/Agentic Fetch) // Parameter list styles - ParamMain lipgloss.Style // Main parameter - ParamKey lipgloss.Style // Parameter keys + ParamMain lipgloss.Style + ParamKey lipgloss.Style // Content rendering styles ContentLine lipgloss.Style // Individual content line with background and width @@ -1119,7 +1119,7 @@ func DefaultStyles() Styles { s.Tool.IconCancelled = s.Muted.SetString(ToolPending) s.Tool.NameNormal = base.Foreground(blue) - s.Tool.NameNested = base.Foreground(fgHalfMuted) + s.Tool.NameNested = base.Foreground(blue) s.Tool.ParamMain = s.Subtle s.Tool.ParamKey = s.Subtle