From 4cb834ad6c60cfc0c3fd5aad8c16d6342255931d Mon Sep 17 00:00:00 2001 From: Nils Briggen Date: Mon, 8 Jun 2026 17:08:13 +0200 Subject: [PATCH] P14: dock the session sidebar (persistent column, not a temp drawer) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The session panel now docks as a persistent right-hand column on desktop and only floats as an overlay drawer on phones (md breakpoint). Open state persists (uiStore.sessionDockOpen). The header button opens it (idempotent); the ✕ closes. - Players get it auto-opened on join — they live in the panel during play. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/RootLayout.tsx | 32 ++++++++++++++++++++-------- src/features/play/SessionSidebar.tsx | 10 ++++----- src/stores/uiStore.ts | 5 +++++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/app/RootLayout.tsx b/src/app/RootLayout.tsx index 1ca19b8..b5d011e 100644 --- a/src/app/RootLayout.tsx +++ b/src/app/RootLayout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Link, Outlet, useRouterState } from '@tanstack/react-router'; import { useUiStore } from '@/stores/uiStore'; import { CommandPalette } from '@/components/CommandPalette'; @@ -63,8 +63,11 @@ function ThemeToggle() { export function RootLayout() { const pathname = useRouterState({ select: (s) => s.location.pathname }); const [paletteOpen, setPaletteOpen] = useState(false); - const [sessionOpen, setSessionOpen] = useState(false); + const sessionDockOpen = useUiStore((s) => s.sessionDockOpen); + const setSessionDock = useUiStore((s) => s.setSessionDock); const sessionConnected = useSessionStore((s) => s.status === 'connected'); + const sessionRole = useSessionStore((s) => s.role); + const joinIntent = useSessionStore((s) => s.joinIntent); const activeCampaign = useActiveCampaign(); // While the GM is hosting, mirror state to players (inert unless hosting). useSessionBroadcaster(activeCampaign ?? null); @@ -82,6 +85,15 @@ export function RootLayout() { return () => window.removeEventListener('keydown', onKey); }, []); + // Players get the session panel docked open by default (once) — they live in it. + const autoOpenedRef = useRef(false); + useEffect(() => { + if (!autoOpenedRef.current && joinIntent && sessionRole === 'player') { + autoOpenedRef.current = true; + setSessionDock(true); + } + }, [joinIntent, sessionRole, setSessionDock]); + return (
@@ -107,7 +119,7 @@ export function RootLayout() {
- {activeCampaign && } @@ -121,14 +133,16 @@ export function RootLayout() {
-
- - - -
+
+
+ + + +
+ setSessionDock(false)} /> +
- setSessionOpen(false)} /> {paletteOpen && setPaletteOpen(false)} />}
); diff --git a/src/features/play/SessionSidebar.tsx b/src/features/play/SessionSidebar.tsx index 2eca5a4..3fe1eda 100644 --- a/src/features/play/SessionSidebar.tsx +++ b/src/features/play/SessionSidebar.tsx @@ -18,14 +18,14 @@ export function SessionSidebar({ open, onClose }: { open: boolean; onClose: () = const code = role === 'gm' ? joinCode : intent?.joinCode; const inSession = role !== 'off' || !!intent; + if (!open) return null; + return ( <> - {open &&
} + {/* On phones the panel floats over content with a backdrop; on desktop it docks as a column. */} +