diff --git a/crates/tui/src/palette.rs b/crates/tui/src/palette.rs index b3a5a367..7de9d730 100644 --- a/crates/tui/src/palette.rs +++ b/crates/tui/src/palette.rs @@ -825,6 +825,39 @@ pub const DRACULA_UI_THEME: UiTheme = UiTheme { tool_failed: Color::Rgb(0xff, 0x55, 0x55), // red }; +/// "Terminal" theme: lets the host terminal's color scheme show through +/// instead of painting any RGB surface. Backgrounds use `Color::Reset` +/// (the terminal's own default bg) and most text uses `Color::Reset` +/// (terminal's own default fg). Accents are ANSI named colors so they +/// also inherit the user's terminal palette (Solarized, Nord, custom +/// schemes, etc.) rather than DeepSeek brand RGB. +pub const TERMINAL_UI_THEME: UiTheme = UiTheme { + name: "terminal", + // Mode is reported as Dark to avoid the dark→light cell remap kicking + // in; the terminal-theme cell remap already normalizes everything to + // `Color::Reset`, and we never want a second pass overwriting that. + mode: PaletteMode::Dark, + surface_bg: Color::Reset, + panel_bg: Color::Reset, + elevated_bg: Color::Reset, + composer_bg: Color::Reset, + selection_bg: Color::Reset, + header_bg: Color::Reset, + footer_bg: Color::Reset, + mode_agent: Color::Blue, + mode_yolo: Color::Red, + mode_plan: Color::Yellow, + status_ready: Color::Reset, + status_working: Color::Cyan, + status_warning: Color::Yellow, + text_dim: Color::Reset, + text_hint: Color::Reset, + text_muted: Color::Reset, + text_body: Color::Reset, + text_soft: Color::Reset, + border: Color::Reset, +}; + pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme { name: "gruvbox-dark", mode: PaletteMode::Dark, @@ -874,6 +907,7 @@ pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ThemeId { System, + Terminal, Whale, WhaleLight, Grayscale, @@ -891,6 +925,7 @@ impl ThemeId { pub fn from_name(value: &str) -> Option { match normalize_theme_name(value)? { "system" => Some(Self::System), + "terminal" => Some(Self::Terminal), "dark" => Some(Self::Whale), "light" => Some(Self::WhaleLight), "grayscale" => Some(Self::Grayscale), @@ -908,6 +943,7 @@ impl ThemeId { pub const fn name(self) -> &'static str { match self { Self::System => "system", + Self::Terminal => "terminal", Self::Whale => "dark", Self::WhaleLight => "light", Self::Grayscale => "grayscale", @@ -923,6 +959,7 @@ impl ThemeId { pub const fn display_name(self) -> &'static str { match self { Self::System => "System", + Self::Terminal => "Terminal", Self::Whale => "Whale (Dark)", Self::WhaleLight => "Whale Light", Self::Grayscale => "Grayscale", @@ -938,6 +975,9 @@ impl ThemeId { pub const fn tagline(self) -> &'static str { match self { Self::System => "Follow terminal background (COLORFGBG / macOS appearance)", + Self::Terminal => { + "Inherit terminal colors fully (transparent surfaces, ANSI accents)" + } Self::Whale => "Whale dark — deep navy & gold", Self::WhaleLight => "DeepSeek light, paper-ish", Self::Grayscale => "Color-minimal high contrast", @@ -956,6 +996,7 @@ impl ThemeId { pub fn ui_theme(self) -> UiTheme { match self { Self::System => UiTheme::detect(), + Self::Terminal => TERMINAL_UI_THEME, Self::Whale => UI_THEME, Self::WhaleLight => LIGHT_UI_THEME, Self::Grayscale => GRAYSCALE_UI_THEME, @@ -970,6 +1011,7 @@ impl ThemeId { /// Themes shown in the `/theme` picker, in display order. pub const SELECTABLE_THEMES: &[ThemeId] = &[ ThemeId::System, + ThemeId::Terminal, ThemeId::Whale, ThemeId::WhaleLight, ThemeId::Grayscale, @@ -1012,6 +1054,7 @@ impl UiTheme { pub fn normalize_theme_name(value: &str) -> Option<&'static str> { match value.trim().to_ascii_lowercase().as_str() { "" | "auto" | "system" | "default" => Some("system"), + "terminal" | "term" | "transparent" | "follow-terminal" | "inherit" => Some("terminal"), "dark" | "whale" | "whale-dark" => Some("dark"), "light" | "whale-light" => Some("light"), "grayscale" | "greyscale" | "gray" | "grey" | "mono" | "monochrome" | "black-white" @@ -1189,7 +1232,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::Terminal + | ThemeId::CatppuccinMocha + | ThemeId::TokyoNight + | ThemeId::Dracula + | ThemeId::GruvboxDark ) } diff --git a/crates/tui/src/tui/theme_picker.rs b/crates/tui/src/tui/theme_picker.rs index 85da1d41..fca7254a 100644 --- a/crates/tui/src/tui/theme_picker.rs +++ b/crates/tui/src/tui/theme_picker.rs @@ -317,7 +317,7 @@ mod tests { let mut v = ThemePickerView::new("system".to_string()); let action = v.handle_key(key(KeyCode::Down)); assert!(matches!(action, ViewAction::Emit(_))); - assert_eq!(selected_name(&action), Some(ThemeId::Whale.name())); + assert_eq!(selected_name(&action), Some(ThemeId::Terminal.name())); } #[test] @@ -337,6 +337,7 @@ mod tests { v.handle_key(key(KeyCode::Down)); v.handle_key(key(KeyCode::Down)); v.handle_key(key(KeyCode::Down)); + v.handle_key(key(KeyCode::Down)); v.handle_key(key(KeyCode::Down)); // -> CatppuccinMocha let action = v.handle_key(key(KeyCode::Enter)); match action { @@ -376,8 +377,8 @@ mod tests { #[test] fn digit_jumps_to_row() { let mut v = ThemePickerView::new("system".to_string()); - let action = v.handle_key(key(KeyCode::Char('5'))); - // Row 5 (1-indexed) -> index 4 -> CatppuccinMocha + let action = v.handle_key(key(KeyCode::Char('6'))); + // Row 6 (1-indexed) -> index 5 -> CatppuccinMocha assert_eq!( selected_name(&action), Some(ThemeId::CatppuccinMocha.name())