Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/tui/src/config_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ pub enum UiThemeValue {
TokyoNight,
Dracula,
GruvboxDark,
Matrix,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
Expand Down Expand Up @@ -743,6 +744,7 @@ impl UiThemeValue {
Self::TokyoNight => "tokyo-night",
Self::Dracula => "dracula",
Self::GruvboxDark => "gruvbox-dark",
Self::Matrix => "matrix",
}
}

Expand All @@ -756,6 +758,7 @@ impl UiThemeValue {
Some("tokyo-night") => Ok(Self::TokyoNight),
Some("dracula") => Ok(Self::Dracula),
Some("gruvbox-dark") => Ok(Self::GruvboxDark),
Some("matrix") => Ok(Self::Matrix),
Some(other) => bail!("unsupported theme '{other}'"),
None => bail!("invalid theme '{value}'"),
}
Expand Down Expand Up @@ -1184,7 +1187,8 @@ background_color = "#1A1B26"
"catppuccin-mocha",
"tokyo-night",
"dracula",
"gruvbox-dark"
"gruvbox-dark",
"matrix"
])
);
}
Expand Down Expand Up @@ -1269,4 +1273,4 @@ mcp_config_path = "disk-mcp.json"
assert!(outcome.changed);
assert!(!outcome.requires_engine_sync);
}
}
}
71 changes: 68 additions & 3 deletions crates/tui/src/palette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ pub const GRAYSCALE_TEXT_SOFT_RGB: (u8, u8, u8) = (220, 220, 220); // #DCDCDC
pub const GRAYSCALE_BORDER_RGB: (u8, u8, u8) = (96, 96, 96); // #606060
pub const GRAYSCALE_SELECTION_RGB: (u8, u8, u8) = (62, 62, 62); // #3E3E3E

pub const MATRIX_SURFACE_RGB: (u8, u8, u8) = (0, 10, 0); // #000A00
pub const MATRIX_ELEVATED_RGB: (u8, u8, u8) = (0, 51, 0); // #003300
pub const MATRIX_SELECTION_RGB: (u8, u8, u8) = (0, 51, 0); // #003300
Comment on lines +85 to +86
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 MATRIX_SELECTION_RGB and MATRIX_ELEVATED_RGB share the same hex value #003300. Because selection_bg and elevated_bg resolve to identical colours, a highlighted row in any list is visually indistinguishable from an elevated-panel background. The user receives no feedback when navigating the theme picker (or any other list) under the Matrix theme.

Suggested change
pub const MATRIX_ELEVATED_RGB: (u8, u8, u8) = (0, 51, 0); // #003300
pub const MATRIX_SELECTION_RGB: (u8, u8, u8) = (0, 51, 0); // #003300
pub const MATRIX_ELEVATED_RGB: (u8, u8, u8) = (0, 51, 0); // #003300
pub const MATRIX_SELECTION_RGB: (u8, u8, u8) = (0, 85, 0); // #005500

Fix in Codex Fix in Claude Code Fix in Cursor

pub const MATRIX_TEXT_BODY_RGB: (u8, u8, u8) = (136, 255, 136); // #88FF88
pub const MATRIX_TEXT_MUTED_RGB: (u8, u8, u8) = (0, 68, 0); // #004400
pub const MATRIX_TEXT_HINT_RGB: (u8, u8, u8) = (0, 102, 0); // #006600
Comment thread
greptile-apps[bot] marked this conversation as resolved.
pub const MATRIX_TEXT_SOFT_RGB: (u8, u8, u8) = (221, 255, 221); // #DDFFDD
pub const MATRIX_TEXT_DIM_RGB: (u8, u8, u8) = (0, 102, 0); // #006600
pub const MATRIX_BORDER_RGB: (u8, u8, u8) = (0, 204, 0); // #00CC00

// New semantic colors
pub const BORDER_COLOR_RGB: (u8, u8, u8) = WHALE_BORDER_RGB; // #2A4A7F

