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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user