magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use crate::summarizer::SummarySnapshot;
use ratatui::{
    style::{Modifier, Style},
    text::{Line, Span, Text},
    widgets::{Paragraph, Wrap},
};

#[derive(Debug, Default)]
pub(crate) struct SummaryDisplay {
    pub(crate) session_id: Option<String>,
    pub(crate) enabled: bool,
    pub(crate) running: bool,
    pub(crate) entries: Vec<String>,
    pub(crate) error: Option<String>,
    /// `u16::MAX` follows the bottom at the current viewport size until manually scrolled.
    pub(crate) scroll: u16,
    pub(crate) requested_enabled: Option<bool>,
}

impl SummaryDisplay {
    /// Keep toggles based on submitted intent until the current worker confirms it.
    pub(crate) fn take_requested_enabled(&mut self) -> Option<bool> {
        let enabled = self.requested_enabled.take()?;
        self.enabled = enabled;
        Some(enabled)
    }

    pub(crate) fn apply(&mut self, snapshot: SummarySnapshot) {
        let mut entries: Vec<_> = snapshot
            .entries
            .into_iter()
            .rev()
            .take(128)
            .map(|entry| crate::tui::activity::sanitize_and_bound_preview(&entry))
            .collect();
        entries.reverse();
        if self.session_id.as_deref() != Some(snapshot.session_id.as_str())
            || self.entries != entries
        {
            self.scroll = u16::MAX;
        }
        self.session_id = Some(snapshot.session_id);
        self.enabled = snapshot.enabled;
        self.running = snapshot.running;
        self.entries = entries;
        self.error = snapshot
            .error
            .map(|text| crate::tui::activity::sanitize_and_bound_preview(&text));
    }

    pub(crate) fn text(&self) -> Text<'_> {
        let heading_style = Style::default().add_modifier(Modifier::BOLD);
        let mut lines = Vec::new();
        if let Some(error) = &self.error {
            lines.push(Line::styled("Error:", heading_style));
            lines.extend(error.split('\n').map(Line::raw));
            lines.push(Line::default());
        }
        if self.entries.is_empty() {
            lines.push(Line::raw("No summary yet."));
        } else {
            for (index, entry) in self.entries.iter().enumerate() {
                if index > 0 {
                    lines.push(Line::default());
                }
                let latest = if index + 1 == self.entries.len() {
                    " · Latest"
                } else {
                    ""
                };
                lines.push(Line::from(Span::styled(
                    format!("── Entry {}{latest} ──", index + 1),
                    heading_style,
                )));
                lines.extend(entry.split('\n').map(Line::raw));
            }
        }
        Text::from(lines)
    }

    pub(crate) fn max_scroll(&self, width: u16, height: u16) -> u16 {
        Paragraph::new(self.text())
            .wrap(Wrap { trim: false })
            .line_count(width.max(1))
            .saturating_sub(usize::from(height))
            .min(usize::from(u16::MAX)) as u16
    }
}