travelagent-core 1.10.3

Core library for travelagent code review tool
Documentation
//! UI-agnostic style hint IR (H7).
//!
//! `core` used to depend on `ratatui::style::{Color, Modifier, Style}` just
//! so syntax-highlighted spans could carry styling through the diff model.
//! That was a layering violation: the library core had a UI toolkit in its
//! Cargo.toml. [`StyleHint`] and [`ColorHint`] replace those types with a
//! minimal, serializable description — enough for any frontend to map to
//! its own style surface.
//!
//! The TUI maps `StyleHint` -> `ratatui::Style` at the render boundary
//! (see `travelagent-tui/src/ui/styles.rs`). An MCP or web frontend
//! could map to CSS / ANSI escapes / JSON without any ratatui runtime.

use serde::{Deserialize, Serialize};

/// Color described as 24-bit RGB. Matches the color representation
/// `syntect` emits natively, so we never lose precision going through the
/// IR. ANSI named colors could be added if a future consumer needs them,
/// but the syntax highlighter only ever produces RGB today.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColorHint {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl ColorHint {
    #[must_use]
    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
        Self { r, g, b }
    }
}

/// UI-agnostic style description. A frontend renderer maps this to its
/// own style type (ratatui, HTML/CSS, ANSI, ...). `Default` is a
/// no-style style — useful as a starting point for builder-like chains.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct StyleHint {
    pub fg: Option<ColorHint>,
    pub bg: Option<ColorHint>,
    pub bold: bool,
    pub italic: bool,
    pub underlined: bool,
}

impl StyleHint {
    /// Style with just a foreground color set.
    #[must_use]
    pub const fn with_fg(fg: ColorHint) -> Self {
        Self {
            fg: Some(fg),
            bg: None,
            bold: false,
            italic: false,
            underlined: false,
        }
    }

    /// Return a copy of `self` with `bg` set.
    #[must_use]
    pub const fn bg(mut self, bg: ColorHint) -> Self {
        self.bg = Some(bg);
        self
    }

    /// Return a copy of `self` with the bold attribute enabled.
    #[must_use]
    pub const fn bold(mut self) -> Self {
        self.bold = true;
        self
    }

    /// Return a copy of `self` with the italic attribute enabled.
    #[must_use]
    pub const fn italic(mut self) -> Self {
        self.italic = true;
        self
    }

    /// Return a copy of `self` with the underlined attribute enabled.
    #[must_use]
    pub const fn underlined(mut self) -> Self {
        self.underlined = true;
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn color_hint_rgb_constructor() {
        let c = ColorHint::rgb(12, 34, 56);
        assert_eq!(c.r, 12);
        assert_eq!(c.g, 34);
        assert_eq!(c.b, 56);
    }

    #[test]
    fn style_hint_default_is_no_style() {
        let s = StyleHint::default();
        assert!(s.fg.is_none());
        assert!(s.bg.is_none());
        assert!(!s.bold);
        assert!(!s.italic);
        assert!(!s.underlined);
    }

    #[test]
    fn style_hint_builder_chain() {
        let s = StyleHint::with_fg(ColorHint::rgb(1, 2, 3))
            .bg(ColorHint::rgb(10, 20, 30))
            .bold()
            .italic()
            .underlined();
        assert_eq!(s.fg, Some(ColorHint::rgb(1, 2, 3)));
        assert_eq!(s.bg, Some(ColorHint::rgb(10, 20, 30)));
        assert!(s.bold);
        assert!(s.italic);
        assert!(s.underlined);
    }
}