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 <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 19:03:06 -04:00
parent 68f115b880
commit 6f000bd0f6
3 changed files with 80 additions and 67 deletions

View file

@ -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<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
// 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<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
// 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(

View file

@ -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();

View file

@ -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<dyn ScreenView>]) -> 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<String> = 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 {