Expand Down Expand Up @@ -864,6 +874,49 @@ pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme {
tool_failed: Color::Rgb(0xfb, 0x49, 0x34), // red
};

pub const MATRIX_UI_THEME: UiTheme = UiTheme {
name: "matrix",
mode: PaletteMode::Dark,
surface_bg: Color::Rgb(MATRIX_SURFACE_RGB.0, MATRIX_SURFACE_RGB.1, MATRIX_SURFACE_RGB.2),
panel_bg: Color::Rgb(MATRIX_SURFACE_RGB.0, MATRIX_SURFACE_RGB.1, MATRIX_SURFACE_RGB.2),
elevated_bg: Color::Rgb(MATRIX_ELEVATED_RGB.0, MATRIX_ELEVATED_RGB.1, MATRIX_ELEVATED_RGB.2),
composer_bg: Color::Rgb(MATRIX_SURFACE_RGB.0, MATRIX_SURFACE_RGB.1, MATRIX_SURFACE_RGB.2),
selection_bg: Color::Rgb(MATRIX_SELECTION_RGB.0, MATRIX_SELECTION_RGB.1, MATRIX_SELECTION_RGB.2),
header_bg: Color::Rgb(MATRIX_SURFACE_RGB.0, MATRIX_SURFACE_RGB.1, MATRIX_SURFACE_RGB.2),
footer_bg: Color::Rgb(MATRIX_SURFACE_RGB.0, MATRIX_SURFACE_RGB.1, MATRIX_SURFACE_RGB.2),
Comment on lines +884 to +890
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The MATRIX_UI_THEME uses the same color (MATRIX_SURFACE_RGB) for surface_bg, panel_bg, composer_bg, header_bg, and footer_bg. This eliminates the visual separation between the sidebar, composer, and main transcript area, which is a core part of the TUI's layout design. Consider using a slightly different shade (e.g., MATRIX_ELEVATED_RGB) for the panel and composer backgrounds to restore visual hierarchy.

text_dim: Color::Rgb(MATRIX_TEXT_DIM_RGB.0, MATRIX_TEXT_DIM_RGB.1, MATRIX_TEXT_DIM_RGB.2),
text_hint: Color::Rgb(MATRIX_TEXT_HINT_RGB.0, MATRIX_TEXT_HINT_RGB.1, MATRIX_TEXT_HINT_RGB.2),
text_muted: Color::Rgb(MATRIX_TEXT_MUTED_RGB.0, MATRIX_TEXT_MUTED_RGB.1, MATRIX_TEXT_MUTED_RGB.2),
text_body: Color::Rgb(MATRIX_TEXT_BODY_RGB.0, MATRIX_TEXT_BODY_RGB.1, MATRIX_TEXT_BODY_RGB.2),
text_soft: Color::Rgb(MATRIX_TEXT_SOFT_RGB.0, MATRIX_TEXT_SOFT_RGB.1, MATRIX_TEXT_SOFT_RGB.2),
border: Color::Rgb(MATRIX_BORDER_RGB.0, MATRIX_BORDER_RGB.1, MATRIX_BORDER_RGB.2),
accent_primary: Color::Rgb(0, 204, 0),
accent_secondary: Color::Rgb(0, 153, 0),
accent_action: Color::Rgb(0x88, 0xff, 0x88),
error_fg: Color::Rgb(0xb4, 0, 0),
error_hover: Color::Rgb(0xe0, 0, 0),
error_surface: Color::Rgb(0x1a, 0x0d, 0x0d),
error_border: Color::Rgb(0xb4, 0, 0),
error_text: Color::Rgb(0xff, 0x44, 0x44),
warning: Color::Rgb(204, 204, 0),
success: Color::Rgb(0x88, 0xff, 0x88),
info: Color::Rgb(0, 204, 0),
mode_agent: Color::Rgb(0, 153, 0),
mode_yolo: Color::Rgb(255, 100, 100),
mode_plan: Color::Rgb(255, 170, 60),
mode_goal: Color::Rgb(170, 255, 170),
status_ready: Color::Rgb(0, 85, 0),
status_working: Color::Rgb(MATRIX_TEXT_BODY_RGB.0, MATRIX_TEXT_BODY_RGB.1, MATRIX_TEXT_BODY_RGB.2),
status_warning: Color::Rgb(204, 204, 0),
diff_added_fg: Color::Rgb(0x88, 0xff, 0x88),
diff_deleted_fg: Color::Rgb(0xb4, 0, 0),
diff_added_bg: Color::Rgb(0x0d, 0x1a, 0x0d),
diff_deleted_bg: Color::Rgb(0x1a, 0x0d, 0x0d),
tool_running: Color::Rgb(0x88, 0xff, 0x88),
tool_success: Color::Rgb(0, 102, 0),
tool_failed: Color::Rgb(0xb4, 0, 0),
};

