From eab853e92b7740bf02fe5f2d344e7a1ffccf2ef0 Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Mon, 27 Apr 2026 23:46:49 -0500 Subject: [PATCH] feat(tui): #138 add Alt+V chord for tool-details pager Add Alt+V keybinding that opens the tool-details pager regardless of composer state, fixing the broken "press v for details" hint. Update all 5 transcript hint strings to show "Alt+V for details". Bare v with empty composer is preserved for legacy muscle memory. Co-Authored-By: Claude Sonnet 4.6 --- crates/tui/src/tui/history.rs | 20 ++++++++++---------- crates/tui/src/tui/keybindings.rs | 5 +++++ crates/tui/src/tui/ui.rs | 6 ++++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/tui/src/tui/history.rs b/crates/tui/src/tui/history.rs index 9d57966c..4228e66e 100644 --- a/crates/tui/src/tui/history.rs +++ b/crates/tui/src/tui/history.rs @@ -53,7 +53,7 @@ const TOOL_FAILED_SYMBOL: &str = "•"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RenderMode { /// Live in-stream view: thinking is collapsed to a summary, tool output is - /// truncated with a "press v for details" affordance. + /// truncated with a "Alt+V for details" affordance. Live, /// Full transcript view: every line of reasoning and tool output is /// emitted, no caps, no affordance. @@ -223,7 +223,7 @@ impl HistoryCell { if lines.len() > TOOL_CARD_SUMMARY_LINES { lines.truncate(TOOL_CARD_SUMMARY_LINES); lines.push(details_affordance_line( - "press v for details", + "Alt+V for details", Style::default().fg(palette::TEXT_MUTED).italic(), )); } @@ -250,7 +250,7 @@ impl HistoryCell { } /// Render the cell in transcript mode: full content, no caps, no - /// "press v for details" affordances. + /// "Alt+V for details" affordances. /// /// Use this for the pager (`v` / `Ctrl+O`), clipboard exports, and any /// surface that wants the complete body rather than the live summary. @@ -410,7 +410,7 @@ impl ToolCell { } /// Full-content rendering for the pager / clipboard. Tool output that - /// would be capped + suffixed with "press v for details" in the live view + /// would be capped + suffixed with "Alt+V for details" in the live view /// is emitted in full here. pub fn transcript_lines(&self, width: u16) -> Vec> { self.render(width, /*low_motion*/ false, RenderMode::Transcript) @@ -1505,7 +1505,7 @@ fn render_command_mode(command: &str, width: u16, mode: RenderMode) -> Vec= cap { lines.push(details_affordance_line( - "command clipped; press v for details", + "command clipped; Alt+V for details", Style::default().fg(palette::TEXT_MUTED), )); break; @@ -1552,7 +1552,7 @@ fn render_tool_output_mode( let omitted = total.saturating_sub(effective_limit); if omitted > 0 { lines.push(details_affordance_line( - &format!("+{omitted} more lines; press v for details"), + &format!("+{omitted} more lines; Alt+V for details"), Style::default().fg(palette::TEXT_MUTED), )); } @@ -1635,7 +1635,7 @@ fn render_exec_output_mode( if total > 2 * line_limit { let omitted = total.saturating_sub(2 * line_limit); lines.push(details_affordance_line( - &format!("+{omitted} more lines; press v for details"), + &format!("+{omitted} more lines; Alt+V for details"), Style::default().fg(palette::TEXT_MUTED), )); let tail_start = total.saturating_sub(line_limit); @@ -2613,11 +2613,11 @@ mod tests { transcript.len() ); assert!( - live_text.contains("press v for details"), + live_text.contains("Alt+V for details"), "live exec output must surface the pager affordance: {live_text}" ); assert!( - !transcript_text.contains("press v for details"), + !transcript_text.contains("Alt+V for details"), "transcript exec output must not include the pager affordance" ); // First line is always emitted on both surfaces. @@ -2758,7 +2758,7 @@ mod tests { ); let live_text = lines_text(&live); assert!( - live_text.contains("press v for details"), + live_text.contains("Alt+V for details"), "live view must show pager affordance: {live_text}" ); // First line shows up in both; later rows only in transcript. diff --git a/crates/tui/src/tui/keybindings.rs b/crates/tui/src/tui/keybindings.rs index c7a60626..7d1bb4f1 100644 --- a/crates/tui/src/tui/keybindings.rs +++ b/crates/tui/src/tui/keybindings.rs @@ -178,6 +178,11 @@ pub const KEYBINDINGS: &[KeybindingEntry] = &[ description: "Open details for the selected tool or message (when input is empty)", section: KeybindingSection::Submission, }, + KeybindingEntry { + chord: "Alt+V", + description: "Open tool-details pager", + section: KeybindingSection::Submission, + }, KeybindingEntry { chord: "Ctrl+O", description: "Open thinking pager", diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index b1fbeb05..76a560e2 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -1840,6 +1840,12 @@ async fn run_event_loop( app.set_mode(AppMode::Plan); continue; } + KeyCode::Char('v') | KeyCode::Char('V') + if key.modifiers.contains(KeyModifiers::ALT) => + { + open_tool_details_pager(app); + continue; + } KeyCode::Char(c) => { app.insert_char(c); }