# Plan M: Layout Shell — Header, Tabs, FAB, Toast, Data Table --- wave: 3 depends_on: [01-PLAN-J] files_modified: - frontend/src/routes/+layout.svelte - frontend/src/lib/components/Header.svelte - frontend/src/lib/components/BottomTabs.svelte - frontend/src/lib/components/FAB.svelte - frontend/src/lib/components/Toast.svelte - frontend/src/lib/components/DataTable.svelte autonomous: true requirements: [UI-01, UI-02, UI-03, UI-04, UI-07, UI-08] --- ## Goal The layout shell wraps all page content: a persistent clock header, mobile bottom tab bar (or desktop sidebar), floating action button (FAB) for quick actions, toast notification system, and a reusable data table component. Multi-tournament tab switching when 2+ tournaments are active. ## Context - **SvelteKit scaffold** — Plan J (theme, WS client, API client, auth/tournament state stores) - **Svelte 5 runes** for all reactivity - **Mobile-first** with responsive desktop layout (sidebar instead of bottom tabs) - **48px minimum touch targets** — poker room environment - See 01-RESEARCH.md: Pattern 3 (SvelteKit SPA) ## User Decisions (from CONTEXT.md) - **Mobile-first bottom tab bar** — Overview, Players, Tables, Financials, More - **FAB for quick actions** — Bust, Buy In, Rebuy, Add-On, Pause/Resume - **Persistent header** showing clock, level, blinds, player count - **Desktop/laptop sidebar** with wider content area - **Toast notifications** (success, info, warning, error) with auto-dismiss - **Multi-tournament switching** — tabs at top (phone) ## Tasks **1. Root Layout** (`frontend/src/routes/+layout.svelte`): - Auth guard: if not authenticated, redirect to /login - Structure: ``` ┌─────────────────────────┐ │ PersistentHeader │ ← Fixed top, always visible ├─────────────────────────┤ │ [Tournament Tabs] │ ← Multi-tournament selector (when 2+ active) ├─────────────────────────┤ │ │ │ │ ← Page content (scrollable) │ │ ├─────────────────────────┤ │ BottomTabBar │ ← Fixed bottom (mobile), Sidebar (desktop) └─────────────────────────┘ │ FAB (floating) │ ← Bottom-right, above tab bar │ Toast (floating) │ ← Top-right or bottom-center ``` - Responsive: detect screen width - Mobile (< 768px): bottom tab bar, content full width - Desktop (>= 768px): sidebar left, content fills remaining width — UI-04 **2. Persistent Header** (`frontend/src/lib/components/Header.svelte`) — UI-03: - Fixed at top, always visible - Content (reactive, from tournament state): - Clock: large countdown timer (MM:SS format, red text in final 10s) - Current level number and name (e.g., "Level 5 — NL Hold'em") - Blinds: SB/BB display (e.g., "100/200") - Ante: if > 0, show ante (e.g., "Ante 25") - Player count: "12/20 remaining" (active/total) - Pause indicator: pulsing "PAUSED" when clock is paused - Break indicator: "BREAK" with different styling when on break level - Compact on mobile (smaller font, abbreviated), expanded on desktop - Connected to tournament state store (auto-updates from WebSocket) **3. Bottom Tab Bar** (`frontend/src/lib/components/BottomTabs.svelte`) — UI-01: - 5 tabs: Overview, Players, Tables, Financials, More - Each tab: icon + label - Active tab highlighted with accent color - 48px touch targets — UI-06 - Renders only on mobile (hidden on desktop where sidebar shows) - Navigation: SvelteKit goto() or elements **4. Desktop Sidebar** — UI-04: - Same 5 navigation items as bottom tabs but in vertical sidebar - Wider labels, no icons-only mode - Active item highlighted - Renders only on desktop (>= 768px) **5. Floating Action Button** (`frontend/src/lib/components/FAB.svelte`) — UI-02: - Positioned bottom-right, above the tab bar - Default state: single button with "+" icon - Expanded state: fan out action buttons: - Bust (red) — opens bust-out flow - Buy In (green) — opens buy-in flow - Rebuy (blue) — opens rebuy flow - Add-On (yellow) — opens add-on flow - Pause/Resume (orange) — toggles clock - Each action button: 48px, with label - Press-state animation (scale down on press) — UI-06 - Context-aware: only show relevant actions (e.g., hide "Add-On" if not in addon window) - Close on backdrop tap or ESC **6. Toast Notifications** (`frontend/src/lib/components/Toast.svelte`) — UI-07: - Toast state using Svelte 5 runes: ```typescript class ToastState { toasts = $state([]); success(message: string, duration?: number) { ... } info(message: string, duration?: number) { ... } warning(message: string, duration?: number) { ... } error(message: string, duration?: number) { ... } dismiss(id: string) { ... } } export const toast = new ToastState(); ``` - Auto-dismiss: success (3s), info (4s), warning (5s), error (manual dismiss or 8s) - Stacking: multiple toasts stack vertically - Animation: slide in from right, fade out - Color coding: green (success), blue (info), yellow (warning), red (error) — using Catppuccin colors **7. Data Table** (`frontend/src/lib/components/DataTable.svelte`) — UI-08: - Props: columns config, data array, sortable flag, searchable flag - Features: - Sort by clicking column header (asc/desc toggle) - Sticky header on scroll - Search/filter input (filters across all visible columns) - Row click handler (for detail navigation) - Mobile: swipe actions (swipe left reveals action buttons like "Bust", "Rebuy") - Loading state: skeleton rows - Empty state: "No data" message - Responsive: hide less important columns on mobile (configurable per column) - 48px row height for touch targets — UI-06 **8. Multi-Tournament Tabs:** - Show tabs at top of content area when 2+ tournaments are active - Each tab: tournament name + status indicator - Tapping a tab switches the active tournament (changes which state the views render) - Keep both tournament states in memory (keyed by tournament ID) for fast switching — don't clear/re-fetch on tab change - WebSocket subscribes to all active tournaments simultaneously; messages route to the correct state by tournament ID - On phone: scrollable horizontal tabs **9. Loading States** — UI-06: - Skeleton loading component: animated placeholder matching content shape - Used in all data-fetching views - Full-page loading spinner for initial app load - Inline loading states for buttons (spinner replaces label during action) **Verification:** - App renders with Catppuccin Mocha dark theme - Header shows clock countdown (updates from WebSocket) - Bottom tabs navigate between Overview/Players/Tables/Financials/More on mobile - Sidebar navigation works on desktop - FAB expands to show action buttons - Toast notifications appear and auto-dismiss - Data table sorts, filters, and handles mobile swipe actions - Multi-tournament tabs appear when 2+ tournaments exist - All interactive elements meet 48px minimum touch target ## Verification Criteria 1. Mobile-first bottom tab bar with Overview, Players, Tables, Financials, More 2. FAB expands to show quick actions (Bust, Buy In, Rebuy, Add-On, Pause/Resume) 3. Persistent header shows clock, level, blinds, player count — updates in real time 4. Desktop sidebar navigation for wider screens 5. Toast notifications work (success, info, warning, error) with auto-dismiss 6. Data tables with sort, sticky header, search/filter, swipe actions (mobile) 7. Multi-tournament tabs appear when 2+ tournaments active 8. All interactive elements meet 48px minimum touch targets 9. Loading states (skeleton, spinner) for all data-fetching views ## Must-Haves (Goal-Backward) - [ ] Mobile-first bottom tab bar with 5 navigation tabs - [ ] FAB with context-aware quick actions - [ ] Persistent header with live clock, level, blinds, player count - [ ] Responsive layout (bottom tabs on mobile, sidebar on desktop) - [ ] Data table component reusable across all views - [ ] Toast notification system