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 <noreply@anthropic.com>
This commit is contained in:
Hunter Bown
2026-04-27 23:46:49 -05:00
parent 693fbca4ea
commit eab853e92b
3 changed files with 21 additions and 10 deletions
+10 -10
View File
@@ -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<Line<'static>> {
self.render(width, /*low_motion*/ false, RenderMode::Transcript)
@@ -1505,7 +1505,7 @@ fn render_command_mode(command: &str, width: u16, mode: RenderMode) -> Vec<Line<
{
if count >= 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.
+5
View File
@@ -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",
+6
View File
@@ -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);
}