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 <noreply@anthropic.com>
main
Elijah Voigt 2 months ago
parent fd54cebd57
commit 590e9e209e

@ -1,10 +1,11 @@
--- ---
# claudbg-ysj0 # claudbg-ysj0
title: 'TUI: prevent transcript overflow into sub-agents panel; add Ctrl+L to redraw' title: 'TUI: prevent transcript overflow into sub-agents panel; add Ctrl+L to redraw'
status: todo status: in-progress
type: bug type: bug
priority: normal
created_at: 2026-04-01T16:47:10Z 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. 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.

@ -16,7 +16,7 @@ use crate::tui::state::AppState;
/// Dialog dimensions. /// Dialog dimensions.
const DIALOG_WIDTH: u16 = 36; 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`. /// Compute a centered [`Rect`] of the given size within `area`.
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect { fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
@ -71,7 +71,8 @@ const HELP_TEXT: &str = "\
Global\n\ Global\n\
q/Q quit\n\ q/Q quit\n\
? this help\n\ ? this help\n\
c toggle color"; c toggle color\n\
Ctrl+L redraw screen";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Rendering // Rendering

@ -15,7 +15,7 @@ use std::time::Duration;
use ratatui::Terminal; use ratatui::Terminal;
use ratatui::backend::CrosstermBackend; 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::execute;
use ratatui::crossterm::terminal::{ use ratatui::crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, 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 { if key.kind != KeyEventKind::Press {
return; 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 { match key.code {
KeyCode::Char('q') => state.should_quit = true, KeyCode::Char('q') => state.should_quit = true,
KeyCode::Char('c') => state.color_enabled = !state.color_enabled, 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. // Load transcript data lazily when entering a transcript screen.
maybe_load_transcript(&mut state); 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 // Record the visible chat-log height before rendering so that
// Space / PageDown / PageUp can scroll by exactly one page. // Space / PageDown / PageUp can scroll by exactly one page.
// Layout: 4-row stats header + 3-row search bar + 2 border rows = 9 fixed rows. // Layout: 4-row stats header + 3-row search bar + 2 border rows = 9 fixed rows.

@ -432,9 +432,13 @@ pub fn render_transcript(f: &mut Frame, area: Rect, state: &AppState) {
f.render_widget(header_paragraph, chunks[0]); f.render_widget(header_paragraph, chunks[0]);
// ── Chat log + sub-agents panel (horizontal split) ────────────────────── // ── 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() let body_chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Min(0), Constraint::Length(30)]) .constraints([Constraint::Fill(1), Constraint::Length(30)])
.split(chunks[1]); .split(chunks[1]);
// ── Chat log ───────────────────────────────────────────────────────────── // ── Chat log ─────────────────────────────────────────────────────────────
@ -551,6 +555,11 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool {
state.pending_g = false; 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 { match key.code {
// Toggle focus: ChatLog → SubagentsPanel → SearchInput → ChatLog. // Toggle focus: ChatLog → SubagentsPanel → SearchInput → ChatLog.
KeyCode::Tab => { KeyCode::Tab => {

@ -179,6 +179,11 @@ pub struct AppState {
// ── Lifecycle ─────────────────────────────────────────────────────────── // ── Lifecycle ───────────────────────────────────────────────────────────
/// Set to `true` to signal the event loop to exit. /// Set to `true` to signal the event loop to exit.
pub should_quit: bool, 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 { impl AppState {
@ -258,6 +263,7 @@ impl AppState {
pending_g: false, pending_g: false,
color_enabled, color_enabled,
should_quit: false, should_quit: false,
needs_clear: false,
} }
} }

Loading…
Cancel
Save