codetether-agent 4.5.7

A2A-native AI coding agent for the CodeTether ecosystem
Documentation
use ratatui::{
    Frame,
    layout::Rect,
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, List, ListItem},
};

use crate::tui::app::state::App;

pub fn render_webview_sidebar(f: &mut Frame, app: &App, area: Rect) {
    let block = Block::default()
        .borders(Borders::RIGHT)
        .title(" Sessions ")
        .border_style(Style::default().fg(Color::DarkGray));
    let mut items: Vec<ListItem> = Vec::new();
    for (i, summary) in app.state.sessions.iter().enumerate() {
        let title = summary.title.as_deref().unwrap_or("Untitled");
        let label = if i == app.state.selected_session {
            format!("{}", truncate_str(title, 20))
        } else {
            format!("  {}", truncate_str(title, 20))
        };
        let style = if i == app.state.selected_session {
            Style::default()
                .fg(Color::Cyan)
                .add_modifier(Modifier::BOLD)
        } else {
            Style::default().fg(Color::Gray)
        };
        items.push(ListItem::new(Line::from(Span::styled(label, style))));
    }
    if items.is_empty() {
        items.push(ListItem::new(Line::from(Span::styled(
            "  No sessions",
            Style::default().fg(Color::DarkGray),
        ))));
    }
    let list = List::new(items).block(block);
    f.render_widget(list, area);
}

fn truncate_str(s: &str, max: usize) -> String {
    if s.len() <= max {
        return s.to_string();
    }
    let target = max.saturating_sub(1);
    let boundary = (0..=target)
        .rev()
        .find(|&i| s.is_char_boundary(i))
        .unwrap_or(0);
    format!("{}", &s[..boundary])
}