Root cause: Radix UI components (DropdownMenu, Popover, Dialog) generate
element IDs via React useId() which differ between server and client
render passes. This causes a hydration mismatch warning in the console.
Fix:
1. Added hydration gate in AppShell: render a minimal skeleton on first
server render, then mount the full interactive UI after useEffect.
This ensures Radix IDs are only generated on the client.
2. Added suppressHydrationWarning to <body> (already on <html>).
This is the official next-themes + Radix pattern.
The skeleton shows: sidebar placeholder + empty header + footer.
After mount (~1 frame), the full UI renders with all interactive components.
Verified: 0 hydration errors, 0 console errors, 0 page errors.
0 TS errors, ESLint clean.