fix(tui): force full repaint on theme switch to prevent stale sidebar colors

When switching themes, ratatui's incremental diff engine may miss
color-only changes in sidebar cells that were rendered with
theme-resolved UiTheme fields rather than palette constants routed
through the backend remap layer.  This manifests as the sidebar
retaining the previous theme's colors until a window resize or
conversation turn triggers a full repaint.

Add a force_next_full_repaint flag on App that is set whenever a
theme or background_color ConfigUpdated event is processed.  The
main render loop merges this into the existing force_terminal_repaint
mechanism, which clears the terminal and redraws every cell.
This commit is contained in:
Hu Qiantao
2026-06-02 01:38:00 +08:00
committed by Hunter B
parent 556e0b46fb
commit b1cc344d21
2 changed files with 25 additions and 0 deletions
+6
View File
@@ -1504,6 +1504,11 @@ pub struct App {
pub session_started_at: chrono::DateTime<chrono::Utc>,
/// Whether the UI needs to be redrawn.
pub needs_redraw: bool,
/// When true, the next draw will be a full repaint (terminal clear +
/// all cells redrawn) instead of a ratatui incremental diff. Used by
/// theme switches where the diff engine may miss color-only changes
/// in sidebar cells that were previously rendered with palette constants.
pub force_next_full_repaint: bool,
/// When the current thinking block started (for duration tracking).
pub thinking_started_at: Option<Instant>,
/// Whether context compaction is currently in progress.
@@ -2094,6 +2099,7 @@ impl App {
decision_card: None,
session_started_at: chrono::Utc::now(),
needs_redraw: true,
force_next_full_repaint: false,
thinking_started_at: None,
is_compacting: false,
is_purging: false,
+19
View File
@@ -2383,6 +2383,12 @@ async fn run_event_loop(
} else {
None
};
// Merge the per-app full-repaint hint (set by theme switches)
// into the loop-level flag before the draw decision.
if app.force_next_full_repaint {
force_terminal_repaint = true;
app.force_next_full_repaint = false;
}
if app.needs_redraw && draw_wait.is_none() {
let was_full_repaint = force_terminal_repaint;
draw_app_frame_inner(terminal, app, force_terminal_repaint)?;
@@ -6807,6 +6813,19 @@ async fn handle_view_events(
persist,
} => {
let result = commands::set_config_value(app, &key, &value, persist);
// Theme / background changes require a full terminal repaint
// because ratatui's incremental diff may miss color-only
// changes in cells that were rendered with theme-resolved
// colors (sidebar panels) rather than palette constants that
// go through the backend remap layer. A full repaint
// (terminal clear + all cells redrawn) guarantees every cell
// picks up the new theme immediately.
if matches!(
key.as_str(),
"theme" | "ui_theme" | "background_color" | "background" | "bg"
) {
app.force_next_full_repaint = true;
}
// Only surface the "key = value" confirmation when the
// change is being persisted. Live-preview events
// (`persist: false`, e.g. arrow keys in the theme picker)