Skip to main content

baml_agent_tui/
context_bar.rs

1//! Compact project context bar widget.
2//!
3//! Renders a single-line status showing project info:
4//! `📂 my-trip.l2f | 3/5 files | 42 seg | ⭐ 0.87 | ♫ song.mp3 | ⏱ 15.0s`
5
6use ratatui::prelude::*;
7use ratatui::widgets::{Block, Borders, Paragraph};
8
9/// Project context data for display in the status bar.
10#[derive(Debug, Clone, Default)]
11pub struct ProjectContext {
12    pub name: String,
13    pub files_total: usize,
14    pub files_analyzed: usize,
15    pub segments_total: usize,
16    pub top_score: f32,
17    pub timeline_secs: Option<f64>,
18    pub music_track: Option<String>,
19}
20
21impl ProjectContext {
22    /// Render as a single-line bar in the given area.
23    pub fn render(&self, area: Rect, buf: &mut Buffer) {
24        if self.name.is_empty() {
25            let widget = Paragraph::new("No project")
26                .style(Style::default().fg(Color::DarkGray))
27                .block(Block::default().borders(Borders::ALL).title(" Context "));
28            widget.render(area, buf);
29            return;
30        }
31
32        let mut parts = vec![
33            format!("\u{1F4C2} {}", self.name),
34            format!("{}/{} files", self.files_analyzed, self.files_total),
35            format!("{} seg", self.segments_total),
36        ];
37
38        if self.top_score > 0.0 {
39            parts.push(format!("\u{2B50} {:.2}", self.top_score));
40        }
41
42        if let Some(ref track) = self.music_track {
43            // Truncate long names.
44            let name = if track.len() > 20 {
45                format!("{}...", &track[..17])
46            } else {
47                track.clone()
48            };
49            parts.push(format!("\u{266B} {}", name));
50        }
51
52        if let Some(secs) = self.timeline_secs {
53            parts.push(format!("\u{23F1} {:.1}s", secs));
54        }
55
56        let text = parts.join(" \u{2502} ");
57        let widget = Paragraph::new(text)
58            .style(Style::default().fg(Color::White))
59            .block(
60                Block::default()
61                    .borders(Borders::ALL)
62                    .title(" Context ")
63                    .border_style(Style::default().fg(Color::DarkGray)),
64            );
65        widget.render(area, buf);
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use ratatui::buffer::Buffer;
73
74    #[test]
75    fn empty_context_renders() {
76        let ctx = ProjectContext::default();
77        let area = Rect::new(0, 0, 40, 3);
78        let mut buf = Buffer::empty(area);
79        ctx.render(area, &mut buf);
80        // Should not panic.
81    }
82
83    #[test]
84    fn full_context_renders() {
85        let ctx = ProjectContext {
86            name: "trip.l2f".into(),
87            files_total: 5,
88            files_analyzed: 3,
89            segments_total: 42,
90            top_score: 0.87,
91            timeline_secs: Some(15.0),
92            music_track: Some("song.mp3".into()),
93        };
94        let area = Rect::new(0, 0, 80, 3);
95        let mut buf = Buffer::empty(area);
96        ctx.render(area, &mut buf);
97    }
98}