agentry-tui 0.2.2

Multi-Agent Prompt Manager TUI
use ratatui::{
    layout::Alignment,
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Paragraph},
    Frame,
};

use crate::app::App;

const BRAND_PHRASES: &[&str] = &[
    "One prompt to rule them all — synced to 11 agents",
    "Your agents' prompts, finally on the same page",
    "Audit your agents before they audit you",
    "The multi-agent prompt manager that actually manages",
    "Write once, sync everywhere, sleep better",
    "11 agent CLIs, one canonical truth",
    "Prompts drift. agentry doesn't.",
    "The TUI that keeps your agents in line",
    "From chaos to canonical in one keystroke",
    "Canonical prompts, obedient agents",
];

fn brand_phrase_for_elapsed(elapsed_secs: u64) -> &'static str {
    BRAND_PHRASES[((elapsed_secs / 10) as usize) % BRAND_PHRASES.len()]
}

const ASCII_ART: &[&str] = &[
    "████████████████████████████████████████████████████████████████████████████████████████████",
    "█▌                                                                                        ▐█",
    "█▌                                                                                        ▐█",
    "█▌                                                                                        ▐█",
    "█▌                                                     I8                                 ▐█",
    "█▌                                                     I8                                 ▐█",
    "█▌                                                   88888888                             ▐█",
    "█▌                                                     I8                                 ▐█",
    "█▌       ,gggg,gg    ,gggg,gg   ,ggg,    ,ggg,,ggg,    I8    ,gggggg,  gg     gg          ▐█",
    "█▌      dP\"  \"Y8I   dP\"  \"Y8I  i8\" \"8i  ,8\" \"8P\" \"8,   I8    dP\"\"\"\"8I  I8     8I          ▐█",
    "█▌     i8'    ,8I  i8'    ,8I  I8, ,8I  I8   8I   8I  ,I8,  ,8'    8I  I8,   ,8I          ▐█",
    "█▌    ,d8,   ,d8b,,d8,   ,d8I  'YbadP' ,dP   8I   Yb,,d88b,,dP     Y8,,d8b, ,d8I          ▐█",
    "█▌    P\"Y8888P\"'Y8P\"Y8888P\"888888P\"Y8888P'   8I   'Y88P\"\"Y88P      'Y8P\"\"Y88P\"888         ▐█",
    "█▌                       ,d8I'                                              ,d8I'         ▐█",
    "█▌                     ,dP'8I                                             ,dP'8I          ▐█",
    "█▌                    ,8\"  8I                                            ,8\"  8I          ▐█",
    "█▌                    I8   8I                                            I8   8I          ▐█",
    "█▌                    '8, ,8I                                            '8, ,8I          ▐█",
    "█▌                     'Y8P\"                                              'Y8P\"           ▐█",
    "█▌                                                                                        ▐█",
    "█▌                                                                                        ▐█",
    "█▌                                                                                        ▐█",
    "████████████████████████████████████████████████████████████████████████████████████████████"
];

pub fn draw_intro(f: &mut Frame, app: &App) {
    let size = f.area();
    let block = Block::default()
        .borders(Borders::ALL)
        .border_style(Style::default().fg(Color::Cyan))
        .title(" agentry ");

    let inner = block.inner(size);
    f.render_widget(block, size);

    let lines_to_show = ((ASCII_ART.len() as f32) * app.intro_progress).ceil() as usize;

    let mut lines: Vec<Line> = Vec::new();

    // Top spacing
    let top_pad = inner.height.saturating_sub(32) / 2;
    for _ in 0..top_pad {
        lines.push(Line::from(""));
    }

    // ASCII art (progressive reveal)
    let art_style = Style::default()
        .fg(Color::Cyan)
        .add_modifier(Modifier::BOLD);
    for (i, line) in ASCII_ART.iter().enumerate() {
        if i < lines_to_show {
            lines.push(Line::from(Span::styled(*line, art_style)));
        }
    }

    lines.push(Line::from(""));
    lines.push(Line::from(Span::styled(
        "     The Multi-Agent Prompt Manager",
        Style::default()
            .fg(Color::White)
            .add_modifier(Modifier::BOLD),
    )));
    lines.push(Line::from(""));

    // Progress bar
    let bar_width = 40usize;
    let filled = (bar_width as f32 * app.intro_progress).round() as usize;
    let bar_str = format!("[{}{}]", "".repeat(filled), "".repeat(bar_width - filled),);

    let detected = app.detected_agents.len();
    let total = 11;
    let status = if app.intro_progress < 1.0 {
        format!(
            "  {} Loading agents... ({}/{})",
            app.spinner_char(),
            detected,
            total
        )
    } else {
        format!("  {} {} agents detected", app.spinner_char(), detected)
    };

    lines.push(Line::from(Span::styled(
        format!("  {}", bar_str),
        Style::default().fg(Color::Green),
    )));
    lines.push(Line::from(Span::styled(
        status,
        Style::default().fg(Color::Yellow),
    )));

    lines.push(Line::from(""));
    lines.push(Line::from(Span::styled(
        format!(
            "        v{}  │  Press any key to continue",
            env!("CARGO_PKG_VERSION")
        ),
        Style::default().fg(Color::DarkGray),
    )));

    lines.push(Line::from(Span::styled(
        format!(
            "        \"{}\"",
            brand_phrase_for_elapsed(app.intro_started_at.elapsed().as_secs())
        ),
        Style::default().fg(Color::Cyan),
    )));

    // Help hint
    lines.push(Line::from(Span::styled(
        "        j/k: navigate  Tab: switch  ?: help",
        Style::default().fg(Color::DarkGray),
    )));

    let paragraph = Paragraph::new(lines).alignment(Alignment::Center);
    f.render_widget(paragraph, inner);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn brand_phrase_cycles_with_elapsed_time() {
        assert_eq!(BRAND_PHRASES.len(), 10);
        let first = brand_phrase_for_elapsed(0);
        let second = brand_phrase_for_elapsed(10);
        let third = brand_phrase_for_elapsed(20);
        assert_ne!(first, second);
        assert_ne!(second, third);
        assert_eq!(brand_phrase_for_elapsed(100), first);
        assert_eq!(brand_phrase_for_elapsed(90), brand_phrase_for_elapsed(99));
    }
}