From 19d3cd06eed1a9cb594a0e31bd18d113d6de5729 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 4 Aug 2025 20:58:03 -0400 Subject: [PATCH 1/3] chore: change LSP/MCP loading icon color + refactor icons + theme prep --- .../tui/components/chat/messages/messages.go | 1 - internal/tui/components/core/core.go | 39 +++++++------------ internal/tui/components/files/files.go | 2 - internal/tui/components/lsp/lsp.go | 12 +++--- internal/tui/components/mcp/mcp.go | 12 +++--- internal/tui/styles/crush.go | 11 +++++- internal/tui/styles/theme.go | 6 +++ 7 files changed, 41 insertions(+), 42 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index 17bb582dcadbea1f314b976bc31a31639f8d9609..907efab4eaf22aac6ce4f5ff2ec5afbb244aed2b 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -278,7 +278,6 @@ func (m *messageCmp) renderThinkingContent() string { opts := core.StatusOpts{ Title: "Thought for", Description: duration.String(), - NoIcon: true, } return t.S().Base.PaddingLeft(1).Render(core.Status(opts, m.textWidth()-1)) } else if finishReason != nil && finishReason.Reason == message.FinishReasonCanceled { diff --git a/internal/tui/components/core/core.go b/internal/tui/components/core/core.go index 9f6657de33d5ed824b7d5bc7086342e8f3ec7d48..18de56b17f08e4513bde34fe9fef7aaf4e08c09f 100644 --- a/internal/tui/components/core/core.go +++ b/internal/tui/components/core/core.go @@ -82,41 +82,30 @@ func Title(title string, width int) string { } type StatusOpts struct { - Icon string - IconColor color.Color - NoIcon bool // If true, no icon will be displayed + Icon string // if empty no icon will be shown Title string TitleColor color.Color Description string DescriptionColor color.Color - ExtraContent string // Additional content to append after the description + ExtraContent string // additional content to append after the description } -func Status(ops StatusOpts, width int) string { +func Status(opts StatusOpts, width int) string { t := styles.CurrentTheme() - icon := "●" - iconColor := t.Success - if ops.Icon != "" { - icon = ops.Icon - } else if ops.NoIcon { - icon = "" - } - if ops.IconColor != nil { - iconColor = ops.IconColor - } - title := ops.Title + icon := opts.Icon + title := opts.Title titleColor := t.FgMuted - if ops.TitleColor != nil { - titleColor = ops.TitleColor + if opts.TitleColor != nil { + titleColor = opts.TitleColor } - description := ops.Description + description := opts.Description descriptionColor := t.FgSubtle - if ops.DescriptionColor != nil { - descriptionColor = ops.DescriptionColor + if opts.DescriptionColor != nil { + descriptionColor = opts.DescriptionColor } title = t.S().Base.Foreground(titleColor).Render(title) if description != "" { - extraContentWidth := lipgloss.Width(ops.ExtraContent) + extraContentWidth := lipgloss.Width(opts.ExtraContent) if extraContentWidth > 0 { extraContentWidth += 1 } @@ -126,11 +115,11 @@ func Status(ops StatusOpts, width int) string { content := []string{} if icon != "" { - content = append(content, t.S().Base.Foreground(iconColor).Render(icon)) + content = append(content, icon) } content = append(content, title, description) - if ops.ExtraContent != "" { - content = append(content, ops.ExtraContent) + if opts.ExtraContent != "" { + content = append(content, opts.ExtraContent) } return strings.Join(content, " ") diff --git a/internal/tui/components/files/files.go b/internal/tui/components/files/files.go index 234a75fd4e06431018eab1fbf37e90e562da4083..8272bd53900acf4dd032f86b8f9d2a0bd3b52ccd 100644 --- a/internal/tui/components/files/files.go +++ b/internal/tui/components/files/files.go @@ -98,8 +98,6 @@ func RenderFileList(fileSlice []SessionFile, opts RenderOptions) []string { fileList = append(fileList, core.Status( core.StatusOpts{ - IconColor: t.FgMuted, - NoIcon: true, Title: filePath, ExtraContent: extraContent, }, diff --git a/internal/tui/components/lsp/lsp.go b/internal/tui/components/lsp/lsp.go index 10d9f42198a6996e966d01305131e734fa54a614..893e6470d9f99992fe9d195e5500db8a3891c763 100644 --- a/internal/tui/components/lsp/lsp.go +++ b/internal/tui/components/lsp/lsp.go @@ -57,22 +57,22 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri } // Determine icon color and description based on state - iconColor := t.FgMuted + // iconColor := t.FgMuted + icon := t.ItemOfflineIcon description := l.LSP.Command if l.LSP.Disabled { - iconColor = t.FgMuted description = t.S().Subtle.Render("disabled") } else if state, exists := lspStates[l.Name]; exists { switch state.State { case lsp.StateStarting: - iconColor = t.Yellow + icon = t.ItemBusyIcon description = t.S().Subtle.Render("starting...") case lsp.StateReady: - iconColor = t.Success + icon = t.ItemOnlineIcon description = l.LSP.Command case lsp.StateError: - iconColor = t.Red + icon = t.ItemErrorIcon if state.Error != nil { description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error())) } else { @@ -119,7 +119,7 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri lspList = append(lspList, core.Status( core.StatusOpts{ - IconColor: iconColor, + Icon: icon.String(), Title: l.Name, Description: description, ExtraContent: extraContent, diff --git a/internal/tui/components/mcp/mcp.go b/internal/tui/components/mcp/mcp.go index 93f2dcb230721ab95c3ea2f4937647ff7ccf5bda..2376011ae1f18f44962d59f142652a52bfc47c3d 100644 --- a/internal/tui/components/mcp/mcp.go +++ b/internal/tui/components/mcp/mcp.go @@ -54,25 +54,24 @@ func RenderMCPList(opts RenderOptions) []string { } // Determine icon and color based on state - iconColor := t.FgMuted + icon := t.ItemOfflineIcon description := l.MCP.Command extraContent := "" if state, exists := mcpStates[l.Name]; exists { switch state.State { case agent.MCPStateDisabled: - iconColor = t.FgMuted description = t.S().Subtle.Render("disabled") case agent.MCPStateStarting: - iconColor = t.Yellow + icon = t.ItemBusyIcon description = t.S().Subtle.Render("starting...") case agent.MCPStateConnected: - iconColor = t.Success + icon = t.ItemOnlineIcon if state.ToolCount > 0 { extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount)) } case agent.MCPStateError: - iconColor = t.Red + icon = t.ItemErrorIcon if state.Error != nil { description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error())) } else { @@ -80,14 +79,13 @@ func RenderMCPList(opts RenderOptions) []string { } } } else if l.MCP.Disabled { - iconColor = t.FgMuted description = t.S().Subtle.Render("disabled") } mcpList = append(mcpList, core.Status( core.StatusOpts{ - IconColor: iconColor, + Icon: icon.String(), Title: l.Name, Description: description, ExtraContent: extraContent, diff --git a/internal/tui/styles/crush.go b/internal/tui/styles/crush.go index 2c54d5e41c91521b9418cdcdd4bcbc5dc7231eee..f27632784ad64ed3228ee548c7c8fe84b58bc9ec 100644 --- a/internal/tui/styles/crush.go +++ b/internal/tui/styles/crush.go @@ -1,11 +1,12 @@ package styles import ( + "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/x/exp/charmtone" ) func NewCrushTheme() *Theme { - return &Theme{ + t := &Theme{ Name: "crush", IsDark: true, @@ -54,4 +55,12 @@ func NewCrushTheme() *Theme { RedLight: charmtone.Salmon, Cherry: charmtone.Cherry, } + + // LSP and MCP status. + t.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("●") + t.ItemBusyIcon = t.ItemOfflineIcon.Foreground(charmtone.Citron) + t.ItemErrorIcon = t.ItemOfflineIcon.Foreground(charmtone.Coral) + t.ItemOnlineIcon = t.ItemOfflineIcon.Foreground(charmtone.Guac) + + return t } diff --git a/internal/tui/styles/theme.go b/internal/tui/styles/theme.go index 1d6967684c6ccb5c8f9db2dd23300600b2b5af15..e917cb2b6ffc1ff864012366e0711b66ccf1be83 100644 --- a/internal/tui/styles/theme.go +++ b/internal/tui/styles/theme.go @@ -74,6 +74,12 @@ type Theme struct { RedLight color.Color Cherry color.Color + // LSP and MCP status indicators. + ItemOfflineIcon lipgloss.Style + ItemBusyIcon lipgloss.Style + ItemErrorIcon lipgloss.Style + ItemOnlineIcon lipgloss.Style + styles *Styles } From f5f7a10b2650201606866a1cb220b5540a25cbf8 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 4 Aug 2025 21:35:37 -0400 Subject: [PATCH 2/3] chore(tests): update golden files for status --- CRUSH.md | 2 ++ internal/tui/components/core/status_test.go | 3 --- .../core/testdata/TestStatus/AllFieldsWithExtraContent.golden | 2 +- .../tui/components/core/testdata/TestStatus/Default.golden | 2 +- .../core/testdata/TestStatus/EmptyDescription.golden | 2 +- .../components/core/testdata/TestStatus/LongDescription.golden | 2 +- .../tui/components/core/testdata/TestStatus/NarrowWidth.golden | 2 +- .../components/core/testdata/TestStatus/VeryNarrowWidth.golden | 2 +- .../tui/components/core/testdata/TestStatus/WithColors.golden | 2 +- .../components/core/testdata/TestStatus/WithCustomIcon.golden | 2 +- .../core/testdata/TestStatus/WithExtraContent.golden | 2 +- .../core/testdata/TestStatusTruncation/Width20.golden | 2 +- .../core/testdata/TestStatusTruncation/Width30.golden | 2 +- .../core/testdata/TestStatusTruncation/Width40.golden | 2 +- .../core/testdata/TestStatusTruncation/Width50.golden | 2 +- .../core/testdata/TestStatusTruncation/Width60.golden | 2 +- 16 files changed, 16 insertions(+), 17 deletions(-) diff --git a/CRUSH.md b/CRUSH.md index 69132723e99e20b6e1d56ee79f7c777e79ce06d8..5a3104b6685fb5e246c77d416d4a12adeda91734 100644 --- a/CRUSH.md +++ b/CRUSH.md @@ -4,6 +4,8 @@ - **Build**: `go build .` or `go run .` - **Test**: `task test` or `go test ./...` (run single test: `go test ./internal/llm/prompt -run TestGetContextFromPaths`) +- **Update Golden Files**: `go test ./... -update` (regenerates .golden files when test output changes) + - Update specific package: `go test ./internal/tui/components/core -update` (in this case, we're updating "core") - **Lint**: `task lint-fix` - **Format**: `task fmt` (gofumpt -w .) - **Dev**: `task dev` (runs with profiling enabled) diff --git a/internal/tui/components/core/status_test.go b/internal/tui/components/core/status_test.go index 0b24dc321d8863c8bad2bc4fc38e38020230a7f5..c82fc5b2a3e735e1eafd385b74ae5a4877032bd9 100644 --- a/internal/tui/components/core/status_test.go +++ b/internal/tui/components/core/status_test.go @@ -37,7 +37,6 @@ func TestStatus(t *testing.T) { { name: "NoIcon", opts: core.StatusOpts{ - NoIcon: true, Title: "Info", Description: "This status has no icon", }, @@ -47,7 +46,6 @@ func TestStatus(t *testing.T) { name: "WithColors", opts: core.StatusOpts{ Icon: "⚠", - IconColor: color.RGBA{255, 165, 0, 255}, // Orange Title: "Warning", TitleColor: color.RGBA{255, 255, 0, 255}, // Yellow Description: "This is a warning message", @@ -102,7 +100,6 @@ func TestStatus(t *testing.T) { name: "AllFieldsWithExtraContent", opts: core.StatusOpts{ Icon: "🚀", - IconColor: color.RGBA{0, 255, 0, 255}, // Green Title: "Deployment", TitleColor: color.RGBA{0, 0, 255, 255}, // Blue Description: "Deploying to production environment", diff --git a/internal/tui/components/core/testdata/TestStatus/AllFieldsWithExtraContent.golden b/internal/tui/components/core/testdata/TestStatus/AllFieldsWithExtraContent.golden index e6f7fb0be25997b79c3d39bddedee2f2d7b11b72..89477e3738e6547ea26734e8a49df5d281d70c57 100644 --- a/internal/tui/components/core/testdata/TestStatus/AllFieldsWithExtraContent.golden +++ b/internal/tui/components/core/testdata/TestStatus/AllFieldsWithExtraContent.golden @@ -1 +1 @@ -🚀 Deployment Deploying to production environment v1.2.3 \ No newline at end of file +🚀 Deployment Deploying to production environment v1.2.3 \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/Default.golden b/internal/tui/components/core/testdata/TestStatus/Default.golden index a0066dedd418dafe54757dc3159b3a6b11d106ca..2151efd10b7aeb6500b55a0e61fbf5d4a6ef1638 100644 --- a/internal/tui/components/core/testdata/TestStatus/Default.golden +++ b/internal/tui/components/core/testdata/TestStatus/Default.golden @@ -1 +1 @@ -● Status Everything is working fine \ No newline at end of file +Status Everything is working fine \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/EmptyDescription.golden b/internal/tui/components/core/testdata/TestStatus/EmptyDescription.golden index f9c4d759b50d02598791a6462f8e9cab2e0a0b6d..5b396377658610dd0fbc0746fd960f2faaf76f49 100644 --- a/internal/tui/components/core/testdata/TestStatus/EmptyDescription.golden +++ b/internal/tui/components/core/testdata/TestStatus/EmptyDescription.golden @@ -1 +1 @@ -● Title Only  \ No newline at end of file +● Title Only  \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/LongDescription.golden b/internal/tui/components/core/testdata/TestStatus/LongDescription.golden index f008176649f7941b9f1ee6276f6e65fea36d4c52..13fc6c3335871aaa5513d370d078f8e350571abe 100644 --- a/internal/tui/components/core/testdata/TestStatus/LongDescription.golden +++ b/internal/tui/components/core/testdata/TestStatus/LongDescription.golden @@ -1 +1 @@ -● Processing This is a very long description that should be… \ No newline at end of file +Processing This is a very long description that should be … \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/NarrowWidth.golden b/internal/tui/components/core/testdata/TestStatus/NarrowWidth.golden index 5b9efd7dbb74dcf56344567c1918b470f90eace7..0c5b8e93c35e302038e019d58682716b1b220ef7 100644 --- a/internal/tui/components/core/testdata/TestStatus/NarrowWidth.golden +++ b/internal/tui/components/core/testdata/TestStatus/NarrowWidth.golden @@ -1 +1 @@ -● Status Short message \ No newline at end of file +● Status Short message \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/VeryNarrowWidth.golden b/internal/tui/components/core/testdata/TestStatus/VeryNarrowWidth.golden index 26628ae3bc28acd49e8f30e60f65912fe563c0e6..9bb3917977486b8f862c74db4f43951a9c44a450 100644 --- a/internal/tui/components/core/testdata/TestStatus/VeryNarrowWidth.golden +++ b/internal/tui/components/core/testdata/TestStatus/VeryNarrowWidth.golden @@ -1 +1 @@ -● Test This will be… \ No newline at end of file +● Test This will be… \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/WithColors.golden b/internal/tui/components/core/testdata/TestStatus/WithColors.golden index ff0e3a6ec4847c4786387d26c9752f664d78cd51..97eeb24db9a9803f4d8877296d38a9d878b50fed 100644 --- a/internal/tui/components/core/testdata/TestStatus/WithColors.golden +++ b/internal/tui/components/core/testdata/TestStatus/WithColors.golden @@ -1 +1 @@ -⚠ Warning This is a warning message \ No newline at end of file +⚠ Warning This is a warning message \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/WithCustomIcon.golden b/internal/tui/components/core/testdata/TestStatus/WithCustomIcon.golden index 6857f0d29dd58886308e15ea50c7e0822834f2ee..00cf9455b72e0fd3b8fc94e48b09053bb3fde60a 100644 --- a/internal/tui/components/core/testdata/TestStatus/WithCustomIcon.golden +++ b/internal/tui/components/core/testdata/TestStatus/WithCustomIcon.golden @@ -1 +1 @@ -✓ Success Operation completed successfully \ No newline at end of file +✓ Success Operation completed successfully \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatus/WithExtraContent.golden b/internal/tui/components/core/testdata/TestStatus/WithExtraContent.golden index 47b02e81b5ec4fc0d0c5dd54545d9634811b1636..292d1fa97f0400a7c411eff5a658af537fc8b69e 100644 --- a/internal/tui/components/core/testdata/TestStatus/WithExtraContent.golden +++ b/internal/tui/components/core/testdata/TestStatus/WithExtraContent.golden @@ -1 +1 @@ -● Build Building project [2/5] \ No newline at end of file +Build Building project [2/5] \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatusTruncation/Width20.golden b/internal/tui/components/core/testdata/TestStatusTruncation/Width20.golden index 4437cba67aa068c2597e558000b9b3005478b378..0df96289f5aa373f174aa9f833478d5c559abe53 100644 --- a/internal/tui/components/core/testdata/TestStatusTruncation/Width20.golden +++ b/internal/tui/components/core/testdata/TestStatusTruncation/Width20.golden @@ -1 +1 @@ -● Very Long Title  [extra] \ No newline at end of file +● Very Long Title  [extra] \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatusTruncation/Width30.golden b/internal/tui/components/core/testdata/TestStatusTruncation/Width30.golden index b09cc983c97382e4d92719bb5606d22f9dc2301f..56915d1966ab547740910398b101fd70371bb264 100644 --- a/internal/tui/components/core/testdata/TestStatusTruncation/Width30.golden +++ b/internal/tui/components/core/testdata/TestStatusTruncation/Width30.golden @@ -1 +1 @@ -● Very Long Title Thi… [extra] \ No newline at end of file +● Very Long Title Thi… [extra] \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatusTruncation/Width40.golden b/internal/tui/components/core/testdata/TestStatusTruncation/Width40.golden index 5113ce07a0b07d1cfddbcbae0c14046546308f2a..6b249b2f865698ebc73ed7787daad30ddf417945 100644 --- a/internal/tui/components/core/testdata/TestStatusTruncation/Width40.golden +++ b/internal/tui/components/core/testdata/TestStatusTruncation/Width40.golden @@ -1 +1 @@ -● Very Long Title This is an ex… [extra] \ No newline at end of file +● Very Long Title This is an ex… [extra] \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatusTruncation/Width50.golden b/internal/tui/components/core/testdata/TestStatusTruncation/Width50.golden index 25bd8723b0cd461311364ecaac92a2b93f00ecd9..1862198d631f525c3080f7f811ade5a5738658b1 100644 --- a/internal/tui/components/core/testdata/TestStatusTruncation/Width50.golden +++ b/internal/tui/components/core/testdata/TestStatusTruncation/Width50.golden @@ -1 +1 @@ -● Very Long Title This is an extremely lo… [extra] \ No newline at end of file +● Very Long Title This is an extremely lo… [extra] \ No newline at end of file diff --git a/internal/tui/components/core/testdata/TestStatusTruncation/Width60.golden b/internal/tui/components/core/testdata/TestStatusTruncation/Width60.golden index 0152f1c2d0ac9e011d744e0cd02283c18edc8d03..0f29e46d2660d1bf2584c730c50972e962c4dd32 100644 --- a/internal/tui/components/core/testdata/TestStatusTruncation/Width60.golden +++ b/internal/tui/components/core/testdata/TestStatusTruncation/Width60.golden @@ -1 +1 @@ -● Very Long Title This is an extremely long descrip… [extra] \ No newline at end of file +● Very Long Title This is an extremely long descrip… [extra] \ No newline at end of file From 40e16d1913ae73647b3c87c2d5d01a45bf6523e2 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 4 Aug 2025 21:36:37 -0400 Subject: [PATCH 3/3] chore: remove commented code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/tui/components/lsp/lsp.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/tui/components/lsp/lsp.go b/internal/tui/components/lsp/lsp.go index 893e6470d9f99992fe9d195e5500db8a3891c763..f2546c945e436ca196064dda5b50d35583d5b2ab 100644 --- a/internal/tui/components/lsp/lsp.go +++ b/internal/tui/components/lsp/lsp.go @@ -57,7 +57,6 @@ func RenderLSPList(lspClients map[string]*lsp.Client, opts RenderOptions) []stri } // Determine icon color and description based on state - // iconColor := t.FgMuted icon := t.ItemOfflineIcon description := l.LSP.Command