Two issues:
1. Icon badges read as a coloured glyph on a same-hue LIGHT tile
(bgcolor X.light + color X.main) — low contrast, "badly visible".
The AttendanceHistory month tile and the Dashboard 2FA-banner icon now
use a SOLID tile (X.main) + white glyph, matching the StatCard badge
convention. Readable in both schemes (verified: info.main #1d4ed8 light
/ #3b82f6 dark, error.main, white glyph).
2. Theme reverted to light on F5 while the toggle still showed dark.
Cause: two independent stores — our ThemeContext key (boha-theme) and
MUI's own cssVariables mode key (mui-mode). The toggle only wrote
boha-theme, so on mount MUI restored its stale mui-mode and overrode
data-theme. Collapsed to a SINGLE source of truth: ThemeContext now
reads/writes MUI's `mui-mode` key directly (with a one-time migration
from the legacy boha-theme, which is then removed). Verified: toggle
dark -> F5 -> stays dark; only `mui-mode` remains in localStorage.
tsc -b --noEmit, npm run build, vitest 152/152 clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-element color/box-shadow transition repainted the whole tree every frame (~5fps). Replaced with document.startViewTransition: the browser cross-fades one GPU-composited snapshot of the page — smooth regardless of element count. flushSync + useLayoutEffect ensure the data-theme flip is captured in the transition; instant fallback where unsupported / reduced-motion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ThemeContext adds a .theme-transition class to <html> for ~300ms during a real toggle (skipped on initial mount); a scoped base.css rule applies a uniform 0.3s color/bg/border/fill/stroke/shadow transition to the whole tree only while that class is present — so the entire UI fades together on switch, while normal interactions keep their own transitions. Respects prefers-reduced-motion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>