Skip to main content

leviath_cli/tui/
theme.rs

1//! Color palette, glyph constants, and spinner frames shared by every Leviath
2//! terminal UI (`lev dash`, the `lev setup` wizard, and the markdown renderer).
3//!
4//! This is the single definition: `commands/dashboard/theme` is an alias of
5//! this module and `render.rs` imports from here, rather than each surface
6//! carrying its own hand-copied palette. Two surfaces drifting apart is a real
7//! cost the moment a third one exists.
8
9use ratatui::style::Color;
10
11// ─── Color palette ──────────────────────────────────────────────────────────
12
13pub(crate) const C_ACCENT: Color = Color::Cyan;
14pub(crate) const C_SUCCESS: Color = Color::Green;
15pub(crate) const C_WARN: Color = Color::Yellow;
16pub(crate) const C_ERROR: Color = Color::Red;
17pub(crate) const C_DIM: Color = Color::DarkGray;
18pub(crate) const C_MUTED: Color = Color::Gray;
19pub(crate) const C_WHITE: Color = Color::White;
20pub(crate) const C_ACTIVE: Color = Color::Cyan;
21/// Script-backed (custom) region kind in the dashboard's region list.
22pub(crate) const C_SCRIPT: Color = Color::Magenta;
23pub(crate) const C_BORDER: Color = Color::DarkGray;
24pub(crate) const C_BORDER_FOCUS: Color = Color::Cyan;
25
26/// Background tint for fenced code blocks in the markdown renderer.
27pub(crate) const C_CODE_BG: Color = Color::Rgb(30, 30, 40);
28
29// ─── Stage status glyphs ────────────────────────────────────────────────────
30
31pub(crate) const GLYPH_PENDING: &str = "○";
32pub(crate) const GLYPH_ACTIVE: &str = "●";
33pub(crate) const GLYPH_WAITING: &str = "⏸";
34pub(crate) const GLYPH_COMPLETE: &str = "✓";
35pub(crate) const GLYPH_ERROR: &str = "✗";
36
37// ─── Spinner frames ─────────────────────────────────────────────────────────
38
39pub(crate) const SPINNER: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn theme_colors_are_distinct() {
47        // Verify key colors don't collide
48        assert_ne!(C_ACCENT, C_ERROR);
49        assert_ne!(C_SUCCESS, C_ERROR);
50        assert_ne!(C_WARN, C_SUCCESS);
51        assert_ne!(C_DIM, C_WHITE);
52    }
53
54    #[test]
55    fn code_background_is_not_a_foreground_color() {
56        // The code-block tint must stay distinguishable from the text drawn on
57        // top of it, or fenced blocks render as a solid unreadable slab.
58        assert_ne!(C_CODE_BG, C_WHITE);
59        assert_ne!(C_CODE_BG, C_MUTED);
60    }
61
62    #[test]
63    fn spinner_has_ten_frames() {
64        assert_eq!(SPINNER.len(), 10);
65        for frame in &SPINNER {
66            assert!(!frame.is_empty());
67        }
68    }
69
70    #[test]
71    fn glyphs_are_nonempty() {
72        assert!(!GLYPH_PENDING.is_empty());
73        assert!(!GLYPH_ACTIVE.is_empty());
74        assert!(!GLYPH_WAITING.is_empty());
75        assert!(!GLYPH_COMPLETE.is_empty());
76        assert!(!GLYPH_ERROR.is_empty());
77    }
78}