From 4bb9e6cfb013241ead243e0fa80ba8424e155d33 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 21 Jul 2026 14:16:44 -0500 Subject: [PATCH] fix(ui-tui): first-swap repaint + config-auto respects shell theme pin (Bugbot r3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - commitTheme compares the first commit against the SEED theme uiStore mounted with (boot cache/default), not null — a cold start that resolves to a skin differing from the default previously skipped the anti-tearing forceRedraw on that first swap. - applyConfiguredTuiTheme('auto') now clears only a pin CONFIG set (tracked via configPinnedTheme), never a HERMES_TUI_THEME the user exported in their shell — that env override outranks auto-detection per detectLightMode's documented priority, and a config hydrate was wiping it. (Bugbot's recurring "GatewaySkin missing paired palette fields" is a diff-truncation false positive — gatewayTypes.ts declares light_colors/ dark_colors, typecheck green.) 81 handler + build/lint green. --- ui-tui/src/app/createGatewayEventHandler.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ui-tui/src/app/createGatewayEventHandler.ts b/ui-tui/src/app/createGatewayEventHandler.ts index 979d92bb535e..ab35bc8e5851 100644 --- a/ui-tui/src/app/createGatewayEventHandler.ts +++ b/ui-tui/src/app/createGatewayEventHandler.ts @@ -68,7 +68,12 @@ const themeForSkin = (s: GatewaySkin) => { let lastCommittedTheme: Theme | null = null const commitTheme = (theme: Theme) => { - const changed = lastCommittedTheme !== null && !themesEqual(lastCommittedTheme, theme) + // First commit compares against the SEED uiStore mounted with (boot cache + // or default), not null — otherwise a first resolve that differs from the + // boot-cached theme skips the anti-tearing repaint (the exact seed≠skin + // case: cold start defaults dark, then resolves to a light skin). + const prev = lastCommittedTheme ?? getUiState().theme + const changed = !themesEqual(prev, theme) lastCommittedTheme = theme patchUiState({ theme }) @@ -113,6 +118,10 @@ export function reapplyTheme(): void { * regardless of the painted background when the editor theme leaves the * terminal background unset. */ +// True once CONFIG (via light/dark) owns the HERMES_TUI_THEME env pin, so an +// 'auto' hydrate knows not to clobber a user's shell-exported pin. +let configPinnedTheme = false + export function applyConfiguredTuiTheme(raw: unknown): void { const mode = String(raw ?? '') .trim() @@ -125,12 +134,17 @@ export function applyConfiguredTuiTheme(raw: unknown): void { return } + configPinnedTheme = true process.env.HERMES_TUI_THEME = mode } else { - if (!current) { + // 'auto' clears only a pin CONFIG set — never a HERMES_TUI_THEME the user + // exported in their shell, which is an explicit override that outranks + // auto-detection (see detectLightMode's priority order). + if (!current || !configPinnedTheme) { return } + configPinnedTheme = false delete process.env.HERMES_TUI_THEME }