From 4c3f65027da0b18790238ac94fc7980de3080b4f Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Tue, 25 Aug 2026 13:02:48 +0100 Subject: [PATCH] fix(maestro): make TUI chrome contrast palette-controlled --- crates/maestro-tui/src/palette.rs | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/maestro-tui/src/palette.rs b/crates/maestro-tui/src/palette.rs index cca2413..cf8603e 100644 --- a/crates/maestro-tui/src/palette.rs +++ b/crates/maestro-tui/src/palette.rs @@ -106,6 +106,10 @@ pub struct Palette { pub card: Surface, /// The surface the highlighted card swaps in. pub selection: Surface, + /// Background for TUI-owned chrome outside cards. Fixed-RGB chrome must + /// paint this background too; otherwise its contrast depends on the + /// operator's terminal theme. + pub chrome_bg: Color, /// Panel border while that panel holds focus. pub border_focused: Color, /// Panel border while it does not. @@ -148,8 +152,9 @@ impl Palette { awaiting: Color::Rgb(0xf2, 0xcc, 0x60), error: Color::Rgb(0xff, 0x94, 0x92), }, + chrome_bg: Color::Rgb(0x14, 0x16, 0x1b), border_focused: Color::Rgb(0x58, 0xa6, 0xff), - border_unfocused: Color::Rgb(0x30, 0x36, 0x3d), + border_unfocused: Color::Rgb(0x6b, 0x72, 0x80), focus_accent: Color::Rgb(0x58, 0xa6, 0xff), group_row: Color::Rgb(0x8f, 0xa3, 0xc8), mode_normal_bg: Color::Rgb(0x58, 0xa6, 0xff), @@ -172,15 +177,18 @@ impl Palette { /// Border style for a panel, by whether it holds focus. pub fn border_style(&self, focused: bool) -> Style { if focused { - Style::default().fg(self.border_focused) + Style::default().bg(self.chrome_bg).fg(self.border_focused) } else { - Style::default().fg(self.border_unfocused) + Style::default() + .bg(self.chrome_bg) + .fg(self.border_unfocused) } } /// Style for a sidebar group heading. pub fn group_row_style(&self) -> Style { Style::default() + .bg(self.chrome_bg) .fg(self.group_row) .add_modifier(Modifier::BOLD) } @@ -200,7 +208,7 @@ impl Palette { /// Style for error text outside a card. pub fn error_style(&self) -> Style { - Style::default().fg(self.error_text) + Style::default().bg(self.chrome_bg).fg(self.error_text) } } @@ -298,10 +306,13 @@ mod tests { } /// Other panels' roles are RGB too, so a preset swap moves all of them. + /// Foregrounds are checked against the chrome background they are + /// actually painted on; they must never rely on the terminal default. #[test] fn chrome_roles_are_rgb_values() { let p = active(); for (name, color) in [ + ("chrome_bg", p.chrome_bg), ("border_focused", p.border_focused), ("border_unfocused", p.border_unfocused), ("focus_accent", p.focus_accent), @@ -325,6 +336,19 @@ mod tests { let ratio = contrast_ratio(fg, bg).unwrap(); assert!(ratio >= 4.5, "mode {name} contrast {ratio:.2} is below 4.5"); } + for (name, fg, floor) in [ + ("border_focused", p.border_focused, 3.0), + ("border_unfocused", p.border_unfocused, 3.0), + ("focus_accent", p.focus_accent, 3.0), + ("group_row", p.group_row, 4.5), + ("error_text", p.error_text, 4.5), + ] { + let ratio = contrast_ratio(fg, p.chrome_bg).unwrap(); + assert!( + ratio >= floor, + "{name} contrast {ratio:.2} is below {floor} on chrome_bg" + ); + } } #[test]