From 6f000bd0f672a479af9ff095db5b7ee60232d95a Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Sun, 5 Apr 2026 19:03:06 -0400 Subject: [PATCH] user: hook up screen_legend from ScreenView::label() Build legend string from actual screen labels instead of hardcoded constant. Computed once at startup via OnceLock, accessible from all screen draw methods. Co-Authored-By: Kent Overstreet --- src/user/chat.rs | 126 ++++++++++++++++++++--------------------- src/user/event_loop.rs | 1 + src/user/mod.rs | 20 ++++++- 3 files changed, 80 insertions(+), 67 deletions(-) diff --git a/src/user/chat.rs b/src/user/chat.rs index 5b3bc79..5f0b0e8 100644 --- a/src/user/chat.rs +++ b/src/user/chat.rs @@ -159,71 +159,7 @@ impl InteractScreen { _ => {} } } -} -impl ScreenView for InteractScreen { - fn label(&self) -> &'static str { "interact" } - - fn tick(&mut self, frame: &mut Frame, area: Rect, - key: Option, app: &mut App) -> Option { - // Handle keys - if let Some(key) = key { - match key.code { - KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)), - KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => { - let input: String = self.textarea.lines().join("\n"); - if !input.is_empty() { - if self.input_history.last().map_or(true, |h| h != &input) { - self.input_history.push(input.clone()); - } - self.history_index = None; - // TODO: push to submitted via app or return action - } - } - KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_up(3), - KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_down(3), - KeyCode::Up => { - if !self.input_history.is_empty() { - let idx = match self.history_index { None => self.input_history.len() - 1, Some(i) => i.saturating_sub(1) }; - self.history_index = Some(idx); - let mut ta = new_textarea(self.input_history[idx].lines().map(String::from).collect()); - ta.move_cursor(tui_textarea::CursorMove::End); - self.textarea = ta; - } - } - KeyCode::Down => { - if let Some(idx) = self.history_index { - if idx + 1 < self.input_history.len() { - self.history_index = Some(idx + 1); - let mut ta = new_textarea(self.input_history[idx + 1].lines().map(String::from).collect()); - ta.move_cursor(tui_textarea::CursorMove::End); - self.textarea = ta; - } else { - self.history_index = None; - self.textarea = new_textarea(vec![String::new()]); - } - } - } - KeyCode::PageUp => self.scroll_active_up(10), - KeyCode::PageDown => self.scroll_active_down(10), - KeyCode::Tab => { - self.active_pane = match self.active_pane { - ActivePane::Autonomous => ActivePane::Tools, - ActivePane::Tools => ActivePane::Conversation, - ActivePane::Conversation => ActivePane::Autonomous, - }; - } - _ => { self.textarea.input(key); } - } - } - - // Draw - self.draw_main(frame, area, app); - None - } -} - -impl InteractScreen { /// Draw the main (F1) screen — four-pane layout with status bar. pub(crate) fn draw_main(&mut self, frame: &mut Frame, size: Rect, app: &App) { // Main layout: content area + active tools overlay + status bar @@ -407,6 +343,68 @@ impl InteractScreen { } } +impl ScreenView for InteractScreen { + fn label(&self) -> &'static str { "interact" } + + fn tick(&mut self, frame: &mut Frame, area: Rect, + key: Option, app: &mut App) -> Option { + // Handle keys + if let Some(key) = key { + match key.code { + KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)), + KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => { + let input: String = self.textarea.lines().join("\n"); + if !input.is_empty() { + if self.input_history.last().map_or(true, |h| h != &input) { + self.input_history.push(input.clone()); + } + self.history_index = None; + // TODO: push to submitted via app or return action + } + } + KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_up(3), + KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_down(3), + KeyCode::Up => { + if !self.input_history.is_empty() { + let idx = match self.history_index { None => self.input_history.len() - 1, Some(i) => i.saturating_sub(1) }; + self.history_index = Some(idx); + let mut ta = new_textarea(self.input_history[idx].lines().map(String::from).collect()); + ta.move_cursor(tui_textarea::CursorMove::End); + self.textarea = ta; + } + } + KeyCode::Down => { + if let Some(idx) = self.history_index { + if idx + 1 < self.input_history.len() { + self.history_index = Some(idx + 1); + let mut ta = new_textarea(self.input_history[idx + 1].lines().map(String::from).collect()); + ta.move_cursor(tui_textarea::CursorMove::End); + self.textarea = ta; + } else { + self.history_index = None; + self.textarea = new_textarea(vec![String::new()]); + } + } + } + KeyCode::PageUp => self.scroll_active_up(10), + KeyCode::PageDown => self.scroll_active_down(10), + KeyCode::Tab => { + self.active_pane = match self.active_pane { + ActivePane::Autonomous => ActivePane::Tools, + ActivePane::Tools => ActivePane::Conversation, + ActivePane::Conversation => ActivePane::Autonomous, + }; + } + _ => { self.textarea.input(key); } + } + } + + // Draw + self.draw_main(frame, area, app); + None + } +} + /// Draw the conversation pane with a two-column layout: marker gutter + text. /// The gutter shows a marker at turn boundaries, aligned with the input gutter. fn draw_conversation_pane( diff --git a/src/user/event_loop.rs b/src/user/event_loop.rs index 4a7253d..61c8217 100644 --- a/src/user/event_loop.rs +++ b/src/user/event_loop.rs @@ -391,6 +391,7 @@ pub async fn run( Box::new(crate::user::thalamus::ThalamusScreen::new()), ]; let mut active_screen: usize = 0; // 0 = interact, 1-4 = overlay + tui::set_screen_legend(tui::screen_legend_from(&interact, &screens)); let mut terminal = tui::init_terminal()?; let mut reader = EventStream::new(); diff --git a/src/user/mod.rs b/src/user/mod.rs index c482f2d..fd28caf 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -28,10 +28,24 @@ use std::io; use crate::user::ui_channel::{ContextInfo, SharedContextState, StatusInfo}; -/// Build the screen legend from the screen table. +/// Build the screen legend from screen labels. +pub(crate) fn screen_legend_from(interact: &dyn ScreenView, screens: &[Box]) -> String { + let mut parts = vec![format!("F1={}", interact.label())]; + for (i, s) in screens.iter().enumerate() { + parts.push(format!("F{}={}", i + 2, s.label())); + } + format!(" {} ", parts.join(" ")) +} + +// Cached legend — set once at startup by event_loop +static SCREEN_LEGEND: std::sync::OnceLock = std::sync::OnceLock::new(); + +pub(crate) fn set_screen_legend(legend: String) { + let _ = SCREEN_LEGEND.set(legend); +} + pub(crate) fn screen_legend() -> String { - // Built from the SCREENS table in event_loop - " F1=interact F2=conscious F3=subconscious F4=unconscious F5=thalamus ".to_string() + SCREEN_LEGEND.get().cloned().unwrap_or_default() } pub(crate) fn strip_ansi(text: &str) -> String {