diff --git a/crates/tui/src/deepseek_theme.rs b/crates/tui/src/deepseek_theme.rs index 88ff5e0b1..614f1a626 100644 --- a/crates/tui/src/deepseek_theme.rs +++ b/crates/tui/src/deepseek_theme.rs @@ -114,6 +114,32 @@ impl Theme { } } + /// Solarized Light theme tokens — warm ivory tones, high contrast. + #[must_use] + pub const fn solarized_light() -> Self { + Self { + variant: Variant::Light, + section_borders: Borders::ALL, + section_border_type: BorderType::Plain, + section_border_color: palette::SOLARIZED_BORDER, + section_bg: palette::SOLARIZED_PANEL, + section_title_color: palette::SOLARIZED_BLUE, + section_padding: Padding::horizontal(1), + tool_title_color: palette::SOLARIZED_TEXT_SOFT, + tool_value_color: palette::SOLARIZED_TEXT_MUTED, + tool_label_color: palette::SOLARIZED_TEXT_DIM, + tool_running_accent: palette::SOLARIZED_BLUE, + tool_success_accent: palette::SOLARIZED_CYAN, + tool_failed_accent: palette::SOLARIZED_RED, + plan_progress_color: palette::SOLARIZED_BLUE, + plan_summary_color: palette::SOLARIZED_TEXT_MUTED, + plan_explanation_color: palette::SOLARIZED_TEXT_DIM, + plan_pending_color: palette::SOLARIZED_TEXT_MUTED, + plan_in_progress_color: palette::SOLARIZED_ORANGE, + plan_completed_color: palette::SOLARIZED_BLUE, + } + } + /// Neutral black/white tokens for users who want minimal brand color. #[must_use] pub const fn grayscale() -> Self { @@ -146,6 +172,7 @@ impl Theme { PaletteMode::Dark => Self::dark(), PaletteMode::Light => Self::light(), PaletteMode::Grayscale => Self::grayscale(), + PaletteMode::SolarizedLight => Self::solarized_light(), } } diff --git a/crates/tui/src/palette.rs b/crates/tui/src/palette.rs index b3a5a367f..73b2d644c 100644 --- a/crates/tui/src/palette.rs +++ b/crates/tui/src/palette.rs @@ -30,6 +30,27 @@ pub const WHALE_BORDER_RGB: (u8, u8, u8) = (52, 88, 145); // #345891 pub const WHALE_REASONING_TEXT_RGB: (u8, u8, u8) = (224, 153, 72); // #E09948 pub const WHALE_REASONING_SURFACE_RGB: (u8, u8, u8) = (42, 34, 24); // #2A2218 pub const WHALE_REASONING_TINT_RGB: (u8, u8, u8) = (24, 36, 52); // #182434 + +// Solarized Light palette RGB tuples +pub const SOLARIZED_BASE03_RGB: (u8, u8, u8) = (0x00, 0x2B, 0x36); +pub const SOLARIZED_BASE02_RGB: (u8, u8, u8) = (0x07, 0x36, 0x42); +pub const SOLARIZED_BASE01_RGB: (u8, u8, u8) = (0x58, 0x6E, 0x75); +pub const SOLARIZED_BASE00_RGB: (u8, u8, u8) = (0x65, 0x7B, 0x83); +pub const SOLARIZED_BASE0_RGB: (u8, u8, u8) = (0x83, 0x94, 0x96); +pub const SOLARIZED_BASE1_RGB: (u8, u8, u8) = (0x93, 0xA1, 0xA1); +#[allow(dead_code)] +pub const SOLARIZED_BASE2_RGB: (u8, u8, u8) = (0xEE, 0xE8, 0xD5); +pub const SOLARIZED_BASE3_RGB: (u8, u8, u8) = (0xFD, 0xF6, 0xE3); +pub const SOLARIZED_YELLOW_RGB: (u8, u8, u8) = (0xB5, 0x89, 0x00); +pub const SOLARIZED_ORANGE_RGB: (u8, u8, u8) = (0xCB, 0x4B, 0x16); +pub const SOLARIZED_RED_RGB: (u8, u8, u8) = (0xDC, 0x32, 0x2F); +pub const SOLARIZED_BLUE_RGB: (u8, u8, u8) = (0x26, 0x8B, 0xD2); +pub const SOLARIZED_CYAN_RGB: (u8, u8, u8) = (0x2A, 0xA1, 0x98); +pub const SOLARIZED_GREEN_RGB: (u8, u8, u8) = (0x85, 0x99, 0x00); +pub const SOLARIZED_PANEL_RGB: (u8, u8, u8) = (0xF0, 0xED, 0xE7); +pub const SOLARIZED_ELEVATED_RGB: (u8, u8, u8) = (0xE4, 0xDF, 0xCF); +pub const SOLARIZED_SELECT_RGB: (u8, u8, u8) = (0xD6, 0xD2, 0xC9); + pub const WHALE_DIFF_ADDED_RGB: (u8, u8, u8) = (87, 199, 133); // #57C785 #[allow(dead_code)] pub const WHALE_DIFF_DELETED_RGB: (u8, u8, u8) = (255, 92, 122); // #FF5C7A Rose Red @@ -66,6 +87,100 @@ pub const LIGHT_TEXT_BODY_RGB: (u8, u8, u8) = (15, 23, 42); // #0F172A pub const LIGHT_TEXT_MUTED_RGB: (u8, u8, u8) = (51, 65, 85); // #334155 pub const LIGHT_TEXT_HINT_RGB: (u8, u8, u8) = (100, 116, 139); // #64748B pub const LIGHT_TEXT_SOFT_RGB: (u8, u8, u8) = (30, 41, 59); // #1E293B + +// Solarized Light palette colors +pub const SOLARIZED_TEXT_DIM: Color = Color::Rgb( + SOLARIZED_BASE00_RGB.0, + SOLARIZED_BASE00_RGB.1, + SOLARIZED_BASE00_RGB.2, +); +pub const SOLARIZED_TEXT_HINT: Color = Color::Rgb( + SOLARIZED_BASE0_RGB.0, + SOLARIZED_BASE0_RGB.1, + SOLARIZED_BASE0_RGB.2, +); +pub const SOLARIZED_TEXT_MUTED: Color = Color::Rgb( + SOLARIZED_BASE01_RGB.0, + SOLARIZED_BASE01_RGB.1, + SOLARIZED_BASE01_RGB.2, +); +pub const SOLARIZED_TEXT_BODY: Color = Color::Rgb( + SOLARIZED_BASE03_RGB.0, + SOLARIZED_BASE03_RGB.1, + SOLARIZED_BASE03_RGB.2, +); +pub const SOLARIZED_TEXT_SOFT: Color = Color::Rgb( + SOLARIZED_BASE02_RGB.0, + SOLARIZED_BASE02_RGB.1, + SOLARIZED_BASE02_RGB.2, +); +pub const SOLARIZED_BORDER: Color = Color::Rgb( + SOLARIZED_BASE1_RGB.0, + SOLARIZED_BASE1_RGB.1, + SOLARIZED_BASE1_RGB.2, +); +pub const SOLARIZED_BLUE: Color = Color::Rgb( + SOLARIZED_BLUE_RGB.0, + SOLARIZED_BLUE_RGB.1, + SOLARIZED_BLUE_RGB.2, +); +pub const SOLARIZED_CYAN: Color = Color::Rgb( + SOLARIZED_CYAN_RGB.0, + SOLARIZED_CYAN_RGB.1, + SOLARIZED_CYAN_RGB.2, +); +pub const SOLARIZED_RED: Color = Color::Rgb( + SOLARIZED_RED_RGB.0, + SOLARIZED_RED_RGB.1, + SOLARIZED_RED_RGB.2, +); +pub const SOLARIZED_ORANGE: Color = Color::Rgb( + SOLARIZED_ORANGE_RGB.0, + SOLARIZED_ORANGE_RGB.1, + SOLARIZED_ORANGE_RGB.2, +); +pub const SOLARIZED_YELLOW: Color = Color::Rgb( + SOLARIZED_YELLOW_RGB.0, + SOLARIZED_YELLOW_RGB.1, + SOLARIZED_YELLOW_RGB.2, +); +pub const SOLARIZED_GREEN: Color = Color::Rgb( + SOLARIZED_GREEN_RGB.0, + SOLARIZED_GREEN_RGB.1, + SOLARIZED_GREEN_RGB.2, +); +pub const SOLARIZED_SURFACE: Color = Color::Rgb( + SOLARIZED_BASE3_RGB.0, + SOLARIZED_BASE3_RGB.1, + SOLARIZED_BASE3_RGB.2, +); +pub const SOLARIZED_PANEL: Color = Color::Rgb( + SOLARIZED_PANEL_RGB.0, + SOLARIZED_PANEL_RGB.1, + SOLARIZED_PANEL_RGB.2, +); +pub const SOLARIZED_ELEVATED: Color = Color::Rgb( + SOLARIZED_ELEVATED_RGB.0, + SOLARIZED_ELEVATED_RGB.1, + SOLARIZED_ELEVATED_RGB.2, +); +pub const SOLARIZED_SELECT_BG: Color = Color::Rgb( + SOLARIZED_SELECT_RGB.0, + SOLARIZED_SELECT_RGB.1, + SOLARIZED_SELECT_RGB.2, +); +pub const SOLARIZED_DIFF_ADDED_BG: Color = Color::Rgb(0xEA, 0xF2, 0xE0); +pub const SOLARIZED_ERROR_SURFACE: Color = Color::Rgb(0xFD, 0xEE, 0xEB); +/// Same tone as the error surface; kept as a distinct alias for diff context. +pub const SOLARIZED_DIFF_DELETED_BG: Color = SOLARIZED_ERROR_SURFACE; +pub const SOLARIZED_ERROR_TEXT: Color = Color::Rgb(0x8B, 0x00, 0x00); +pub const SOLARIZED_ERROR_HOVER: Color = Color::Rgb(0xE0, 0x55, 0x52); +pub const SOLARIZED_COMPOSER: Color = Color::Rgb( + SOLARIZED_PANEL_RGB.0, + SOLARIZED_PANEL_RGB.1, + SOLARIZED_PANEL_RGB.2, +); + pub const LIGHT_BORDER_RGB: (u8, u8, u8) = (139, 161, 184); // #8BA1B8 pub const LIGHT_SELECTION_RGB: (u8, u8, u8) = (207, 224, 247); // #CFE0F7 pub const GRAYSCALE_SURFACE_RGB: (u8, u8, u8) = (10, 10, 10); // #0A0A0A @@ -413,6 +528,7 @@ pub enum PaletteMode { Dark, Light, Grayscale, + SolarizedLight, } impl PaletteMode { @@ -653,6 +769,49 @@ pub const LIGHT_UI_THEME: UiTheme = UiTheme { tool_failed: Color::Rgb(200, 40, 60), // red }; +pub const SOLARIZED_LIGHT_UI_THEME: UiTheme = UiTheme { + name: "solarized-light", + mode: PaletteMode::SolarizedLight, + surface_bg: SOLARIZED_SURFACE, + panel_bg: SOLARIZED_PANEL, + elevated_bg: SOLARIZED_ELEVATED, + composer_bg: SOLARIZED_COMPOSER, + selection_bg: SOLARIZED_SELECT_BG, + header_bg: SOLARIZED_SURFACE, + footer_bg: SOLARIZED_SURFACE, + text_dim: SOLARIZED_TEXT_DIM, + text_hint: SOLARIZED_TEXT_HINT, + text_muted: SOLARIZED_TEXT_MUTED, + text_body: SOLARIZED_TEXT_BODY, + text_soft: SOLARIZED_TEXT_SOFT, + border: SOLARIZED_BORDER, + accent_primary: SOLARIZED_BLUE, + accent_secondary: SOLARIZED_CYAN, + accent_action: SOLARIZED_ORANGE, + error_fg: SOLARIZED_RED, + error_hover: SOLARIZED_ERROR_HOVER, + error_surface: SOLARIZED_ERROR_SURFACE, + error_border: SOLARIZED_RED, + error_text: SOLARIZED_ERROR_TEXT, + warning: SOLARIZED_YELLOW, + success: SOLARIZED_GREEN, + info: SOLARIZED_BLUE, + mode_agent: SOLARIZED_BLUE, + mode_yolo: SOLARIZED_RED, + mode_plan: SOLARIZED_ORANGE, + mode_goal: SOLARIZED_GREEN, + status_ready: SOLARIZED_CYAN, + status_working: SOLARIZED_BLUE, + status_warning: SOLARIZED_YELLOW, + diff_added_fg: SOLARIZED_GREEN, + diff_deleted_fg: SOLARIZED_RED, + diff_added_bg: SOLARIZED_DIFF_ADDED_BG, + diff_deleted_bg: SOLARIZED_DIFF_DELETED_BG, + tool_running: SOLARIZED_BLUE, + tool_success: SOLARIZED_CYAN, + tool_failed: SOLARIZED_RED, +}; + pub const GRAYSCALE_UI_THEME: UiTheme = UiTheme { name: "grayscale", mode: PaletteMode::Grayscale, @@ -881,6 +1040,7 @@ pub enum ThemeId { TokyoNight, Dracula, GruvboxDark, + SolarizedLight, } impl ThemeId { @@ -898,6 +1058,7 @@ impl ThemeId { "tokyo-night" => Some(Self::TokyoNight), "dracula" => Some(Self::Dracula), "gruvbox-dark" => Some(Self::GruvboxDark), + "solarized-light" => Some(Self::SolarizedLight), _ => None, } } @@ -915,6 +1076,7 @@ impl ThemeId { Self::TokyoNight => "tokyo-night", Self::Dracula => "dracula", Self::GruvboxDark => "gruvbox-dark", + Self::SolarizedLight => "solarized-light", } } @@ -930,6 +1092,7 @@ impl ThemeId { Self::TokyoNight => "Tokyo Night", Self::Dracula => "Dracula", Self::GruvboxDark => "Gruvbox Dark", + Self::SolarizedLight => "Solarized Light", } } @@ -945,6 +1108,9 @@ impl ThemeId { Self::TokyoNight => "Deep blue/violet night palette", Self::Dracula => "Classic high-contrast purple", Self::GruvboxDark => "Vintage warm earth tones", + Self::SolarizedLight => { + "Solarized light — Light, calming palette on warm ivory — easy on the eyes" + } } } @@ -963,6 +1129,7 @@ impl ThemeId { Self::TokyoNight => TOKYO_NIGHT_UI_THEME, Self::Dracula => DRACULA_UI_THEME, Self::GruvboxDark => GRUVBOX_DARK_UI_THEME, + Self::SolarizedLight => SOLARIZED_LIGHT_UI_THEME, } } } @@ -977,6 +1144,7 @@ pub const SELECTABLE_THEMES: &[ThemeId] = &[ ThemeId::TokyoNight, ThemeId::Dracula, ThemeId::GruvboxDark, + ThemeId::SolarizedLight, ]; impl UiTheme { @@ -986,6 +1154,7 @@ impl UiTheme { PaletteMode::Dark => UI_THEME, PaletteMode::Light => LIGHT_UI_THEME, PaletteMode::Grayscale => GRAYSCALE_UI_THEME, + PaletteMode::SolarizedLight => SOLARIZED_LIGHT_UI_THEME, } } @@ -1020,6 +1189,7 @@ pub fn normalize_theme_name(value: &str) -> Option<&'static str> { "tokyo-night" | "tokyonight" | "tokyo" => Some("tokyo-night"), "dracula" => Some("dracula"), "gruvbox-dark" | "gruvbox" => Some("gruvbox-dark"), + "solarized-light" | "solarized" => Some("solarized-light"), _ => None, } } @@ -1030,6 +1200,7 @@ pub fn theme_label_for_mode(mode: PaletteMode) -> &'static str { PaletteMode::Dark => "dark", PaletteMode::Light => "light", PaletteMode::Grayscale => "grayscale", + PaletteMode::SolarizedLight => "solarized-light", } } @@ -1074,6 +1245,7 @@ pub fn adapt_fg_for_palette_mode(color: Color, _bg: Color, mode: PaletteMode) -> PaletteMode::Dark => color, PaletteMode::Light => adapt_fg_for_light_palette(color), PaletteMode::Grayscale => adapt_fg_for_grayscale_palette(color), + PaletteMode::SolarizedLight => adapt_fg_for_solarized_light_palette(color), } } @@ -1083,6 +1255,7 @@ pub fn adapt_bg_for_palette_mode(color: Color, mode: PaletteMode) -> Color { PaletteMode::Dark => color, PaletteMode::Light => adapt_bg_for_light_palette(color), PaletteMode::Grayscale => adapt_bg_for_grayscale_palette(color), + PaletteMode::SolarizedLight => adapt_bg_for_solarized_light_palette(color), } } @@ -1143,6 +1316,59 @@ fn adapt_bg_for_light_palette(color: Color) -> Color { } } +fn adapt_fg_for_solarized_light_palette(color: Color) -> Color { + if color == TEXT_BODY || color == SELECTION_TEXT || color == Color::White { + SOLARIZED_TEXT_BODY + } else if color == TEXT_SECONDARY || color == TEXT_MUTED { + SOLARIZED_TEXT_MUTED + } else if color == TEXT_HINT || color == TEXT_DIM { + SOLARIZED_TEXT_HINT + } else if color == TEXT_SOFT || color == TEXT_TOOL_OUTPUT { + SOLARIZED_TEXT_SOFT + } else if color == BORDER_COLOR { + SOLARIZED_BORDER + } else if color == TEXT_ACCENT || color == DEEPSEEK_SKY || color == ACCENT_TOOL_LIVE { + SOLARIZED_BLUE + } else if color == TEXT_REASONING || color == ACCENT_REASONING_LIVE { + SOLARIZED_ORANGE + } else if color == ACCENT_TOOL_ISSUE { + SOLARIZED_RED + } else if color == DIFF_ADDED || color == USER_BODY { + SOLARIZED_GREEN + } else { + color + } +} + +fn adapt_bg_for_solarized_light_palette(color: Color) -> Color { + if color == DEEPSEEK_INK || color == BACKGROUND_DARK { + SOLARIZED_SURFACE + } else if color == DEEPSEEK_SLATE + || color == COMPOSER_BG + || color == SURFACE_PANEL + || color == SURFACE_TOOL + { + SOLARIZED_PANEL + } else if color == SURFACE_ELEVATED || color == SURFACE_TOOL_ACTIVE { + SOLARIZED_ELEVATED + } else if color == SURFACE_REASONING + || color == SURFACE_REASONING_TINT + || color == SURFACE_REASONING_ACTIVE + { + SOLARIZED_PANEL + } else if color == SURFACE_SUCCESS || color == DIFF_ADDED_BG { + SOLARIZED_DIFF_ADDED_BG + } else if color == SURFACE_ERROR { + SOLARIZED_ERROR_SURFACE + } else if color == DIFF_DELETED_BG { + SOLARIZED_DIFF_DELETED_BG + } else if color == SELECTION_BG { + SOLARIZED_SELECT_BG + } else { + color + } +} + // === Community-theme remap === // // The vast majority of render sites in this crate reach for `palette::TEXT_*`, @@ -1189,7 +1415,11 @@ const fn theme_diff_deleted_bg(ui: &UiTheme) -> Color { pub const fn theme_remap_active(theme: ThemeId) -> bool { matches!( theme, - ThemeId::CatppuccinMocha | ThemeId::TokyoNight | ThemeId::Dracula | ThemeId::GruvboxDark + ThemeId::CatppuccinMocha + | ThemeId::TokyoNight + | ThemeId::Dracula + | ThemeId::GruvboxDark + | ThemeId::SolarizedLight ) } @@ -1680,13 +1910,14 @@ mod tests { DEEPSEEK_SLATE, GRAYSCALE_BORDER, GRAYSCALE_ELEVATED, GRAYSCALE_PANEL, GRAYSCALE_REASONING, GRAYSCALE_SURFACE, GRAYSCALE_TEXT_BODY, GRAYSCALE_TEXT_HINT, GRAYSCALE_TEXT_SOFT, GRAYSCALE_UI_THEME, LIGHT_BORDER, LIGHT_ELEVATED, LIGHT_PANEL, LIGHT_REASONING, - LIGHT_SURFACE, LIGHT_TEXT_BODY, LIGHT_TEXT_HINT, LIGHT_UI_THEME, PaletteMode, - SURFACE_REASONING, SURFACE_REASONING_TINT, TEXT_BODY, TEXT_HINT, TEXT_REASONING, - TEXT_TOOL_OUTPUT, UI_THEME, WHALE_REASONING_TEXT_RGB, WHALE_REASONING_TINT_RGB, - WHALE_TEXT_BODY_RGB, adapt_bg, adapt_bg_for_palette_mode, adapt_color, - adapt_fg_for_palette_mode, blend, luma, nearest_ansi16, normalize_hex_rgb_color, - normalize_theme_name, parse_hex_rgb_color, pulse_brightness, reasoning_surface_tint, - rgb_to_ansi256, theme_label_for_mode, ui_theme_from_settings, + LIGHT_SURFACE, LIGHT_TEXT_BODY, LIGHT_TEXT_BODY_RGB, LIGHT_TEXT_HINT, LIGHT_UI_THEME, + PaletteMode, SOLARIZED_LIGHT_UI_THEME, SOLARIZED_PANEL, SOLARIZED_SURFACE, + SOLARIZED_TEXT_BODY, SOLARIZED_TEXT_HINT, SURFACE_REASONING, SURFACE_REASONING_TINT, + TEXT_BODY, TEXT_HINT, TEXT_REASONING, TEXT_TOOL_OUTPUT, UI_THEME, WHALE_REASONING_TEXT_RGB, + WHALE_REASONING_TINT_RGB, WHALE_TEXT_BODY_RGB, adapt_bg, adapt_bg_for_palette_mode, + adapt_color, adapt_fg_for_palette_mode, blend, luma, nearest_ansi16, + normalize_hex_rgb_color, normalize_theme_name, parse_hex_rgb_color, pulse_brightness, + reasoning_surface_tint, rgb_to_ansi256, theme_label_for_mode, ui_theme_from_settings, }; use ratatui::style::Color; @@ -1765,6 +1996,15 @@ mod tests { assert_eq!(theme.text_body, GRAYSCALE_TEXT_BODY); } + #[test] + fn ui_theme_selects_solarized_light_variant() { + let theme = super::UiTheme::for_mode(PaletteMode::SolarizedLight); + assert_eq!(theme, SOLARIZED_LIGHT_UI_THEME); + assert_eq!(theme.surface_bg, SOLARIZED_SURFACE); + assert_eq!(theme.panel_bg, SOLARIZED_PANEL); + assert_eq!(theme.text_body, SOLARIZED_TEXT_BODY); + } + #[test] fn theme_names_normalize_common_grayscale_aliases() { assert_eq!(normalize_theme_name("system"), Some("system")); @@ -1772,7 +2012,7 @@ mod tests { assert_eq!(normalize_theme_name("whale"), Some("dark")); assert_eq!(normalize_theme_name("black-white"), Some("grayscale")); assert_eq!(normalize_theme_name("mono"), Some("grayscale")); - assert_eq!(normalize_theme_name("solarized"), None); + assert_eq!(normalize_theme_name("solarized"), Some("solarized-light")); assert_eq!(theme_label_for_mode(PaletteMode::Grayscale), "grayscale"); } @@ -1786,6 +2026,19 @@ mod tests { assert_ne!(LIGHT_PANEL, LIGHT_ELEVATED); } + #[test] + fn solarized_light_does_not_mutate_whale_light_text() { + assert_eq!( + LIGHT_TEXT_BODY, + Color::Rgb( + LIGHT_TEXT_BODY_RGB.0, + LIGHT_TEXT_BODY_RGB.1, + LIGHT_TEXT_BODY_RGB.2 + ) + ); + assert_ne!(LIGHT_TEXT_BODY, SOLARIZED_TEXT_BODY); + } + #[test] fn dark_palette_uses_soft_body_text_and_warm_reasoning() { assert_eq!( @@ -1862,6 +2115,26 @@ mod tests { ); } + #[test] + fn solarized_light_palette_maps_dark_surfaces_and_text_to_solarized_roles() { + assert_eq!( + adapt_bg_for_palette_mode(DEEPSEEK_INK, PaletteMode::SolarizedLight), + SOLARIZED_SURFACE + ); + assert_eq!( + adapt_bg_for_palette_mode(DEEPSEEK_SLATE, PaletteMode::SolarizedLight), + SOLARIZED_PANEL + ); + assert_eq!( + adapt_fg_for_palette_mode(Color::White, SOLARIZED_SURFACE, PaletteMode::SolarizedLight), + SOLARIZED_TEXT_BODY + ); + assert_eq!( + adapt_fg_for_palette_mode(TEXT_HINT, SOLARIZED_SURFACE, PaletteMode::SolarizedLight), + SOLARIZED_TEXT_HINT + ); + } + #[test] fn grayscale_palette_maps_brand_hues_to_neutral_roles() { assert_eq!( diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index f4520af82..0b8d9b25f 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -156,7 +156,7 @@ impl TuiPrefs { let theme = self.theme.trim().to_ascii_lowercase(); let Some(theme) = normalize_theme_name(&theme) else { anyhow::bail!( - "Invalid tui.toml theme '{}': expected system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, or gruvbox-dark.", + "Invalid tui.toml theme '{}': expected system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark, or solarized-light.", self.theme ); }; @@ -520,7 +520,7 @@ impl Settings { "theme" => { let Some(id) = crate::palette::ThemeId::from_name(value) else { anyhow::bail!( - "Failed to update setting: invalid theme '{value}'. Expected: system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark." + "Failed to update setting: invalid theme '{value}'. Expected: system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark, solarized-light." ); }; self.theme = id.name().to_string(); @@ -528,7 +528,7 @@ impl Settings { "ui_theme" => { let Some(id) = crate::palette::ThemeId::from_name(value) else { anyhow::bail!( - "Failed to update setting: invalid theme '{value}'. Expected: system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark." + "Failed to update setting: invalid theme '{value}'. Expected: system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark, solarized-light." ); }; self.theme = id.name().to_string(); @@ -780,7 +780,7 @@ impl Settings { ), ( "theme", - "UI theme: system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark", + "UI theme: system, dark, light, grayscale, catppuccin-mocha, tokyo-night, dracula, gruvbox-dark, solarized-light", ), ( "background_color", @@ -1153,8 +1153,13 @@ mod tests { .expect("set community theme alias"); assert_eq!(settings.theme, "tokyo-night"); - let err = settings + settings .set("theme", "solarized") + .expect("set solarized alias"); + assert_eq!(settings.theme, "solarized-light"); + + let err = settings + .set("theme", "nord") .expect_err("unknown theme should fail"); assert!(err.to_string().contains("invalid theme")); } @@ -2052,6 +2057,7 @@ mod tests { "tokyo-night", "dracula", "gruvbox-dark", + "solarized-light", ] { let mut prefs = TuiPrefs { theme: theme.to_string(), @@ -2079,17 +2085,16 @@ mod tests { #[test] fn tui_prefs_validate_rejects_unknown_theme() { let mut prefs = TuiPrefs { - theme: "solarized".to_string(), + theme: "nord".to_string(), ..TuiPrefs::default() }; - let err = prefs - .validate() - .expect_err("solarized is not a valid theme"); + let err = prefs.validate().expect_err("nord is not a valid theme"); assert!(err.to_string().contains("Invalid tui.toml theme")); assert!( err.to_string() .contains("expected system, dark, light, grayscale") ); + assert!(err.to_string().contains("solarized-light")); } #[test] diff --git a/crates/tui/src/tui/theme_picker.rs b/crates/tui/src/tui/theme_picker.rs index 85da1d41a..32daacf8f 100644 --- a/crates/tui/src/tui/theme_picker.rs +++ b/crates/tui/src/tui/theme_picker.rs @@ -325,7 +325,8 @@ mod tests { let mut v = ThemePickerView::new("system".to_string()); let action = v.handle_key(key(KeyCode::Up)); - assert_eq!(selected_name(&action), Some(ThemeId::GruvboxDark.name())); + let last_theme = SELECTABLE_THEMES.last().expect("theme picker has rows"); + assert_eq!(selected_name(&action), Some(last_theme.name())); let action = v.handle_key(key(KeyCode::Down)); assert_eq!(selected_name(&action), Some(ThemeId::System.name()));