/// Stable identifiers for the named themes the user can select. `System`
/// defers to `PaletteMode::detect()` (terminal-driven dark/light). Each
/// dark/light id resolves to a single fixed `UiTheme`.
Expand All @@ -877,6 +930,7 @@ pub enum ThemeId {
TokyoNight,
Dracula,
GruvboxDark,
Matrix,
}

impl ThemeId {
Expand All @@ -894,6 +948,7 @@ impl ThemeId {
"tokyo-night" => Some(Self::TokyoNight),
"dracula" => Some(Self::Dracula),
"gruvbox-dark" => Some(Self::GruvboxDark),
"matrix" => Some(Self::Matrix),
_ => None,
}
}
Expand All @@ -911,6 +966,7 @@ impl ThemeId {
Self::TokyoNight => "tokyo-night",
Self::Dracula => "dracula",
Self::GruvboxDark => "gruvbox-dark",
Self::Matrix => "matrix",
}
}

Expand All @@ -926,6 +982,7 @@ impl ThemeId {
Self::TokyoNight => "Tokyo Night",
Self::Dracula => "Dracula",
Self::GruvboxDark => "Gruvbox Dark",
Self::Matrix => "Matrix",
}
}

Expand All @@ -941,6 +998,7 @@ impl ThemeId {
Self::TokyoNight => "Deep blue/violet night palette",
Self::Dracula => "Classic high-contrast purple",
Self::GruvboxDark => "Vintage warm earth tones",
Self::Matrix => "The Matrix films inspired theme",
}
}

Expand All @@ -959,6 +1017,7 @@ impl ThemeId {
Self::TokyoNight => TOKYO_NIGHT_UI_THEME,
Self::Dracula => DRACULA_UI_THEME,
Self::GruvboxDark => GRUVBOX_DARK_UI_THEME,
Self::Matrix => MATRIX_UI_THEME,
}
}
}
Expand All @@ -973,6 +1032,7 @@ pub const SELECTABLE_THEMES: &[ThemeId] = &[
ThemeId::TokyoNight,
ThemeId::Dracula,
ThemeId::GruvboxDark,
ThemeId::Matrix,
];

impl UiTheme {
Expand Down Expand Up @@ -1016,6 +1076,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"),
"matrix" | "hacker" => Some("matrix"),
_ => None,
}
}
Expand Down Expand Up @@ -1185,7 +1246,7 @@ 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::Matrix
)
}

