Skip to main content

yarli_cli/stream/
style.rs

1//! Visual hierarchy system (Section 28).
2//!
3//! 4-tier visual weight system:
4//! - URGENT: Bold + Red/Yellow — errors, blockers, conflicts
5//! - ACTIVE: Bold + White/Cyan — executing tasks, live output
6//! - CONTEXTUAL: Normal + Gray — completed tasks, metadata
7//! - BACKGROUND: Dim + Dark Gray — decorative elements
8
9use ratatui::style::{Color, Modifier, Style};
10
11use crate::mode::TerminalColorSupport;
12
13/// Visual tier for the 4-level hierarchy system.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Tier {
16    /// Bold + Red/Yellow — errors, blockers, gate failures, action needed.
17    Urgent,
18    /// Bold + White/Cyan — currently executing task, live output, progress.
19    Active,
20    /// Normal + Gray — completed tasks, timestamps, metadata, historical output.
21    Contextual,
22    /// Dim + Dark Gray — decorative borders, dividers, inactive content.
23    Background,
24}
25
26impl Tier {
27    /// Primary style for this tier.
28    pub fn style(self) -> Style {
29        self.style_for(TerminalColorSupport::detect_from_env())
30    }
31
32    pub fn style_for(self, support: TerminalColorSupport) -> Style {
33        match self {
34            Tier::Urgent => Style::default()
35                .fg(urgent_primary(support))
36                .add_modifier(Modifier::BOLD),
37            Tier::Active => Style::default()
38                .fg(active_primary(support))
39                .add_modifier(Modifier::BOLD),
40            Tier::Contextual => Style::default().fg(contextual_primary(support)),
41            Tier::Background => Style::default()
42                .fg(background_primary(support))
43                .add_modifier(Modifier::DIM),
44        }
45    }
46
47    /// Secondary/accent style for this tier.
48    pub fn accent(self) -> Style {
49        self.accent_for(TerminalColorSupport::detect_from_env())
50    }
51
52    pub fn accent_for(self, support: TerminalColorSupport) -> Style {
53        match self {
54            Tier::Urgent => Style::default()
55                .fg(urgent_accent(support))
56                .add_modifier(Modifier::BOLD),
57            Tier::Active => Style::default().fg(active_accent(support)),
58            Tier::Contextual => Style::default().fg(contextual_accent(support)),
59            Tier::Background => Style::default()
60                .fg(background_primary(support))
61                .add_modifier(Modifier::DIM),
62        }
63    }
64}
65
66fn urgent_primary(support: TerminalColorSupport) -> Color {
67    match support {
68        TerminalColorSupport::TrueColor => Color::Rgb(255, 107, 107),
69        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::Red,
70    }
71}
72
73fn urgent_accent(support: TerminalColorSupport) -> Color {
74    match support {
75        TerminalColorSupport::TrueColor => Color::Rgb(255, 209, 102),
76        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::Yellow,
77    }
78}
79
80fn active_primary(support: TerminalColorSupport) -> Color {
81    match support {
82        TerminalColorSupport::TrueColor => Color::Rgb(245, 247, 250),
83        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::White,
84    }
85}
86
87fn active_accent(support: TerminalColorSupport) -> Color {
88    match support {
89        TerminalColorSupport::TrueColor => Color::Rgb(78, 205, 196),
90        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::Cyan,
91    }
92}
93
94fn contextual_primary(support: TerminalColorSupport) -> Color {
95    match support {
96        TerminalColorSupport::TrueColor => Color::Rgb(146, 154, 171),
97        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::DarkGray,
98    }
99}
100
101fn contextual_accent(support: TerminalColorSupport) -> Color {
102    match support {
103        TerminalColorSupport::TrueColor => Color::Rgb(176, 184, 200),
104        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::Gray,
105    }
106}
107
108fn background_primary(support: TerminalColorSupport) -> Color {
109    match support {
110        TerminalColorSupport::TrueColor => Color::Rgb(92, 99, 112),
111        TerminalColorSupport::Ansi16 | TerminalColorSupport::None => Color::DarkGray,
112    }
113}
114
115/// Style for the tree-drawing characters (├─, │, etc.).
116pub fn tree_style() -> Style {
117    Tier::Background.style()
118}
119
120/// Style for timestamps.
121pub fn timestamp_style() -> Style {
122    Tier::Contextual.style()
123}
124
125/// Style for the "▸" transition arrow.
126pub fn arrow_style() -> Style {
127    Tier::Background.style()
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn urgent_is_bold_red() {
136        let s = Tier::Urgent.style_for(TerminalColorSupport::Ansi16);
137        assert_eq!(s.fg, Some(Color::Red));
138        assert!(s.add_modifier.contains(Modifier::BOLD));
139    }
140
141    #[test]
142    fn active_is_bold_white() {
143        let s = Tier::Active.style_for(TerminalColorSupport::Ansi16);
144        assert_eq!(s.fg, Some(Color::White));
145        assert!(s.add_modifier.contains(Modifier::BOLD));
146    }
147
148    #[test]
149    fn contextual_is_dark_gray() {
150        let s = Tier::Contextual.style_for(TerminalColorSupport::Ansi16);
151        assert_eq!(s.fg, Some(Color::DarkGray));
152    }
153
154    #[test]
155    fn background_is_dim_dark_gray() {
156        let s = Tier::Background.style_for(TerminalColorSupport::Ansi16);
157        assert_eq!(s.fg, Some(Color::DarkGray));
158        assert!(s.add_modifier.contains(Modifier::DIM));
159    }
160
161    #[test]
162    fn urgent_accent_is_yellow() {
163        let s = Tier::Urgent.accent_for(TerminalColorSupport::Ansi16);
164        assert_eq!(s.fg, Some(Color::Yellow));
165    }
166
167    #[test]
168    fn active_accent_is_cyan() {
169        let s = Tier::Active.accent_for(TerminalColorSupport::Ansi16);
170        assert_eq!(s.fg, Some(Color::Cyan));
171    }
172
173    #[test]
174    fn truecolor_active_accent_uses_rgb_palette() {
175        let s = Tier::Active.accent_for(TerminalColorSupport::TrueColor);
176        assert_eq!(s.fg, Some(Color::Rgb(78, 205, 196)));
177    }
178}