From b1cc344d21c0634278e1fb7660be377789e5090b Mon Sep 17 00:00:00 2001 From: Hu Qiantao Date: Tue, 2 Jun 2026 01:38:00 +0800 Subject: [PATCH] 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. --- crates/tui/src/tui/app.rs | 6 ++++++ crates/tui/src/tui/ui.rs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index 54d94062..8c517c6b 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -1504,6 +1504,11 @@ pub struct App { pub session_started_at: chrono::DateTime, /// 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, /// 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, diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index b8157015..76e7cc87 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -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)