Expand Down Expand Up @@ -1219,7 +1280,11 @@ pub fn adapt_fg_for_theme(color: Color, theme: ThemeId, ui: &UiTheme) -> Color {
} else if color == TEXT_ACCENT || color == DEEPSEEK_SKY || color == ACCENT_TOOL_LIVE {
ui.status_working
} else if color == TEXT_REASONING || color == ACCENT_REASONING_LIVE {
ui.mode_plan
if theme == ThemeId::Matrix {
Color::Rgb(0x00, 0x55, 0x00) // #005500
} else {
ui.mode_plan
}
Comment on lines +1287 to +1291
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The hardcoded color #005500 for reasoning text in the Matrix theme has very low contrast against the dark background (#000A00), making it difficult to read. Reasoning text is a primary content element and should be easily legible. Using a brighter green, such as the theme's mode_goal color, would significantly improve readability while maintaining the theme's aesthetic.

Suggested change
if theme == ThemeId::Matrix {
Color::Rgb(0x00, 0x55, 0x00) // #005500
} else {
ui.mode_plan
}
if theme == ThemeId::Matrix {
ui.mode_goal
} else {
ui.mode_plan
}

Copy link
Copy Markdown
Author

@malsony malsony May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning text is not fading to background, actually.

Comment thread
greptile-apps[bot] marked this conversation as resolved.
} else if color == ACCENT_TOOL_ISSUE {
ui.mode_yolo
} else if color == STATUS_WARNING {
Expand Down Expand Up @@ -2060,4 +2125,4 @@ mod tests {
let _ = ColorDepth::detect();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing newline at end of file. cargo fmt typically enforces a trailing newline; the absence here (also visible in config_ui.rs) will cause the cargo fmt --all -- --check step in the checklist to fail.

Fix in Codex Fix in Claude Code Fix in Cursor

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I thought I added a newline once, maybe it was "washed away" when I was trying to build and update... for several times...

let _ = adapt_color(DEEPSEEK_INK, ColorDepth::detect());
}
}
}
21 changes: 5 additions & 16 deletions crates/tui/src/tui/theme_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,11 @@ impl ThemePickerView {
}

fn move_up(&mut self) {
let len = SELECTABLE_THEMES.len();
if len == 0 {
self.selected = 0;
} else if self.selected == 0 {
self.selected = len - 1;
} else {
self.selected -= 1;
}
self.selected = (self.selected + SELECTABLE_THEMES.len() - 1) % SELECTABLE_THEMES.len();
}
Comment on lines 92 to 94
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The refactored move_up uses unsigned arithmetic: if SELECTABLE_THEMES were ever empty (len == 0), 0 + 0 - 1 would underflow a usize and the subsequent % 0 would panic. The old code guarded this explicitly.

Suggested change
fn move_up(&mut self) {
let len = SELECTABLE_THEMES.len();
if len == 0 {
self.selected = 0;
} else if self.selected == 0 {
self.selected = len - 1;
} else {
self.selected -= 1;
}
self.selected = (self.selected + SELECTABLE_THEMES.len() - 1) % SELECTABLE_THEMES.len();
}
fn move_up(&mut self) {
let len = SELECTABLE_THEMES.len();
if len > 0 {
self.selected = (self.selected + len - 1) % len;
}
}

Fix in Codex Fix in Claude Code Fix in Cursor


fn move_down(&mut self) {
let len = SELECTABLE_THEMES.len();
if len == 0 {
self.selected = 0;
} else {
self.selected = (self.selected + 1) % len;
}
self.selected = (self.selected + 1) % SELECTABLE_THEMES.len();
}
}

Expand Down Expand Up @@ -323,12 +311,13 @@ mod tests {
#[test]
fn arrow_navigation_wraps_at_picker_edges() {
let mut v = ThemePickerView::new("system".to_string());
let last = SELECTABLE_THEMES.last().unwrap();

let action = v.handle_key(key(KeyCode::Up));
assert_eq!(selected_name(&action), Some(ThemeId::GruvboxDark.name()));
assert_eq!(selected_name(&action), Some(last.name()));

let action = v.handle_key(key(KeyCode::Down));
assert_eq!(selected_name(&action), Some(ThemeId::System.name()));
assert_eq!(selected_name(&action), Some(SELECTABLE_THEMES[0].name()));
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/tui/src/tui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5908,6 +5908,7 @@ fn toggle_live_transcript_overlay(app: &mut App) {
app.needs_redraw = true;
}

#[allow(clippy::too_many_arguments)]
async fn handle_view_events(
terminal: &mut AppTerminal,
app: &mut App,
Expand Down