diff --git a/README.md b/README.md index 6cb12d9..ae08e12 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,8 @@ all rings, plus the current certification artifacts. ![Tree Ring TUI dashboard showing ring activity, ambient rings, memories, and detail panels](docs/assets/readme-tui-dashboard.png) Dashboard view with populated cambium, outer, inner, heartwood, scar, and seed -rings. +rings. Ambient layers are duller at lower relative fullness and lighter as their +share of stored memories grows; activity pulses are temporary. ![Tree Ring TUI search mode showing ranked recall results from a populated store](docs/assets/readme-tui-search.png) diff --git a/crates/tree-ring-memory-cli/src/tui/mod.rs b/crates/tree-ring-memory-cli/src/tui/mod.rs index dacf32d..b0263fc 100644 --- a/crates/tree-ring-memory-cli/src/tui/mod.rs +++ b/crates/tree-ring-memory-cli/src/tui/mod.rs @@ -12,9 +12,11 @@ mod theme; use std::path::PathBuf; use std::time::Duration; +use ratatui::backend::Backend; use ratatui::crossterm::event as terminal_event; +use ratatui::Terminal; -use app::App; +use app::{App, AppMode}; pub fn run(root: PathBuf, event_stream: Option, tick_ms: u64) -> Result<(), String> { let tick_rate = Duration::from_millis(tick_ms.clamp(50, 5_000)); @@ -30,7 +32,10 @@ fn run_loop( app: &mut App, tick_rate: Duration, ) -> Result<(), String> { + let mut rendered_mode = app.mode; while !app.should_quit { + clear_on_mode_change(terminal, rendered_mode, app.mode).map_err(|err| err.to_string())?; + rendered_mode = app.mode; terminal .draw(|frame| render::render(frame, app)) .map_err(|err| err.to_string())?; @@ -46,3 +51,48 @@ fn run_loop( } Ok(()) } + +fn clear_on_mode_change( + terminal: &mut Terminal, + previous_mode: AppMode, + current_mode: AppMode, +) -> Result<(), B::Error> { + if previous_mode != current_mode { + terminal.clear()?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use ratatui::backend::TestBackend; + use ratatui::text::Text; + + use super::*; + + #[test] + fn mode_change_clears_the_terminal_for_a_full_redraw() { + let backend = TestBackend::new(12, 2); + let mut terminal = Terminal::new(backend).unwrap(); + terminal + .draw(|frame| frame.render_widget(Text::raw("stale frame"), frame.area())) + .unwrap(); + + clear_on_mode_change(&mut terminal, AppMode::Default, AppMode::Search).unwrap(); + + assert!(!terminal.backend().to_string().contains("stale frame")); + } + + #[test] + fn unchanged_mode_preserves_the_current_terminal() { + let backend = TestBackend::new(12, 2); + let mut terminal = Terminal::new(backend).unwrap(); + terminal + .draw(|frame| frame.render_widget(Text::raw("current"), frame.area())) + .unwrap(); + + clear_on_mode_change(&mut terminal, AppMode::Search, AppMode::Search).unwrap(); + + assert!(terminal.backend().to_string().contains("current")); + } +} diff --git a/docs/assets/readme-tui-dashboard.png b/docs/assets/readme-tui-dashboard.png index 48f5e62..500a907 100644 Binary files a/docs/assets/readme-tui-dashboard.png and b/docs/assets/readme-tui-dashboard.png differ diff --git a/docs/assets/readme-tui-evidence.png b/docs/assets/readme-tui-evidence.png index 3b689b0..9863f34 100644 Binary files a/docs/assets/readme-tui-evidence.png and b/docs/assets/readme-tui-evidence.png differ diff --git a/docs/assets/readme-tui-integrations.png b/docs/assets/readme-tui-integrations.png index 6449f28..4c2ef1c 100644 Binary files a/docs/assets/readme-tui-integrations.png and b/docs/assets/readme-tui-integrations.png differ diff --git a/docs/assets/readme-tui-search.png b/docs/assets/readme-tui-search.png index 6a4eac4..52da30e 100644 Binary files a/docs/assets/readme-tui-search.png and b/docs/assets/readme-tui-search.png differ