- bubbles spring in anchored at their avatar corner; thinking state is a
typing bubble (three bouncing dots) that springs in/out where the reply
lands; reduced-motion falls back to opacity fades
- mobile /odin is immersive: AppShell hides its header and main padding
below md, the chat owns 100svh full-bleed with safe-area insets and a
back arrow (history back, home on deep links)
- desktop card border kept divider-colored via longhand border props (a
responsive border shorthand reset the color to black)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AppShell sized the shell with minHeight:100vh while #root (GlobalStyles) and the
Odin pane (OdinChat) use 100dvh. On mobile 100vh = the large viewport (address
bar hidden), a chrome-bar taller than the visible 100dvh, so the shell floor
pushed the document a few px over and html{overflow-x:hidden} (the page scroller)
turned it into a small downward scroll. Most visible on /odin (a fixed 100dvh
pane that otherwise never scrolls the page). Fixes every page on mobile; desktop
unchanged (100vh==100dvh there).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
react / react-dom 18.3.1 -> 19.2.7; @types/react 18.3.31 -> 19.2.17;
@types/react-dom 18.3.7 -> 19.2.3 (latest stable 19.2.x).
Only breaking change hitting our code: React 19 removed the no-arg useRef<T>()
overload. AppShell's logoutTimer -> useRef<T | undefined>(undefined) — the same
transform the official `types-react-codemod preset-19` (useRef-required-initial)
applies. No other preset-19 transforms were relevant (already on createRoot; no
ReactDOM.render, string refs, propTypes/defaultProps on function components).
No Next.js / RSC in this project (it's a Vite SPA served by Fastify), so those
React 19 concerns don't apply here. MUI v7, framer-motion 12, TanStack Query 5,
react-router-dom 6 and react-quill-new all installed cleanly against React 19
(no peer-dependency conflicts).
Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the three remaining hand-written admin-loading/admin-spinner
markup blocks with the kit <LoadingState /> component:
- AdminApp.tsx Suspense fallback
- AppShell.tsx loading branch
- PlanGrid.tsx no-data branch (plan-* classes untouched)
After this the only remaining admin-* classes in TSX are
admin-rich-editor (RichEditor/Quill) and admin-rich-text-view
(sanitized HTML in Order/Invoice detail).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removed MuiColorSchemeSync: it called MUI setMode which ALSO wrote data-theme, fighting ThemeContext (the other writer) → theme flip-flop loop. ThemeContext is now the sole owner; MUI's cssVariables CSS (scoped by [data-theme]) applies from the attribute, no JS bridge needed. New animated ThemeToggle kit button (sun/moon crossfade+rotate) replaces the static AppShell toggle. DashAttendanceToday avatars now solid-fill + white initials (matches StatCard/activity badges).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>