// subconscious_screen.rs — F3 subconscious agent overlay use ratatui::{ layout::Rect, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph, Wrap}, Frame, crossterm::event::KeyCode, }; use super::{App, ScreenView, screen_legend}; pub(crate) struct SubconsciousScreen { selected: usize, scroll: u16, } impl SubconsciousScreen { pub fn new() -> Self { Self { selected: 0, scroll: 0 } } } impl ScreenView for SubconsciousScreen { fn label(&self) -> &'static str { "subconscious" } fn tick(&mut self, frame: &mut Frame, area: Rect, events: &[ratatui::crossterm::event::Event], app: &mut App) { for event in events { if let ratatui::crossterm::event::Event::Key(key) = event { if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; } match key.code { KeyCode::Up => { self.selected = self.selected.saturating_sub(1); } KeyCode::Down => { self.selected = (self.selected + 1) .min(app.agent_state.len().saturating_sub(1)); } KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); } KeyCode::PageDown => { self.scroll += 20; } _ => {} } } } let mut lines: Vec = Vec::new(); let section = Style::default().fg(Color::Yellow); let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC); lines.push(Line::raw("")); lines.push(Line::styled("── Subconscious Agents ──", section)); lines.push(Line::styled(" (↑/↓ select)", hint)); lines.push(Line::raw("")); if app.agent_state.is_empty() { lines.push(Line::styled(" (no agents loaded)", hint)); } for (i, snap) in app.agent_state.iter().enumerate() { let selected = i == self.selected; let prefix = if selected { "▸ " } else { " " }; let bg = if selected { Style::default().bg(Color::DarkGray) } else { Style::default() }; let status_spans = if snap.running { vec![ Span::styled( format!("{}{:<30}", prefix, snap.name), bg.fg(Color::Green), ), Span::styled("● ", bg.fg(Color::Green)), Span::styled( format!("phase: {} turn: {}", snap.current_phase, snap.turn), bg, ), ] } else { let ago = snap.last_run_secs_ago .map(|s| { if s < 60.0 { format!("{:.0}s ago", s) } else if s < 3600.0 { format!("{:.0}m ago", s / 60.0) } else { format!("{:.1}h ago", s / 3600.0) } }) .unwrap_or_else(|| "never".to_string()); vec![ Span::styled( format!("{}{:<30}", prefix, snap.name), bg.fg(Color::Gray), ), Span::styled("○ ", bg.fg(Color::DarkGray)), Span::styled( format!("idle last: {} walked: {}", ago, snap.walked_count), bg.fg(Color::DarkGray), ), ] }; lines.push(Line::from(status_spans)); } let block = Block::default() .title_top(Line::from(screen_legend()).left_aligned()) .title_top(Line::from(" subconscious ").right_aligned()) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Cyan)); let para = Paragraph::new(lines) .block(block) .wrap(Wrap { trim: false }) .scroll((self.scroll, 0)); frame.render_widget(para, area); } }