From baf208281da735e13bed6aabcd346551d6de6ec5 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Wed, 25 Mar 2026 02:42:03 -0400 Subject: [PATCH] tui: overlay screens via F-keys (F1=context, F2=agents) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced debug_visible bool with an Overlay enum. F1 shows the context/debug screen (Ctrl+D still works as alias), F2 shows the agents screen (placeholder for now — will show surface, observe, reflect, journal status). Esc closes any overlay. Co-Authored-By: Proof of Concept --- src/agent/tui.rs | 91 +++++++++++++++++++++++++++++++++----------- src/bin/poc-agent.rs | 2 +- 2 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/agent/tui.rs b/src/agent/tui.rs index 23bbd60..2d54de6 100644 --- a/src/agent/tui.rs +++ b/src/agent/tui.rs @@ -9,6 +9,8 @@ // Uses ratatui + crossterm. The App struct holds all TUI state and // handles rendering. Input is processed from crossterm key events. +const SCREEN_LEGEND: &str = " F1=main F2=agents F10=context "; + use crossterm::{ event::{EnableMouseCapture, DisableMouseCapture, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, MouseButton}, terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, @@ -328,8 +330,8 @@ pub struct App { pub hotkey_actions: Vec, /// Pane areas from last draw (for mouse click → pane selection). pane_areas: [Rect; 3], // [autonomous, conversation, tools] - /// Debug screen visible (Ctrl+D toggle). - debug_visible: bool, + /// Active overlay screen (F1=context, F2=agents, Ctrl+D=context). + overlay: Option, /// Debug screen scroll offset. debug_scroll: u16, /// Index of selected context section in debug view (for expand/collapse). @@ -342,6 +344,15 @@ pub struct App { shared_context: SharedContextState, } +/// Overlay screens toggled by F-keys. +#[derive(Debug, Clone, Copy, PartialEq)] +enum Overlay { + /// F1 / Ctrl+D — context window, model info, budget + Context, + /// F2 — subconscious agent status + Agents, +} + /// Actions triggered by hotkeys, consumed by the main loop. #[derive(Debug)] pub enum HotkeyAction { @@ -385,7 +396,7 @@ impl App { submitted: Vec::new(), hotkey_actions: Vec::new(), pane_areas: [Rect::default(); 3], - debug_visible: false, + overlay: None, debug_scroll: 0, debug_selected: None, debug_expanded: std::collections::HashSet::new(), @@ -510,8 +521,8 @@ impl App { return; } KeyCode::Char('d') => { - self.debug_visible = !self.debug_visible; - self.debug_scroll = 0; + // Legacy alias for F10 + self.set_overlay(Overlay::Context); return; } KeyCode::Char('p') => { @@ -522,13 +533,15 @@ impl App { } } - // Debug screen captures scroll keys and Esc - if self.debug_visible { + // Overlay screen captures scroll keys and Esc + if self.overlay.is_some() { match key.code { - KeyCode::Esc => { - self.debug_visible = false; + KeyCode::F(1) => { + self.overlay = None; return; } + KeyCode::F(10) => { self.set_overlay(Overlay::Context); return; } + KeyCode::F(2) => { self.set_overlay(Overlay::Agents); return; } KeyCode::Up => { let cs = self.read_context_state(); let n = self.debug_item_count(&cs); @@ -574,6 +587,8 @@ impl App { } match key.code { + KeyCode::F(10) => { self.set_overlay(Overlay::Context); return; } + KeyCode::F(2) => { self.set_overlay(Overlay::Agents); return; } KeyCode::Esc => { self.hotkey_actions.push(HotkeyAction::Interrupt); } @@ -695,9 +710,10 @@ impl App { pub fn draw(&mut self, frame: &mut Frame) { let size = frame.area(); - if self.debug_visible { - self.draw_debug(frame, size); - return; + match self.overlay { + Some(Overlay::Context) => { self.draw_debug(frame, size); return; } + Some(Overlay::Agents) => { self.draw_agents(frame, size); return; } + None => {} } // Main layout: content area + active tools overlay + status bar @@ -744,11 +760,12 @@ impl App { // Draw autonomous pane let auto_active = self.active_pane == ActivePane::Autonomous; - draw_pane(frame, auto_area, "autonomous", &mut self.autonomous, auto_active); + draw_pane(frame, auto_area, "autonomous", &mut self.autonomous, auto_active, + Some(SCREEN_LEGEND)); // Draw tools pane let tools_active = self.active_pane == ActivePane::Tools; - draw_pane(frame, right_col, "tools", &mut self.tools, tools_active); + draw_pane(frame, right_col, "tools", &mut self.tools, tools_active, None); // Draw conversation pane (with input line) let conv_active = self.active_pane == ActivePane::Conversation; @@ -849,7 +866,7 @@ impl App { String::new() }; let right_legend = format!( - "{}{} ^P:pause ^D:debug ^R:reason ^K:kill | {} ", + "{}{} ^P:pause ^R:reason ^K:kill | {} ", reason_indicator, proc_indicator, self.status.model, @@ -961,15 +978,35 @@ impl App { } } - fn draw_debug(&self, frame: &mut Frame, size: Rect) { + fn set_overlay(&mut self, screen: Overlay) { + self.overlay = Some(screen); + self.debug_scroll = 0; + } + + fn draw_agents(&self, frame: &mut Frame, size: Rect) { let mut lines: Vec = Vec::new(); let section = Style::default().fg(Color::Yellow); - lines.push(Line::styled( - " Debug (Ctrl+D or Esc to close, arrows/PgUp/PgDn to scroll)", - Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD), - )); lines.push(Line::raw("")); + lines.push(Line::styled("── Subconscious Agents ──", section)); + lines.push(Line::raw("")); + lines.push(Line::raw(" (not yet wired — will show surface, observe, reflect, journal status)")); + + let block = Block::default() + .title_top(Line::from(SCREEN_LEGEND).left_aligned()) + .title_top(Line::from(" agents ").right_aligned()) + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::Cyan)); + + let para = Paragraph::new(lines) + .block(block) + .scroll((self.debug_scroll, 0)); + frame.render_widget(para, size); + } + + fn draw_debug(&self, frame: &mut Frame, size: Rect) { + let mut lines: Vec = Vec::new(); + let section = Style::default().fg(Color::Yellow); // Model lines.push(Line::styled("── Model ──", section)); @@ -1024,7 +1061,8 @@ impl App { lines.push(Line::raw(format!(" Active tools: {}", self.active_tools.len()))); let block = Block::default() - .title(" Debug ") + .title_top(Line::from(SCREEN_LEGEND).left_aligned()) + .title_top(Line::from(" context ").right_aligned()) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Cyan)); @@ -1142,6 +1180,7 @@ fn draw_pane( title: &str, pane: &mut PaneState, is_active: bool, + left_title: Option<&str>, ) { let inner_height = area.height.saturating_sub(2); @@ -1151,10 +1190,16 @@ fn draw_pane( Style::default().fg(Color::DarkGray) }; - let block = Block::default() - .title(format!(" {} ", title)) + let mut block = Block::default() .borders(Borders::ALL) .border_style(border_style); + if let Some(left) = left_title { + block = block + .title_top(Line::from(left).left_aligned()) + .title_top(Line::from(format!(" {} ", title)).right_aligned()); + } else { + block = block.title(format!(" {} ", title)); + } let lines = pane.all_lines(); let paragraph = Paragraph::new(lines) diff --git a/src/bin/poc-agent.rs b/src/bin/poc-agent.rs index 64443bf..8d399f1 100644 --- a/src/bin/poc-agent.rs +++ b/src/bin/poc-agent.rs @@ -628,7 +628,7 @@ impl Session { "Keys: Tab=pane ^Up/Down=scroll PgUp/PgDn=scroll Mouse=click/scroll".into(), )); let _ = self.ui_tx.send(UiMessage::Info( - " Alt+Enter=newline Esc=interrupt ^P=pause ^R=reasoning ^K=kill ^D=debug".into(), + " Alt+Enter=newline Esc=interrupt ^P=pause ^R=reasoning ^K=kill F10=context F2=agents".into(), )); let _ = self.ui_tx.send(UiMessage::Info( " Shift+click for native text selection (copy/paste)".into(),