fix(seasonal): auto theme re-evaluates over time; auto clears chat background

- The "auto" seasonal theme was computed once at mount, so a long-lived session
  never crossed a season/holiday-window boundary. SeasonalEffect now re-evaluates
  on an hourly ticker (auto mode only) AND refreshes on entering auto — the
  interval only runs while auto, so a stale mount-time timestamp would otherwise
  resurface on a pinned/off → auto switch (the exact frozen-at-mount bug, caught
  in review). The decision is extracted to a pure resolveSeasonTheme(override,
  now) in seasonSchedule.ts (removing an unsafe cast) and unit-tested.

- Selecting seasonal "auto" while a chat background was set was a silent no-op:
  the seasonal picker only cleared the background for a *specific* theme, and the
  overlay is suppressed while a background is set. Now any active seasonal mode
  ("auto" included) clears the background; only "off" leaves it — symmetric with
  the background picker (which sets seasonal "off"). The overlay guard stays as a
  backstop for legacy persisted state.

Bug-hunt findings from LOTUS_TODO. Three review passes (the 2nd caught the
switch-into-auto staleness); +2 unit tests. Gate-green (tsc, eslint, prettier,
922 tests, build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 17:34:51 -04:00
co-authored by Claude Opus 4.8
parent a24c98b199
commit d416c62b4c
4 changed files with 67 additions and 10 deletions
@@ -549,7 +549,11 @@ function Appearance() {
value={seasonalThemeOverride ?? 'auto'}
onChange={(v) => {
setSeasonalThemeOverride(v);
if (v !== 'auto' && v !== 'off') setChatBackground('none');
// Any active seasonal mode (incl. "auto") is mutually exclusive
// with a chat background — else picking it is a silent no-op, since
// SeasonalEffect suppresses the overlay while a background is set.
// Only "off" leaves the background alone.
if (v !== 'off') setChatBackground('none');
}}
/>
</Box>