fix(theme): apply per-user theme_pref on login, persist toggle to server

alpine-init.js resolved the theme purely from localStorage, so it never
reflected the logged-in user's theme_pref: switching user kept the previous
user's theme because localStorage persists per-browser.

- base.html injects window.__SERVER_THEME from the session theme (empty when
  anonymous, preserving OS-preference behaviour on the login page).
- alpine-init.js now treats the server value as authoritative for logged-in
  users (server > localStorage > OS > light) and syncs localStorage to it.
- The navbar toggle now persists the choice via POST /auth/set-theme, which
  updates the session and the user's theme_pref, so it survives navigation
  and matches on the next login.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-05 08:32:11 +00:00
parent 2eb5c51353
commit 5cc5123a0c
3 changed files with 57 additions and 2 deletions
+22 -1
View File
@@ -1,7 +1,7 @@
"""Authentication blueprint - login, logout, profile."""
from functools import wraps
from flask import Blueprint, abort, flash, redirect, render_template, request, session, url_for
from flask import Blueprint, abort, flash, jsonify, redirect, render_template, request, session, url_for
from flask_babel import gettext as _
from services.api_client import api_client
@@ -133,6 +133,27 @@ def logout():
return redirect(url_for("auth.login"))
@auth_bp.route("/set-theme", methods=["POST"])
@login_required
def set_theme():
"""Persist the user's theme toggle (light/dark) to session and profile.
Called via AJAX from the navbar theme switch so the choice survives
navigation and matches on the next login. Best-effort on the API side:
the session is updated regardless so the current browsing stays correct.
"""
theme = (request.get_json(silent=True) or {}).get("theme", "")
if theme not in ("light", "dark"):
return jsonify({"error": True, "detail": "invalid theme"}), 400
session["theme"] = theme
try:
api_client.put("/api/auth/me", data={"theme_pref": theme})
except Exception:
pass # Non-fatal: session theme is already updated for this session.
return jsonify({"ok": True, "theme": theme})
@auth_bp.route("/profile", methods=["GET", "POST"])
@login_required
def profile():