Skip to main content

codetether_agent/tui/ui/webview/
sidebar.rs

1use ratatui::{
2    Frame,
3    layout::Rect,
4    style::{Color, Modifier, Style},
5    text::{Line, Span},
6    widgets::{Block, Borders, List, ListItem},
7};
8
9use crate::tui::app::state::App;
10
11pub fn render_webview_sidebar(f: &mut Frame, app: &App, area: Rect) {
12    let block = Block::default()
13        .borders(Borders::RIGHT)
14        .title(" Sessions ")
15        .border_style(Style::default().fg(Color::DarkGray));
16    let mut items: Vec<ListItem> = Vec::new();
17    for (i, summary) in app.state.sessions.iter().enumerate() {
18        let title = summary.title.as_deref().unwrap_or("Untitled");
19        let label = if i == app.state.selected_session {
20            format!("▶ {}", truncate_str(title, 20))
21        } else {
22            format!("  {}", truncate_str(title, 20))
23        };
24        let style = if i == app.state.selected_session {
25            Style::default()
26                .fg(Color::Cyan)
27                .add_modifier(Modifier::BOLD)
28        } else {
29            Style::default().fg(Color::Gray)
30        };
31        items.push(ListItem::new(Line::from(Span::styled(label, style))));
32    }
33    if items.is_empty() {
34        items.push(ListItem::new(Line::from(Span::styled(
35            "  No sessions",
36            Style::default().fg(Color::DarkGray),
37        ))));
38    }
39    let list = List::new(items).block(block);
40    f.render_widget(list, area);
41}
42
43fn truncate_str(s: &str, max: usize) -> String {
44    if s.len() <= max {
45        return s.to_string();
46    }
47    let target = max.saturating_sub(1);
48    let boundary = (0..=target)
49        .rev()
50        .find(|&i| s.is_char_boundary(i))
51        .unwrap_or(0);
52    format!("{}…", &s[..boundary])
53}