feat(claudbg-6t38,claudbg-trk3): add gg/G vim-style jump navigation in transcript

gg (double-g): jump to top of transcript via pending_g chord detection.
G (Shift+G): jump to bottom of transcript.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Elijah Voigt 2 months ago
parent ae337dcf94
commit a6a0b9a0db

@ -1,10 +1,15 @@
--- ---
# claudbg-6t38 # claudbg-6t38
title: 'TUI: gg jumps to top of transcript' title: 'TUI: gg jumps to top of transcript'
status: todo status: completed
type: feature type: feature
priority: normal
created_at: 2026-03-31T23:45:20Z created_at: 2026-03-31T23:45:20Z
updated_at: 2026-03-31T23:45:20Z updated_at: 2026-04-01T05:53:50Z
--- ---
Add gg (double-g) keybinding in the transcript view to jump to the top of the transcript (vim-style gg navigation). Requires buffering single 'g' keypress to detect the chord. Add gg (double-g) keybinding in the transcript view to jump to the top of the transcript (vim-style gg navigation). Requires buffering single 'g' keypress to detect the chord.
## Summary of Changes
Added `pending_g: bool` field to `AppState` (init to false). In `handle_transcript_event`, clear `pending_g` on any key that isn't 'g', then handle 'g': if `pending_g` was already set, scroll to top (gg); otherwise set `pending_g = true`.

@ -1,10 +1,15 @@
--- ---
# claudbg-trk3 # claudbg-trk3
title: 'TUI: Shift+G jumps to bottom of transcript' title: 'TUI: Shift+G jumps to bottom of transcript'
status: todo status: completed
type: feature type: feature
priority: normal
created_at: 2026-03-31T23:45:17Z created_at: 2026-03-31T23:45:17Z
updated_at: 2026-03-31T23:45:17Z updated_at: 2026-04-01T05:53:50Z
--- ---
Add Shift+G keybinding in the transcript view to jump to the bottom of the transcript (vim-style G navigation). Add Shift+G keybinding in the transcript view to jump to the bottom of the transcript (vim-style G navigation).
## Summary of Changes
Added `KeyCode::Char('G')` arm in `handle_transcript_event`: computes total rendered lines via `build_chat_lines` and sets `transcript_scroll` to `total_lines.saturating_sub(page_height)` — same clamping as PageDown, but jumps directly to bottom.

@ -536,6 +536,11 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool {
return handle_search_input_event(key.code, state); return handle_search_input_event(key.code, state);
} }
// Clear the pending-g chord state on any key that is not 'g' itself.
if !matches!(key.code, KeyCode::Char('g')) {
state.pending_g = false;
}
match key.code { match key.code {
// Toggle focus: ChatLog → SubagentsPanel → SearchInput → ChatLog. // Toggle focus: ChatLog → SubagentsPanel → SearchInput → ChatLog.
KeyCode::Tab => { KeyCode::Tab => {
@ -655,6 +660,23 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool {
false false
} }
} }
// gg → jump to top of transcript (vim-style).
KeyCode::Char('g') => {
if std::mem::take(&mut state.pending_g) {
state.transcript_scroll = 0;
} else {
state.pending_g = true;
}
true
}
// Shift+G → jump to bottom of transcript (vim-style).
KeyCode::Char('G') => {
let total_lines =
build_chat_lines(&state.transcript_entries, state.color_enabled).len();
state.transcript_scroll =
total_lines.saturating_sub(state.transcript_page_height as usize);
true
}
_ => false, _ => false,
} }
} }

@ -157,6 +157,11 @@ pub struct AppState {
/// `None` means the user is not currently browsing history. /// `None` means the user is not currently browsing history.
pub filter_history_pos: Option<usize>, pub filter_history_pos: Option<usize>,
// ── Keyboard chord state ─────────────────────────────────────────────────
/// Tracks whether a leading `g` keypress was seen, awaiting a second `g`
/// to complete the `gg` (jump-to-top) chord. Cleared on any other key.
pub pending_g: bool,
// ── Display ───────────────────────────────────────────────────────────── // ── Display ─────────────────────────────────────────────────────────────
/// Whether color coding is enabled in transcript views. /// Whether color coding is enabled in transcript views.
/// ///
@ -174,7 +179,11 @@ impl AppState {
/// Return the path to the filter history file: `~/.claude/claudbg.tui.history`. /// Return the path to the filter history file: `~/.claude/claudbg.tui.history`.
fn history_file_path() -> Option<PathBuf> { fn history_file_path() -> Option<PathBuf> {
let home = std::env::var("HOME").ok()?; let home = std::env::var("HOME").ok()?;
Some(PathBuf::from(home).join(".claude").join("claudbg.tui.history")) Some(
PathBuf::from(home)
.join(".claude")
.join("claudbg.tui.history"),
)
} }
/// Load filter history lines from disk. /// Load filter history lines from disk.
@ -240,6 +249,7 @@ impl AppState {
filter_active: String::new(), filter_active: String::new(),
filter_history, filter_history,
filter_history_pos: None, filter_history_pos: None,
pending_g: false,
color_enabled, color_enabled,
should_quit: false, should_quit: false,
} }

Loading…
Cancel
Save