Closes 9.1.6 (settings UI), 9.1.7 (reactivity), 9.1.8 (Playwright E2E).
- /settings now has an "Appearance" section housing ThemeToggle. The
onChange callback fires an UPDATE on public.users.theme so the choice
follows the user across devices. The DB write is best-effort: a failed
UPDATE does not roll back the immediate localStorage + <html data-theme>
flip the toggle already performed.
- Root +layout.svelte calls initTheme() once on mount so the JS-side
stores hydrate from localStorage (the inline anti-FOUC script in
app.html has already painted the correct theme by this point — this
just rebuilds the JS state on top so reactive consumers see it).
- (app)/+layout.svelte gains a one-shot effect that reads
public.users.theme on first hydration and adopts the remote value when
it differs from localStorage. Defers the PostgREST query through
setTimeout(..., 0) per the Supabase auth-lock gotcha.
- tests/e2e/theme.test.ts adds T-01 (toggle persists across reload),
T-02 (system mode follows emulated prefers-color-scheme), and T-03
(refresh in dark mode never paints a light first frame, including on
/logged-out which lives outside the (app) group and relies entirely
on the inline app.html script).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>