tui: overlay screens via F-keys (F1=context, F2=agents)

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 <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-03-25 02:42:03 -04:00 committed by Kent Overstreet
parent c5efc6e650
commit baf208281d
2 changed files with 69 additions and 24 deletions

View file

@ -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<HotkeyAction>,
/// 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<Overlay>,
/// 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<Line> = 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<Line> = 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)