diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a05445..a4882ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Inline guidance overlay adds a three-step onboarding tour, keymap-aware cheat sheets (F1 / `?`), and persists completion state via `ui.guidance`. - Status surface renders a layered HUD with streaming/tool indicators, contextual gauges, and redesigned toast cards featuring icons, countdown timers, and a compact history log. - Published a TUI UX & keybinding playbook documenting modal ergonomics, command metadata, theming tokens, and animation policy. +- Automated TUI regression snapshots now cover mode transitions, keymap variants, accessibility presets, and multiple terminal breakpoints. - Cloud usage tracker persists hourly/weekly token totals, adds a `:limits` command, shows live header badges, and raises toast warnings at 80โ€ฏ%/95โ€ฏ% of the configured quotas. - Message rendering caches wrapped lines and throttles streaming redraws to keep the TUI responsive on long sessions. - Model picker badges now inspect provider capabilities so vision/audio/thinking models surface the correct icons even when descriptions are sparse. diff --git a/crates/owlen-tui/tests/chat_snapshots.rs b/crates/owlen-tui/tests/chat_snapshots.rs index 8fd7516..a5a7256 100644 --- a/crates/owlen-tui/tests/chat_snapshots.rs +++ b/crates/owlen-tui/tests/chat_snapshots.rs @@ -10,6 +10,18 @@ use ratatui::{Terminal, backend::TestBackend}; use common::build_chat_app; +async fn send_key(app: &mut ChatApp, code: KeyCode, modifiers: KeyModifiers) { + app.handle_event(Event::Key(KeyEvent::new(code, modifiers))) + .await + .expect("send key event"); +} + +async fn type_text(app: &mut ChatApp, text: &str) { + for ch in text.chars() { + send_key(app, KeyCode::Char(ch), KeyModifiers::NONE).await; + } +} + fn buffer_to_string(buffer: &ratatui::buffer::Buffer) -> String { let mut output = String::new(); @@ -213,3 +225,98 @@ async fn render_guidance_cheatsheet_snapshot() { assert_snapshot!("guidance_cheatsheet", snapshot); }); } + +#[tokio::test(flavor = "multi_thread")] +async fn render_mode_states_snapshots() { + let mut normal = build_chat_app(|_| {}, |_| {}).await; + with_settings!({ snapshot_suffix => "normal-90x28" }, { + let snapshot = render_snapshot(&mut normal, 90, 28); + assert_snapshot!("mode_states", snapshot); + }); + + let mut editing = build_chat_app(|_| {}, |_| {}).await; + send_key(&mut editing, KeyCode::Char('i'), KeyModifiers::NONE).await; + type_text(&mut editing, "Typing in editing mode").await; + with_settings!({ snapshot_suffix => "editing-90x28" }, { + let snapshot = render_snapshot(&mut editing, 90, 28); + assert_snapshot!("mode_states", snapshot); + }); + + let mut visual = build_chat_app( + |_| {}, + |session| { + let conversation = session.conversation_mut(); + conversation.push_user_message("Render visual selection across multiple lines."); + conversation.push_message(Message::assistant( + "Assistant reply for visual mode highlighting.".into(), + )); + }, + ) + .await; + send_key(&mut visual, KeyCode::Char('v'), KeyModifiers::NONE).await; + send_key(&mut visual, KeyCode::Char('j'), KeyModifiers::NONE).await; + send_key(&mut visual, KeyCode::Char('j'), KeyModifiers::NONE).await; + with_settings!({ snapshot_suffix => "visual-90x28" }, { + let snapshot = render_snapshot(&mut visual, 90, 28); + assert_snapshot!("mode_states", snapshot); + }); + + let mut command = build_chat_app(|_| {}, |_| {}).await; + send_key(&mut command, KeyCode::Char(':'), KeyModifiers::NONE).await; + type_text(&mut command, "session save").await; + with_settings!({ snapshot_suffix => "command-90x28" }, { + let snapshot = render_snapshot(&mut command, 90, 28); + assert_snapshot!("mode_states", snapshot); + }); +} + +#[tokio::test(flavor = "multi_thread")] +async fn render_emacs_profile_snapshot() { + let mut app = build_chat_app( + |cfg| { + cfg.ui.keymap_profile = Some("emacs".into()); + cfg.ui.guidance.coach_marks_complete = true; + }, + |_| {}, + ) + .await; + + send_key(&mut app, KeyCode::Char(':'), KeyModifiers::NONE).await; + type_text(&mut app, "help").await; + + with_settings!({ snapshot_suffix => "emacs-110x30" }, { + let snapshot = render_snapshot(&mut app, 110, 30); + assert_snapshot!("emacs_profile", snapshot); + }); +} + +#[tokio::test(flavor = "multi_thread")] +async fn render_accessibility_snapshots() { + let mut high_contrast = build_chat_app( + |cfg| { + cfg.ui.accessibility.high_contrast = true; + cfg.ui.guidance.coach_marks_complete = true; + }, + |_| {}, + ) + .await; + + with_settings!({ snapshot_suffix => "high-contrast-100x32" }, { + let snapshot = render_snapshot(&mut high_contrast, 100, 32); + assert_snapshot!("accessibility_modes", snapshot); + }); + + let mut reduced_chrome = build_chat_app( + |cfg| { + cfg.ui.accessibility.reduced_chrome = true; + cfg.ui.guidance.coach_marks_complete = true; + }, + |_| {}, + ) + .await; + + with_settings!({ snapshot_suffix => "reduced-chrome-100x32" }, { + let snapshot = render_snapshot(&mut reduced_chrome, 100, 32); + assert_snapshot!("accessibility_modes", snapshot); + }); +} diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__accessibility_modes@high-contrast-100x32.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__accessibility_modes@high-contrast-100x32.snap new file mode 100644 index 0000000..ec48a66 --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__accessibility_modes@high-contrast-100x32.snap @@ -0,0 +1,36 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" " +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model ยท Accessibil " +" " +" Context metrics not available Cloud usage pending " +" " +" " +" โ–Œ Chat ยท stub-model PgUp/PgDn scroll ยท g/G jump ยท s save ยท Ctrl+2 focus " +" " +" No messages yet. Press 'i' to start typing. " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" Input Press i to start typing ยท Ctrl+5 focus " +" " +" " +" System/Status " +" " +" " +" NORMAL CHAT Focus INPUT ยท โ“˜ Normal mode โ€ข Press F1 for help owlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:1 ยท Plain Text ยท UTF-8 " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits " +" " diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__accessibility_modes@reduced-chrome-100x32.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__accessibility_modes@reduced-chrome-100x32.snap new file mode 100644 index 0000000..00f7505 --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__accessibility_modes@reduced-chrome-100x32.snap @@ -0,0 +1,36 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model ยท Accessibili " +" " +" Context metrics not available Cloud usage pending " +" " +" Legend ยท Normal <60% ยท Warning 60โ€“85% ยท Critical >85% ยท Modes RC " +" โ–Œ Chat ยท stub-model PgUp/PgDn scroll ยท g/G jump ยท s save ยท Ctrl+2 focus " +" No messages yet. Press 'i' to start typing. " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" Input Press i to start typing ยท Ctrl+5 focus " +" Press 'i' to start typing " +" " +" System/Status " +" Icons: Nerd (auto) " +" " +" NORMAL CHAT Focus INPUT ยท โ“˜ Normal mode โ€ข Press F1 for help owlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:1 ยท Plain Text ยท UTF-8 " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits " diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__emacs_profile@emacs-110x30.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__emacs_profile@emacs-110x30.snap new file mode 100644 index 0000000..a6b262e --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__emacs_profile@emacs-110x30.snap @@ -0,0 +1,34 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" " +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model " +" " +" Context metri Command Palette Ctrl+P " +" " +" :help " +" โ–Œ Chat ยท stub " +" " +" No messages Commands " +" โ€บ help F1 / ? " +" Open the help overlay " +" Support ยท Modes: Normal, Command ยท #help #docs #support " +" tutorial " +" Show keybinding tutorial " +" Support ยท Modes: Command ยท #help #tutorial #onboarding " +" h F1 / ? " +" Open the help overlay " +" Support ยท Modes: Normal, Command ยท #help #docs #support " +" " +" " +" โ–Œ Command En Enter: run ยท Tab: autocomplete ยท /tag filter ยท Esc: cancel " +" " +" " +" System/Status " +" " +" " +" COMMAND CHAT Focus INPUT ยทโ“˜ :help owlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:1 ยท Plain Text ยท UTF-8 " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits " +" " diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@command-90x28.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@command-90x28.snap new file mode 100644 index 0000000..de4ce31 --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@command-90x28.snap @@ -0,0 +1,32 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" " +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model " +" " +" Con Command Palette Ctrl+P " +" " +" :session save " +" โ–Œ C " +" " +" N Commands " +" โ€บ session save " +" Save the current conversation " +" Sessions ยท Modes: Command ยท #session #save #history " +" sessions " +" List saved sessions " +" Sessions ยท Modes: Command ยท #session #history #browse " +" load " +" Load a saved conversation " +" Sessions ยท Modes: Command ยท #session #restore #history " +" โ–Œ C Enter: run ยท Tab: autocomplete ยท /tag filter ยท Esc: cancel " +" " +" " +" System/Status " +" " +" " +" COMMAND CHAT Focus INPUT ยทโ“˜ :session save owlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:1 ยท Plain Text " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits ยท UTF-8 " +" " diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@editing-90x28.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@editing-90x28.snap new file mode 100644 index 0000000..8618060 --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@editing-90x28.snap @@ -0,0 +1,32 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" " +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model " +" " +" Context metrics not available Cloud usage pending " +" " +" " +" โ–Œ Chat ยท stub-model PgUp/PgDn scroll ยท g/G jump ยท s save ยท Ctrl+2 focus " +" " +" No messages yet. Press 'i' to start typing. " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" โ–Œ Input Enter send ยท Shift+Enter newline ยท Esc normal ยท Ctrl+5 focus " +" " +" " +" System/Status " +" " +" " +" INSERT CHAT Focus INPUT ยท โ“˜ Normal mode โ€ข Press F1 for help owlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:20 ยท Plain " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits Text ยท UTF-8 " +" " diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@normal-90x28.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@normal-90x28.snap new file mode 100644 index 0000000..8be5950 --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@normal-90x28.snap @@ -0,0 +1,32 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" " +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model " +" " +" Context metrics not available Cloud usage pending " +" " +" " +" โ–Œ Chat ยท stub-model PgUp/PgDn scroll ยท g/G jump ยท s save ยท Ctrl+2 focus " +" " +" No messages yet. Press 'i' to start typing. " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" Input Press i to start typing ยท Ctrl+5 focus " +" " +" " +" System/Status " +" " +" " +" NORMAL CHAT Focus INPUT ยท โ“˜ Normal mode โ€ข Press F1 for help owlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:1 ยท Plain Text " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits ยท UTF-8 " +" " diff --git a/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@visual-90x28.snap b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@visual-90x28.snap new file mode 100644 index 0000000..abdd7d1 --- /dev/null +++ b/crates/owlen-tui/tests/snapshots/chat_snapshots__mode_states@visual-90x28.snap @@ -0,0 +1,32 @@ +--- +source: crates/owlen-tui/tests/chat_snapshots.rs +expression: snapshot +--- +" " +" ๐Ÿฆ‰ OWLEN v0.2.0 ยท Mode Chat ยท Focus Input ollama_local ยท stub-model " +" " +" Context metrics not available Cloud usage pending " +" " +" " +" โ–Œ Chat ยท stub-model PgUp/PgDn scroll ยท g/G jump ยท s save ยท Ctrl+2 focus " +" " +" โ”Œ ๐Ÿ‘ค You โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” " +" โ”‚ Render visual selection across multiple lines. โ”‚ " +" โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ " +" โ”Œ ๐Ÿค– Assistant โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” " +" โ”‚ Assistant reply for visual mode highlighting. โ”‚ " +" โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ " +" " +" " +" " +" " +" โ–Œ Visual Select y yank ยท d cut ยท Esc cancel ยท Ctrl+5 focus " +" " +" " +" System/Status " +" " +" " +" VISUAL CHAT Focus INPUT ยท โ“˜ -- VISUAL -- (move with j/k, yankowlen-tui " +" Ctrl+5 Icons: Nerd (auto) 1:1 ยท Plain Text " +" F1 Help ? Guidance F12 DebugUsage metrics pending ยท run :limits ยท UTF-8 " +" "