Skip to content

fix: THEME setting overridden by stale localStorage value#2054

Open
genobear wants to merge 1 commit into
unfoldadmin:mainfrom
genobear:fix/theme-setting-respects-server-value
Open

fix: THEME setting overridden by stale localStorage value#2054
genobear wants to merge 1 commit into
unfoldadmin:mainfrom
genobear:fix/theme-setting-respects-server-value

Conversation

@genobear
Copy link
Copy Markdown

Summary

Closes #2046.

UNFOLD = {\"THEME\": \"light\"} (or \"dark\") is silently overridden by any value in localStorage.adminTheme. The docs promise the setting "Will disable theme switcher", but in practice it is only honored on a user's first visit — once anything has written to localStorage.adminTheme (auto-detection, the visible toggle, or the hidden Ctrl+E shortcut), Alpine.$persist hydrates from localStorage and the server-rendered theme('light') is ignored on every subsequent load.

This PR makes the server-passed THEME authoritative on every page load.

Changes

src/unfold/static/unfold/js/app.js:

  • Before Alpine.$persist(defaultTheme).as('adminTheme') runs, when the server passes an explicit defaultTheme (i.e. anything other than \"auto\"), overwrite localStorage.adminTheme with that value. $persist then hydrates from the reconciled value rather than a stale one.
  • Track that state as forcedTheme on the Alpine component and short-circuit both switchTheme() and the Ctrl+E keydown handler when it is set, so the documented "Will disable theme switcher" promise is honored end-to-end (no visible toggle and no hidden keybinding).
  • No behavior change when THEME is unset — defaultTheme defaults to \"auto\", forcedTheme is null, and the existing UX (auto-detect + persisted user preference + Ctrl+E + visible switcher) is preserved.

Tests

Added tests/test_theme.py covering:

  • each_context exposes the configured THEME (light, dark, or absent) under the theme context key.
  • The skeleton.html <html> element renders class=\"light\" / class=\"dark\" and x-data=\"theme('light')\" / x-data=\"theme('dark')\" when THEME is set.
  • When THEME is unset, the skeleton renders x-data=\"theme()\" (Alpine falls back to \"auto\").
  • The theme switcher (switchTheme(...) controls) is omitted from rendered HTML when THEME is set, and present when it is not.

The localStorage-reconciliation and forcedTheme short-circuits are JS-only and not exercised by the Python test suite, but the changes are minimal and the existing template gating they back up is now covered.

Test plan

  • uv run -- pytest . — 425 tests pass
  • pre-commit run --files src/unfold/static/unfold/js/app.js tests/test_theme.py — all hooks pass
  • Manual smoke test against the example server with UNFOLD = {\"THEME\": \"light\"} set and a stale localStorage.adminTheme = '\"dark\"' — page loads in light mode, Ctrl+E is inert

Force the server-passed THEME setting to be authoritative on every page
load by reconciling localStorage before Alpine.$persist hydrates, and
short-circuit the Ctrl+E toggle and switchTheme() helper when a theme is
forced so the documented "Will disable theme switcher" promise is honored
end-to-end.

Closes unfoldadmin#2046
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

THEME: "light" setting is silently overridden by stale localStorage (Alpine $persist)

1 participant