From 5cae8a7c833370f4a513b8b761012ec51e3e3e46 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 24 Jun 2026 13:55:09 -0700 Subject: [PATCH 1/5] fix: respect theme color in comp TUI screens --- cmd/berry/berry.go | 8 ++++---- cmd/berry/berry_test.go | 4 ++-- cmd/card/cardlist.go | 8 ++++---- cmd/card/imageviewer.go | 4 ++-- cmd/card/setslist.go | 2 +- cmd/comp/champions/tab_components.go | 6 +++--- cmd/comp/shell/dashboard.go | 3 ++- cmd/comp/shell/frame.go | 6 +++--- cmd/comp/shell/frame_test.go | 3 --- cmd/comp/shell/picker.go | 2 +- cmd/comp/shell/styles.go | 19 ++++++------------- cmd/speed/speed.go | 20 ++++++++++---------- cmd/types/types.go | 4 ++-- cmd/types/types_test.go | 4 ++-- flags/pokemon_flagset.go | 2 +- setup/setup.go | 4 ++-- styling/styling.go | 14 ++++---------- styling/styling_test.go | 4 ++-- 18 files changed, 51 insertions(+), 66 deletions(-) diff --git a/cmd/berry/berry.go b/cmd/berry/berry.go index 830175ec..67936c5e 100644 --- a/cmd/berry/berry.go +++ b/cmd/berry/berry.go @@ -119,7 +119,7 @@ func (m model) View() tea.View { Width(52). Height(29). Border(lipgloss.RoundedBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). Padding(1). Render(selectedBerry) @@ -158,11 +158,11 @@ func tableGeneration() error { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. Foreground(lipgloss.Color("#000")). - Background(styling.YellowColor) + Background(styling.ThemeColor) t.SetStyles(s) m := model{table: t} @@ -185,7 +185,7 @@ func berryContainers(name string) string { boxStyle := lipgloss.NewStyle(). Padding(1, 2). BorderStyle(lipgloss.ThickBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). Width(34) // Render both without height constraints to measure natural heights. diff --git a/cmd/berry/berry_test.go b/cmd/berry/berry_test.go index 31b828e3..c5060664 100644 --- a/cmd/berry/berry_test.go +++ b/cmd/berry/berry_test.go @@ -192,11 +192,11 @@ func createTestModel() model { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. Foreground(lipgloss.Color("#000")). - Background(styling.YellowColor) + Background(styling.ThemeColor) t.SetStyles(s) return model{table: t} diff --git a/cmd/card/cardlist.go b/cmd/card/cardlist.go index 58a7aef2..243d30a7 100644 --- a/cmd/card/cardlist.go +++ b/cmd/card/cardlist.go @@ -48,7 +48,7 @@ type cardDataMsg struct { } var ( - activeTableSelectedBg color.Color = styling.YellowColor + activeTableSelectedBg color.Color = styling.ThemeColor inactiveTableSelectedBg color.Color = lipgloss.Color("#808080") ) @@ -56,7 +56,7 @@ func cardTableStyles(selectedBg color.Color) table.Styles { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. Foreground(lipgloss.Color("#000")). @@ -297,7 +297,7 @@ func (m cardsModel) View() tea.View { Width(42). Height(29). Border(lipgloss.RoundedBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). Padding(1). Render(selectedCard) @@ -327,7 +327,7 @@ type cardData struct { func CardsList(setID string) (cardsModel, error) { s := spinner.New() s.Spinner = spinner.Dot - s.Style = styling.Yellow + s.Style = styling.Theme return cardsModel{ SetID: setID, diff --git a/cmd/card/imageviewer.go b/cmd/card/imageviewer.go index 1de2277e..f4817dc0 100644 --- a/cmd/card/imageviewer.go +++ b/cmd/card/imageviewer.go @@ -83,7 +83,7 @@ func (m imageModel) View() tea.View { content = lipgloss.NewStyle(). Padding(2). BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). Render(styling.Red.Render(m.Error.Error())) } else { content = m.ImageData @@ -97,7 +97,7 @@ func (m imageModel) View() tea.View { func ImageRenderer(cardName string, imageURL string) imageModel { s := spinner.New() s.Spinner = spinner.Dot - s.Style = styling.Yellow + s.Style = styling.Theme return imageModel{ CardName: cardName, diff --git a/cmd/card/setslist.go b/cmd/card/setslist.go index 6a2d60fc..1550bd93 100644 --- a/cmd/card/setslist.go +++ b/cmd/card/setslist.go @@ -174,7 +174,7 @@ type setData struct { func SetsList(seriesID string) (setsModel, error) { s := spinner.New() s.Spinner = spinner.Dot - s.Style = styling.Yellow + s.Style = styling.Theme return setsModel{ SeriesName: seriesID, diff --git a/cmd/comp/champions/tab_components.go b/cmd/comp/champions/tab_components.go index 8d0e695e..1434f873 100644 --- a/cmd/comp/champions/tab_components.go +++ b/cmd/comp/champions/tab_components.go @@ -68,7 +68,7 @@ func renderPokemonDetail(row compInfoRow, width int) string { teammates := renderStatColumn("Common Teammates", row.CommonTeammates, colWidth) var b strings.Builder - b.WriteString(styling.Yellow.Render(row.Pokemon)) + b.WriteString(styling.Theme.Render(row.Pokemon)) b.WriteString("\n\n") b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, moves, " ", items)) b.WriteString("\n\n") @@ -231,7 +231,7 @@ func renderTeamDetail(team teamRow, width int) string { var b strings.Builder title := fmt.Sprintf("%s (%s)", team.Player, team.Record) - b.WriteString(styling.Yellow.Render("Selected Team")) + b.WriteString(styling.Theme.Render("Selected Team")) b.WriteString("\n") b.WriteString(styling.StyleBold.Render(title)) b.WriteString("\n") @@ -392,7 +392,7 @@ func selectedSpeedTier(speedTable table.Model, rows []speedTierRow) speedTierRow func renderSpeedDetail(row speedTierRow) string { var b strings.Builder - b.WriteString(styling.Yellow.Render("Selected Pokémon")) + b.WriteString(styling.Theme.Render("Selected Pokémon")) b.WriteString("\n") b.WriteString(styling.StyleBold.Render(row.Pokemon)) diff --git a/cmd/comp/shell/dashboard.go b/cmd/comp/shell/dashboard.go index df7c926d..6c89b9c2 100644 --- a/cmd/comp/shell/dashboard.go +++ b/cmd/comp/shell/dashboard.go @@ -6,6 +6,7 @@ import ( "charm.land/bubbles/v2/table" tea "charm.land/bubbletea/v2" "github.com/digitalghost-dev/poke-cli/cmd/utils" + "github.com/digitalghost-dev/poke-cli/styling" ) type dashboardModel struct { @@ -139,7 +140,7 @@ func (m dashboardModel) renderTab(contentWidth int) string { } switch m.activeTab { case 0: - return m.decoded.Overview(contentWidth, m.styles.HighlightColor) + return m.decoded.Overview(contentWidth, styling.ThemeColor) case 1: return m.table.View() case 2: diff --git a/cmd/comp/shell/frame.go b/cmd/comp/shell/frame.go index 9059673b..bf872965 100644 --- a/cmd/comp/shell/frame.go +++ b/cmd/comp/shell/frame.go @@ -45,7 +45,7 @@ func (s *Styles) Render(tabs []string, activeTab, width int, renderContent func( fillWidth := windowWidth - lipgloss.Width(row) if fillWidth > 0 { - fill := lipgloss.NewStyle().Foreground(s.HighlightColor). + fill := lipgloss.NewStyle().Foreground(styling.ThemeColor). Render(strings.Repeat("─", fillWidth-1) + "┐") row = row + fill } @@ -65,11 +65,11 @@ func TableStyles() table.Styles { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderBottom(true). Bold(true) s.Selected = s.Selected. Foreground(lipgloss.Color("#000")). - Background(styling.YellowColor) + Background(styling.ThemeColor) return s } diff --git a/cmd/comp/shell/frame_test.go b/cmd/comp/shell/frame_test.go index ffe935e9..01089052 100644 --- a/cmd/comp/shell/frame_test.go +++ b/cmd/comp/shell/frame_test.go @@ -10,9 +10,6 @@ func TestNewStyles(t *testing.T) { if s == nil { t.Fatal("expected non-nil styles") } - if s.HighlightColor == nil { - t.Error("expected highlight color to be set") - } } func TestRender_ContainsTabsContentAndMenu(t *testing.T) { diff --git a/cmd/comp/shell/picker.go b/cmd/comp/shell/picker.go index 64b089e0..f6a762f4 100644 --- a/cmd/comp/shell/picker.go +++ b/cmd/comp/shell/picker.go @@ -52,7 +52,7 @@ func fetchTournaments(listURL string, conn ConnFunc) tea.Cmd { func newPicker(spec Spec, conn ConnFunc) pickerModel { s := spinner.New() s.Spinner = spinner.Dot - s.Style = styling.Yellow + s.Style = styling.Theme return pickerModel{ conn: conn, diff --git a/cmd/comp/shell/styles.go b/cmd/comp/shell/styles.go index 0b605e6f..24e33dd4 100644 --- a/cmd/comp/shell/styles.go +++ b/cmd/comp/shell/styles.go @@ -1,18 +1,15 @@ package shell import ( - "image/color" - "charm.land/lipgloss/v2" "github.com/digitalghost-dev/poke-cli/styling" ) type Styles struct { - Doc lipgloss.Style - InactiveTab lipgloss.Style - ActiveTab lipgloss.Style - Window lipgloss.Style - HighlightColor color.Color + Doc lipgloss.Style + InactiveTab lipgloss.Style + ActiveTab lipgloss.Style + Window lipgloss.Style } func tabBorderWithBottom(left, middle, right string) lipgloss.Border { @@ -26,24 +23,20 @@ func tabBorderWithBottom(left, middle, right string) lipgloss.Border { func NewStyles() *Styles { inactiveTabBorder := tabBorderWithBottom("┴", "─", "┴") activeTabBorder := tabBorderWithBottom("┘", " ", "└") - isDark := styling.HasDarkBackground() - ld := lipgloss.LightDark(isDark) - highlightColor := ld(lipgloss.Color("#874BFD"), lipgloss.Color("#7D56F4")) s := new(Styles) s.Doc = lipgloss.NewStyle(). Padding(1, 2, 1, 2) s.InactiveTab = lipgloss.NewStyle(). Border(inactiveTabBorder, true). - BorderForeground(highlightColor). + BorderForeground(styling.ThemeColor). Padding(0, 1) s.ActiveTab = s.InactiveTab. Border(activeTabBorder, true) s.Window = lipgloss.NewStyle(). - BorderForeground(highlightColor). + BorderForeground(styling.ThemeColor). Padding(2, 0). Border(lipgloss.NormalBorder()). UnsetBorderTop() - s.HighlightColor = highlightColor return s } diff --git a/cmd/speed/speed.go b/cmd/speed/speed.go index 75805dc7..8cc7b956 100644 --- a/cmd/speed/speed.go +++ b/cmd/speed/speed.go @@ -300,18 +300,18 @@ func formula() (string, error) { finalSpeedStr := fmt.Sprintf("%.0f", finalSpeedFloor) header := fmt.Sprintf("%s at level %s with selected options has a current speed of %s.", - styling.Yellow.Render(chosenPokemon), - styling.Yellow.Render(pokemon.Level), - styling.Yellow.Render(finalSpeedStr), + styling.Theme.Render(chosenPokemon), + styling.Theme.Render(pokemon.Level), + styling.Theme.Render(finalSpeedStr), ) body := fmt.Sprintf("EVs: %s\nIVs: %s\nModifiers: %s\nNature: %s\nAbility: %s\nSpeed Stage: %s\nBase Speed: %s", - styling.Yellow.Render(pokemon.SpeedEV), - styling.Yellow.Render(pokemon.SpeedIV), - styling.Yellow.Render(xstrings.EnglishJoin(pokemon.Modifier, true)), - styling.Yellow.Render(pokemon.Nature), - styling.Yellow.Render(pokemon.Ability), - styling.Yellow.Render(pokemon.SpeedStage), - styling.Yellow.Render(speedStr), + styling.Theme.Render(pokemon.SpeedEV), + styling.Theme.Render(pokemon.SpeedIV), + styling.Theme.Render(xstrings.EnglishJoin(pokemon.Modifier, true)), + styling.Theme.Render(pokemon.Nature), + styling.Theme.Render(pokemon.Ability), + styling.Theme.Render(pokemon.SpeedStage), + styling.Theme.Render(speedStr), ) isDark := styling.HasDarkBackground() diff --git a/cmd/types/types.go b/cmd/types/types.go index 4854cd38..d1ca1dcd 100644 --- a/cmd/types/types.go +++ b/cmd/types/types.go @@ -120,11 +120,11 @@ func createTypeSelectionTable() model { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. Foreground(lipgloss.Color("#000")). - Background(styling.YellowColor) + Background(styling.ThemeColor) tbl.SetStyles(s) return model{table: tbl} diff --git a/cmd/types/types_test.go b/cmd/types/types_test.go index 463beb0d..f7259a78 100644 --- a/cmd/types/types_test.go +++ b/cmd/types/types_test.go @@ -75,11 +75,11 @@ func createTestModel() model { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. Foreground(lipgloss.Color("#000")). - Background(styling.YellowColor) + Background(styling.ThemeColor) t.SetStyles(s) return model{table: t} diff --git a/flags/pokemon_flagset.go b/flags/pokemon_flagset.go index 5898846f..edcb051f 100644 --- a/flags/pokemon_flagset.go +++ b/flags/pokemon_flagset.go @@ -44,7 +44,7 @@ func header(header string) string { HeaderBold := lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(styling.YellowColor). + BorderForeground(styling.ThemeColor). BorderTop(true). Bold(true). Render(header) diff --git a/setup/setup.go b/setup/setup.go index 09b1bd4e..ef5913b5 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -135,7 +135,7 @@ func (m model) View() tea.View { line := lipgloss.NewStyle().Render(label + " " + value) if m.cursor == focused { cursor = "> " - line = styling.Yellow.Render(label + " " + value) + line = styling.Theme.Render(label + " " + value) } return cursor + line } @@ -155,7 +155,7 @@ func (m model) View() tea.View { status := "poke-cache: " + cache panel := lipgloss.NewStyle().Border(lipgloss.RoundedBorder()). - BorderForeground(styling.YellowColor).Padding(1, 2) + BorderForeground(styling.ThemeColor).Padding(1, 2) body := lipgloss.JoinVertical(lipgloss.Left, panel.Render(settings), panel.Render(status)) diff --git a/styling/styling.go b/styling/styling.go index e69a63dd..eae38374 100644 --- a/styling/styling.go +++ b/styling/styling.go @@ -23,17 +23,13 @@ var palettes = map[string]string{ var accent string -var ( - YellowColor color.Color - YellowAdaptive color.Color - YellowAdaptive2 color.Color -) +var ThemeColor color.Color var ( Green = lipgloss.NewStyle().Foreground(lipgloss.Color("#38B000")) Red = lipgloss.NewStyle().Foreground(lipgloss.Color("#D00000")) Gray = lipgloss.Color("#777777") - Yellow lipgloss.Style + Theme lipgloss.Style ColoredBullet lipgloss.Style CheckboxStyle lipgloss.Style KeyMenu = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")) @@ -87,10 +83,8 @@ func ApplyTheme(name string) { accent = hex c := lipgloss.Color(hex) - YellowColor = c - YellowAdaptive = c - YellowAdaptive2 = c - Yellow = lipgloss.NewStyle().Foreground(c) + ThemeColor = c + Theme = lipgloss.NewStyle().Foreground(c) ColoredBullet = lipgloss.NewStyle().SetString("•").Foreground(c) CheckboxStyle = lipgloss.NewStyle().Foreground(c) HelpBorder = lipgloss.NewStyle(). diff --git a/styling/styling_test.go b/styling/styling_test.go index 52bc3ad3..683a29c6 100644 --- a/styling/styling_test.go +++ b/styling/styling_test.go @@ -119,12 +119,12 @@ func TestApplyThemeSwapsAndFallsBack(t *testing.T) { t.Cleanup(func() { ApplyTheme("yellow") }) ApplyTheme("red") - redColor := YellowColor + redColor := ThemeColor assert.Equal(t, "#f00000", accent) ApplyTheme("blue") assert.Equal(t, "#3B4CCA", accent) - assert.NotEqual(t, redColor, YellowColor) + assert.NotEqual(t, redColor, ThemeColor) ApplyTheme("bogus") assert.Equal(t, "#E1AD01", accent) From d39e1edcc76341342cc588eb3aa4fd576f09d362 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 24 Jun 2026 13:55:34 -0700 Subject: [PATCH 2/5] docs: fix spacing --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6cdb3ed3..3c18a80b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ rust-tests-label rust-version + `poke-cli` is a hybrid of a classic CLI and a modern TUI tool for viewing VG and TCG data about Pokémon! View the [documentation](https://docs.poke-cli.com) on the data infrastructure in [data_platform/](https://github.com/digitalghost-dev/poke-cli/tree/main/data_platform) if you're interested. From adf15957b9d779e4c2e274b7b3c2b36586dc4463 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 24 Jun 2026 14:27:42 -0700 Subject: [PATCH 3/5] fix: make `comp` picker back/quit navigation work --- cmd/comp/shell/picker.go | 12 ++++++++++-- cmd/comp/shell/run.go | 16 ++++++---------- cmd/comp/shell/run_test.go | 23 ++++++++++++++++++----- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/cmd/comp/shell/picker.go b/cmd/comp/shell/picker.go index f6a762f4..95108164 100644 --- a/cmd/comp/shell/picker.go +++ b/cmd/comp/shell/picker.go @@ -22,6 +22,7 @@ type pickerModel struct { tournaments []TournamentRef selected *TournamentRef error error + goBack bool list list.Model loading bool spinner spinner.Model @@ -76,6 +77,9 @@ func (m pickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "esc": m.quitting = true return m, tea.Quit + case "b": + m.goBack = true + return m, tea.Quit case "w": return m, utils.Open("https://web.poke-cli.com/") case "enter": @@ -112,12 +116,16 @@ func (m pickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { l.Styles.PaginationStyle = styling.PaginationStyle l.Styles.HelpStyle = styling.HelpStyle + backBinding := key.NewBinding( + key.WithKeys("b"), + key.WithHelp("b", "back"), + ) webBinding := key.NewBinding( key.WithKeys("w"), key.WithHelp("w", "web"), ) - l.AdditionalShortHelpKeys = func() []key.Binding { return []key.Binding{webBinding} } - l.AdditionalFullHelpKeys = func() []key.Binding { return []key.Binding{webBinding} } + l.AdditionalShortHelpKeys = func() []key.Binding { return []key.Binding{backBinding, webBinding} } + l.AdditionalFullHelpKeys = func() []key.Binding { return []key.Binding{backBinding, webBinding} } m.list = l m.loading = false diff --git a/cmd/comp/shell/run.go b/cmd/comp/shell/run.go index 84e64bc4..7479191b 100644 --- a/cmd/comp/shell/run.go +++ b/cmd/comp/shell/run.go @@ -59,10 +59,7 @@ func Run(spec Spec, conn ConnFunc) (back bool, err error) { return result, nil } - if err := loop(spec, conn, runPicker, runDashboard); err != nil { - return false, err - } - return true, nil + return loop(spec, conn, runPicker, runDashboard) } func loop( @@ -70,25 +67,24 @@ func loop( conn ConnFunc, runPicker func(pickerModel) (pickerModel, error), runDashboard func(dashboardModel) (dashboardModel, error), -) error { +) (back bool, err error) { for { result, err := runPicker(newPicker(spec, conn)) if err != nil { - return fmt.Errorf("error running tournament selection program: %w", err) + return false, fmt.Errorf("error running tournament selection program: %w", err) } if result.selected == nil { - break + return result.goBack, nil } dash, err := runDashboard(newDashboard(spec, conn, result.selected.Location)) if err != nil { - return fmt.Errorf("error running dashboard program: %w", err) + return false, fmt.Errorf("error running dashboard program: %w", err) } if !dash.goBack { - break + return false, nil } } - return nil } func FormatInt(n int) string { diff --git a/cmd/comp/shell/run_test.go b/cmd/comp/shell/run_test.go index dbca0957..59b01ab9 100644 --- a/cmd/comp/shell/run_test.go +++ b/cmd/comp/shell/run_test.go @@ -66,11 +66,19 @@ func TestLoop_NoTournamentSelected(t *testing.T) { dashCalled = true return dashboardModel{}, nil } - err := loop(testSpec(), noopConn, runPicker, runDashboard) + back, err := loop(testSpec(), noopConn, runPicker, runDashboard) require.NoError(t, err) + require.False(t, back, "esc/quit from the picker should not return to the selection menu") require.False(t, dashCalled, "dashboard should not launch when no tournament is selected") } +func TestLoop_PickerGoBack_ReturnsBack(t *testing.T) { + runPicker := func(_ pickerModel) (pickerModel, error) { return pickerModel{selected: nil, goBack: true}, nil } + back, err := loop(testSpec(), noopConn, runPicker, nil) + require.NoError(t, err) + require.True(t, back, "pressing b in the picker should return to the selection menu") +} + func TestLoop_TournamentSelected_DashboardExits(t *testing.T) { td := TournamentRef{Location: "London"} runPicker := func(_ pickerModel) (pickerModel, error) { return pickerModel{selected: &td}, nil } @@ -78,7 +86,9 @@ func TestLoop_TournamentSelected_DashboardExits(t *testing.T) { assert.Equal(t, "London", m.tournament) return dashboardModel{goBack: false}, nil } - assert.NoError(t, loop(testSpec(), noopConn, runPicker, runDashboard)) + back, err := loop(testSpec(), noopConn, runPicker, runDashboard) + assert.NoError(t, err) + assert.False(t, back, "quitting the dashboard should not return to the selection menu") } func TestLoop_GoBack_LoopsToPicker(t *testing.T) { @@ -92,13 +102,15 @@ func TestLoop_GoBack_LoopsToPicker(t *testing.T) { return pickerModel{selected: nil}, nil } runDashboard := func(_ dashboardModel) (dashboardModel, error) { return dashboardModel{goBack: true}, nil } - require.NoError(t, loop(testSpec(), noopConn, runPicker, runDashboard)) + back, err := loop(testSpec(), noopConn, runPicker, runDashboard) + require.NoError(t, err) + require.False(t, back) require.Equal(t, 2, calls, "expected the picker to run twice") } func TestLoop_PickerError(t *testing.T) { runPicker := func(_ pickerModel) (pickerModel, error) { return pickerModel{}, errors.New("boom") } - err := loop(testSpec(), noopConn, runPicker, nil) + _, err := loop(testSpec(), noopConn, runPicker, nil) assert.ErrorContains(t, err, "tournament selection") } @@ -108,5 +120,6 @@ func TestLoop_DashboardError(t *testing.T) { runDashboard := func(_ dashboardModel) (dashboardModel, error) { return dashboardModel{}, errors.New("boom") } - assert.ErrorContains(t, loop(testSpec(), noopConn, runPicker, runDashboard), "dashboard") + _, err := loop(testSpec(), noopConn, runPicker, runDashboard) + assert.ErrorContains(t, err, "dashboard") } From b9f611e1f4b3c2da700a52046b61e27df8457b3c Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 24 Jun 2026 14:33:00 -0700 Subject: [PATCH 4/5] test: fixing linter error --- cmd/comp/shell/run_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/comp/shell/run_test.go b/cmd/comp/shell/run_test.go index 59b01ab9..6450a232 100644 --- a/cmd/comp/shell/run_test.go +++ b/cmd/comp/shell/run_test.go @@ -87,7 +87,7 @@ func TestLoop_TournamentSelected_DashboardExits(t *testing.T) { return dashboardModel{goBack: false}, nil } back, err := loop(testSpec(), noopConn, runPicker, runDashboard) - assert.NoError(t, err) + require.NoError(t, err) assert.False(t, back, "quitting the dashboard should not return to the selection menu") } From 128267866ceb4dd92f1819d05627ba9e433dcab5 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 24 Jun 2026 15:16:57 -0700 Subject: [PATCH 5/5] fix: pick readable selected-row text per theme color --- cmd/card/cardlist.go | 2 +- cmd/comp/shell/frame.go | 2 +- cmd/types/types.go | 2 +- styling/styling.go | 12 ++++++++++++ styling/styling_test.go | 25 ++++++++++++++++++++++++- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/cmd/card/cardlist.go b/cmd/card/cardlist.go index 243d30a7..b7a10df0 100644 --- a/cmd/card/cardlist.go +++ b/cmd/card/cardlist.go @@ -59,7 +59,7 @@ func cardTableStyles(selectedBg color.Color) table.Styles { BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. - Foreground(lipgloss.Color("#000")). + Foreground(styling.ContrastText(selectedBg)). Background(selectedBg) return s } diff --git a/cmd/comp/shell/frame.go b/cmd/comp/shell/frame.go index bf872965..68b125b1 100644 --- a/cmd/comp/shell/frame.go +++ b/cmd/comp/shell/frame.go @@ -69,7 +69,7 @@ func TableStyles() table.Styles { BorderBottom(true). Bold(true) s.Selected = s.Selected. - Foreground(lipgloss.Color("#000")). + Foreground(styling.ContrastText(styling.ThemeColor)). Background(styling.ThemeColor) return s } diff --git a/cmd/types/types.go b/cmd/types/types.go index d1ca1dcd..6beef04f 100644 --- a/cmd/types/types.go +++ b/cmd/types/types.go @@ -123,7 +123,7 @@ func createTypeSelectionTable() model { BorderForeground(styling.ThemeColor). BorderBottom(true) s.Selected = s.Selected. - Foreground(lipgloss.Color("#000")). + Foreground(styling.ContrastText(styling.ThemeColor)). Background(styling.ThemeColor) tbl.SetStyles(s) diff --git a/styling/styling.go b/styling/styling.go index eae38374..908eb5ea 100644 --- a/styling/styling.go +++ b/styling/styling.go @@ -183,3 +183,15 @@ func (col Color) Hex() string { return fmt.Sprintf("#%02x%02x%02x", uint8(col.R*255.0+0.5), uint8(col.G*255.0+0.5), uint8(col.B*255.0+0.5)) } + +func ContrastText(bg color.Color) color.Color { + c, ok := MakeColor(bg) + if !ok { + return lipgloss.Color("#000") + } + luma := 0.299*c.R + 0.587*c.G + 0.114*c.B + if luma > 0.5 { + return lipgloss.Color("#000") + } + return lipgloss.Color("#FFF") +} diff --git a/styling/styling_test.go b/styling/styling_test.go index 683a29c6..f94239be 100644 --- a/styling/styling_test.go +++ b/styling/styling_test.go @@ -2,9 +2,11 @@ package styling import ( "fmt" - "github.com/stretchr/testify/assert" "image/color" "testing" + + "charm.land/lipgloss/v2" + "github.com/stretchr/testify/assert" ) func TestGetTypeColor(t *testing.T) { @@ -129,3 +131,24 @@ func TestApplyThemeSwapsAndFallsBack(t *testing.T) { ApplyTheme("bogus") assert.Equal(t, "#E1AD01", accent) } + +func TestContrastText(t *testing.T) { + black := lipgloss.Color("#000") + white := lipgloss.Color("#FFF") + tests := []struct { + name string + bg string + want color.Color + }{ + {"yellow theme", palettes["yellow"], black}, + {"red theme", palettes["red"], white}, + {"blue theme", palettes["blue"], white}, + {"pure white", "#FFFFFF", black}, + {"pure black", "#000000", white}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, ContrastText(lipgloss.Color(tt.bg))) + }) + } +}