From 590e9e209e6b6de334633450e2a750c95037be7c Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Wed, 1 Apr 2026 09:55:03 -0700 Subject: [PATCH] feat(claudbg-ysj0): prevent transcript overflow and add Ctrl+L redraw Switch chat-log layout constraint from Min(0) to Fill(1) to ensure the transcript panel never bleeds into the sub-agents panel. Add Ctrl+L keybinding (global + transcript screen) that sets needs_clear, causing terminal.clear() before the next draw to flush rendering artifacts. Update help modal to document Ctrl+L. Co-Authored-By: Claude Sonnet 4.6 --- ...revent-transcript-overflow-into-sub-agents-pa.md | 5 +++-- src/tui/modals/help_modal.rs | 5 +++-- src/tui/run.rs | 13 ++++++++++++- src/tui/screens/transcript.rs | 11 ++++++++++- src/tui/state.rs | 6 ++++++ 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.beans/claudbg-ysj0--tui-prevent-transcript-overflow-into-sub-agents-pa.md b/.beans/claudbg-ysj0--tui-prevent-transcript-overflow-into-sub-agents-pa.md index 702c4de..8f75dba 100644 --- a/.beans/claudbg-ysj0--tui-prevent-transcript-overflow-into-sub-agents-pa.md +++ b/.beans/claudbg-ysj0--tui-prevent-transcript-overflow-into-sub-agents-pa.md @@ -1,10 +1,11 @@ --- # claudbg-ysj0 title: 'TUI: prevent transcript overflow into sub-agents panel; add Ctrl+L to redraw' -status: todo +status: in-progress type: bug +priority: normal created_at: 2026-04-01T16:47:10Z -updated_at: 2026-04-01T16:47:10Z +updated_at: 2026-04-01T16:51:47Z --- Occasionally the Transcript widget overflows into the Sub-Agents panel, making the UI look broken. Prevent this overflow from happening. Also add a Ctrl+L keybinding to force a full screen redraw/reset to recover from any rendering artifacts. diff --git a/src/tui/modals/help_modal.rs b/src/tui/modals/help_modal.rs index a592e9a..d9300a6 100644 --- a/src/tui/modals/help_modal.rs +++ b/src/tui/modals/help_modal.rs @@ -16,7 +16,7 @@ use crate::tui::state::AppState; /// Dialog dimensions. const DIALOG_WIDTH: u16 = 36; -const DIALOG_HEIGHT: u16 = 36; +const DIALOG_HEIGHT: u16 = 37; /// Compute a centered [`Rect`] of the given size within `area`. fn centered_rect(width: u16, height: u16, area: Rect) -> Rect { @@ -71,7 +71,8 @@ const HELP_TEXT: &str = "\ Global\n\ q/Q quit\n\ ? this help\n\ - c toggle color"; + c toggle color\n\ + Ctrl+L redraw screen"; // --------------------------------------------------------------------------- // Rendering diff --git a/src/tui/run.rs b/src/tui/run.rs index 7cba356..f5a3a11 100644 --- a/src/tui/run.rs +++ b/src/tui/run.rs @@ -15,7 +15,7 @@ use std::time::Duration; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; -use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind}; +use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; use ratatui::crossterm::execute; use ratatui::crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, @@ -145,6 +145,11 @@ fn handle_event(event: Event, state: &mut AppState) { if key.kind != KeyEventKind::Press { return; } + // Ctrl+L: force a full terminal redraw to clear any rendering artifacts. + if key.code == KeyCode::Char('l') && key.modifiers.contains(KeyModifiers::CONTROL) { + state.needs_clear = true; + return; + } match key.code { KeyCode::Char('q') => state.should_quit = true, KeyCode::Char('c') => state.color_enabled = !state.color_enabled, @@ -297,6 +302,12 @@ pub fn run_tui() -> Result<()> { // Load transcript data lazily when entering a transcript screen. maybe_load_transcript(&mut state); + // Ctrl+L: clear all terminal cells before the next draw to remove artifacts. + if state.needs_clear { + guard.terminal.clear()?; + state.needs_clear = false; + } + // Record the visible chat-log height before rendering so that // Space / PageDown / PageUp can scroll by exactly one page. // Layout: 4-row stats header + 3-row search bar + 2 border rows = 9 fixed rows. diff --git a/src/tui/screens/transcript.rs b/src/tui/screens/transcript.rs index 3b69cad..e4a7ad1 100644 --- a/src/tui/screens/transcript.rs +++ b/src/tui/screens/transcript.rs @@ -432,9 +432,13 @@ pub fn render_transcript(f: &mut Frame, area: Rect, state: &AppState) { f.render_widget(header_paragraph, chunks[0]); // ── Chat log + sub-agents panel (horizontal split) ────────────────────── + // Use Fill(1) + Length(30) so the chat log gets all remaining space after + // the sub-agents panel is allocated its fixed 30 columns. Fill is + // preferred over Min(0) here because it avoids any layout solver edge + // cases where Min(0) could allow content to escape its allocated rect. let body_chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Min(0), Constraint::Length(30)]) + .constraints([Constraint::Fill(1), Constraint::Length(30)]) .split(chunks[1]); // ── Chat log ───────────────────────────────────────────────────────────── @@ -551,6 +555,11 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool { state.pending_g = false; } + // Ctrl+L: request a full terminal redraw; pass through to global handler. + if key.code == KeyCode::Char('l') && key.modifiers.contains(KeyModifiers::CONTROL) { + return false; + } + match key.code { // Toggle focus: ChatLog → SubagentsPanel → SearchInput → ChatLog. KeyCode::Tab => { diff --git a/src/tui/state.rs b/src/tui/state.rs index a7d7c3a..53b4ec6 100644 --- a/src/tui/state.rs +++ b/src/tui/state.rs @@ -179,6 +179,11 @@ pub struct AppState { // ── Lifecycle ─────────────────────────────────────────────────────────── /// Set to `true` to signal the event loop to exit. pub should_quit: bool, + /// Set to `true` to request a full terminal clear before the next draw. + /// + /// Cleared automatically by the event loop after `terminal.clear()` is called. + /// Used by Ctrl+L to recover from rendering artifacts. + pub needs_clear: bool, } impl AppState { @@ -258,6 +263,7 @@ impl AppState { pending_g: false, color_enabled, should_quit: false, + needs_clear: false, } }