codetether_agent/tui/ui/webview/
sidebar.rs1use 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}