diff --git a/crates/tui/src/tui/color_compat.rs b/crates/tui/src/tui/color_compat.rs index c2e0ccc0..c8d0644f 100644 --- a/crates/tui/src/tui/color_compat.rs +++ b/crates/tui/src/tui/color_compat.rs @@ -21,6 +21,11 @@ pub(crate) struct ColorCompatBackend { inner: CrosstermBackend, depth: ColorDepth, palette_mode: PaletteMode, + /// During a resize event the terminal emulator may report stale dimensions + /// for a brief window (observed on macOS Terminal.app and Windows ConHost). + /// Forcing the expected size prevents ratatui's internal `autoresize` from + /// shrinking the viewport back to the stale dimension inside `draw()`. + forced_size: Option, } impl ColorCompatBackend { @@ -29,8 +34,17 @@ impl ColorCompatBackend { inner: CrosstermBackend::new(writer), depth, palette_mode, + forced_size: None, } } + + pub(crate) fn force_size(&mut self, size: Size) { + self.forced_size = Some(size); + } + + pub(crate) fn clear_forced_size(&mut self) { + self.forced_size = None; + } } impl Write for ColorCompatBackend { @@ -88,7 +102,10 @@ impl Backend for ColorCompatBackend { } fn size(&self) -> io::Result { - self.inner.size() + match self.forced_size { + Some(size) => Ok(size), + None => self.inner.size(), + } } fn window_size(&mut self) -> io::Result { diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 4bf91811..43d5abbb 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -18,7 +18,7 @@ use crossterm::{ }; use ratatui::{ Frame, Terminal, - layout::{Constraint, Direction, Layout, Rect}, + layout::{Constraint, Direction, Layout, Rect, Size}, prelude::Widget, style::Style, text::Span, @@ -1643,11 +1643,29 @@ async fn run_event_loop( terminal.clear()?; app.handle_resize(final_w, final_h); + // #macos-resize: some terminals (macOS Terminal.app, Windows + // ConHost) briefly report stale dimensions via + // `terminal::size()` after a resize. ratatui's `draw()` calls + // `autoresize()` internally, which queries the backend size; + // if it sees the old dimension it shrinks the viewport back, + // leaving the newly-expanded area filled with stale content + // from the previous frame (duplicate UI panels). + // + // We force the backend to report the resize-event size for + // this single draw so the buffer matches the real viewport. + { + let backend = terminal.backend_mut(); + backend.force_size(Size::new(final_w, final_h)); + } // Draw immediately so the cleared screen gets repainted before // any other events can interleave. Without this, the next // iteration's draw can race against fast follow-up input and // leave the user staring at a blank/partial frame. terminal.draw(|f| render(f, app))?; + { + let backend = terminal.backend_mut(); + backend.clear_forced_size(); + } app.needs_redraw = false; continue; }