From 95a737b592147f9dffd4ab94e481c0cc432ad899 Mon Sep 17 00:00:00 2001 From: liushiao Date: Wed, 27 May 2026 11:47:03 +0800 Subject: [PATCH] fix(tui): clear footer row before rendering statusline --- crates/tui/src/tui/widgets/footer.rs | 43 +++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/tui/src/tui/widgets/footer.rs b/crates/tui/src/tui/widgets/footer.rs index 27f13aa7..6efdfb94 100644 --- a/crates/tui/src/tui/widgets/footer.rs +++ b/crates/tui/src/tui/widgets/footer.rs @@ -11,7 +11,7 @@ use ratatui::{ layout::Rect, style::{Color, Style}, text::{Line, Span}, - widgets::{Paragraph, Widget}, + widgets::{Block, Paragraph, Widget}, }; use unicode_width::UnicodeWidthStr; @@ -573,6 +573,13 @@ impl Renderable for FooterWidget { return; } + // Repaint the whole footer row first so stale transcript glyphs from + // the previous frame cannot survive in cells this frame's spans do not + // touch (#2244). + Block::default() + .style(Style::default().bg(self.props.footer_bg)) + .render(area, buf); + let preview_left_spans = self.left_spans(available_width); let preview_left_width = span_width(&preview_left_spans); let right_budget = available_width @@ -652,6 +659,8 @@ mod tests { use crate::palette; use crate::tui::app::{App, AppMode, TuiOptions}; use ratatui::{ + buffer::Buffer, + layout::Rect, style::{Color, Style}, text::Span, }; @@ -1377,4 +1386,36 @@ mod tests { assert!(!rendered.contains("agent")); assert!(!rendered.contains("deepseek-v4-flash")); } + + #[test] + fn render_clears_stale_cells_across_entire_footer_row() { + let app = make_app(); + let widget = FooterWidget::new(idle_props_for(&app)); + let area = Rect::new(0, 0, 48, 1); + let mut buf = Buffer::empty(area); + + for x in area.x..area.x.saturating_add(area.width) { + buf[(x, area.y)] + .set_symbol("X") + .set_style(Style::default().fg(Color::Red).bg(Color::Blue)); + } + + widget.render(area, &mut buf); + + let rendered: String = (area.x..area.x.saturating_add(area.width)) + .map(|x| buf[(x, area.y)].symbol()) + .collect(); + + assert!( + !rendered.contains('X'), + "footer render must clear stale row content before painting: {rendered:?}" + ); + for x in area.x..area.x.saturating_add(area.width) { + assert_eq!( + buf[(x, area.y)].bg, + app.ui_theme.footer_bg, + "footer background should cover the full row" + ); + } + } }