Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -748,6 +749,7 @@ impl UiThemeValue {
Self::TokyoNight => "tokyo-night",
Self::Dracula => "dracula",
Self::GruvboxDark => "gruvbox-dark",
Self::Matrix => "matrix",
}
}

Expand All @@ -761,6 +763,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 @@ -1191,7 +1194,8 @@ background_color = "#1A1B26"
"catppuccin-mocha",
"tokyo-night",
"dracula",
"gruvbox-dark"
"gruvbox-dark",
"matrix"
])
);
}
Expand Down Expand Up @@ -1276,4 +1280,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 @@ -868,6 +878,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 @@ -881,6 +934,7 @@ pub enum ThemeId {
TokyoNight,
Dracula,
GruvboxDark,
Matrix,
}

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

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

Expand All @@ -945,6 +1002,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 @@ -963,6 +1021,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 @@ -977,6 +1036,7 @@ pub const SELECTABLE_THEMES: &[ThemeId] = &[
ThemeId::TokyoNight,
ThemeId::Dracula,
ThemeId::GruvboxDark,
ThemeId::Matrix,
];

impl UiTheme {
Expand Down Expand Up @@ -1020,6 +1080,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 @@ -1189,7 +1250,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 @@ -1223,7 +1284,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 @@ -2064,4 +2129,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 @@ -6060,6 +6060,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