P12: persistent live session across navigation

- Player room connection lifted out of the /play route to an app-level hook
  (usePlayerConnection in RootLayout), keyed by a persisted joinIntent — so a
  player stays connected while browsing their sheet, the compendium, anywhere,
  and a reload reconnects automatically.
- Header "● Live: CODE · Leave" badge (PlayerSessionBadge); leaving is explicit
  (leaveRoom clears intent + disconnects). /play renders the table from the store.
- Realtime e2e: player navigates away, badge persists, returns to a synced table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 16:24:31 +02:00
parent 5527b7aa7a
commit d4ae60f199
7 changed files with 99 additions and 25 deletions
+4
View File
@@ -11,6 +11,8 @@ import { Button } from '@/components/ui/Button';
import { SessionControl } from '@/features/play/SessionControl';
import { HandoutControl } from '@/features/play/HandoutControl';
import { useSessionBroadcaster } from '@/features/play/useSessionBroadcaster';
import { usePlayerConnection } from '@/features/play/usePlayerConnection';
import { PlayerSessionBadge } from '@/features/play/PlayerConnection';
const NAV = [
{ to: '/', label: 'Campaigns', exact: true },
@@ -62,6 +64,7 @@ export function RootLayout() {
const activeCampaign = useActiveCampaign();
// While the GM is hosting, mirror state to players (inert unless hosting).
useSessionBroadcaster(activeCampaign ?? null);
usePlayerConnection();
// Global Ctrl/Cmd+K opens the command palette.
useEffect(() => {
@@ -99,6 +102,7 @@ export function RootLayout() {
})}
</nav>
<div className="ml-auto flex items-center gap-2">
<PlayerSessionBadge />
{activeCampaign && <HandoutControl />}
<SessionControl />
<Button size="sm" variant="ghost" onClick={() => setPaletteOpen(true)} title="Command palette (Ctrl/Cmd+K)" aria-label="Open command palette">