felt/.planning/phases/01-tournament-engine/01-PLAN-J.md
Mikkel Georgsen 21ff95068e docs(01): create Phase 1 plans (A-N) with research and feedback
14 plans in 6 waves covering all 68 requirements for the Tournament
Engine phase. Includes research (go-libsql, NATS JetStream, Svelte 5
runes, ICM complexity), plan verification (2 iterations), and user
feedback (hand-for-hand UX, SEAT-06 reword, re-entry semantics,
integration test, DKK defaults, JWT 7-day expiry, clock tap safety).

Wave structure:
  1: A (scaffold), B (schema)
  2: C (auth/audit), D (clock), E (templates), J (frontend scaffold)
  3: F (financial), H (seating), M (layout shell)
  4: G (player management)
  5: I (tournament lifecycle)
  6: K (overview/financials), L (players), N (tables/more)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 02:58:22 +01:00

8.7 KiB

Plan J: SvelteKit Frontend Scaffold + Theme + Clients


wave: 2 depends_on: [01-PLAN-A] files_modified:

  • frontend/package.json
  • frontend/svelte.config.js
  • frontend/vite.config.ts
  • frontend/tsconfig.json
  • frontend/src/app.html
  • frontend/src/app.css
  • frontend/src/routes/+layout.ts
  • frontend/src/lib/theme/catppuccin.css
  • frontend/src/lib/ws.ts
  • frontend/src/lib/api.ts
  • frontend/src/lib/stores/tournament.svelte.ts
  • frontend/src/lib/stores/auth.svelte.ts
  • frontend/src/routes/login/+page.svelte autonomous: true requirements: [UI-05, UI-06]

Goal

SvelteKit SPA scaffold with Catppuccin Mocha dark theme, WebSocket client with reconnect, HTTP API client, authentication state, tournament state store, and PIN login page. This is the foundation that the layout shell (Plan M) and all feature views build on.

Context

  • SvelteKit with adapter-static — SPA mode, embedded in Go binary via go:embed
  • Svelte 5 runes — $state, $derived, $effect for all reactivity (not stores)
  • Catppuccin Mocha dark theme default, Latte light theme alternate
  • Mobile-first with responsive desktop layout (sidebar instead of bottom tabs)
  • 48px minimum touch targets — poker room environment, TD using phone with one hand
  • See 01-RESEARCH.md: Svelte 5 Runes WebSocket State, Catppuccin Mocha Theme Setup, Pattern 3 (SvelteKit SPA)

User Decisions (from CONTEXT.md)

  • Overview tab priority — Clock > Time to break > Player count > Table balance > Financial summary > Activity feed
  • 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
  • Catppuccin Mocha dark theme (default), Latte light
  • 48px minimum touch targets, press-state animations, loading states
  • Toast notifications (success, info, warning, error) with auto-dismiss
  • Multi-tournament switching — tabs at top (phone) or split view (tablet landscape)

Tasks

**1. SvelteKit Project Setup:** - Initialize SvelteKit project in `frontend/` directory: - `npm create svelte@latest frontend -- --template skeleton` - Install adapter-static: `npm install -D @sveltejs/adapter-static` - Install Catppuccin: `npm install @catppuccin/palette` - Configure TypeScript
  • svelte.config.js:

    import adapter from '@sveltejs/adapter-static';
    export default {
        kit: {
            adapter: adapter({
                pages: 'build',
                assets: 'build',
                fallback: 'index.html', // SPA fallback
                precompress: false,
                strict: true
            }),
            paths: { base: '' }
        }
    };
    
  • frontend/src/routes/+layout.ts:

    export const prerender = true;
    export const ssr = false; // SPA mode, no SSR
    

2. Catppuccin Theme (frontend/src/lib/theme/catppuccin.css) — UI-05:

  • Define CSS custom properties for Mocha and Latte flavors (all 26 Catppuccin colors)

  • Use [data-theme="mocha"] and [data-theme="latte"] selectors

  • Default to Mocha (dark theme)

  • Include semantic color mappings:

    • --color-bg: base
    • --color-surface: surface0
    • --color-surface-hover: surface1
    • --color-text: text
    • --color-text-secondary: subtext1
    • --color-primary: blue
    • --color-success: green
    • --color-warning: yellow
    • --color-error: red
    • --color-accent: mauve
    • Poker-specific: --color-felt: green, --color-card: text, --color-bounty: pink, --color-prize: yellow
  • Typography: system-ui for body, monospace for timers/numbers

  • Base styles: body background, text color, font size, line height

  • app.css:

    • Import catppuccin.css
    • Reset styles (box-sizing, margin, padding)
    • Touch action: touch-action: manipulation on interactive elements (prevent double-tap zoom)
    • Minimum touch target: .touch-target { min-height: 48px; min-width: 48px; } — UI-06
    • Active/pressed states: :active { transform: scale(0.97); opacity: 0.9; } — UI-06
    • Focus visible styles for accessibility
    • Scrollbar styling for dark theme

3. WebSocket Client (frontend/src/lib/ws.ts):

  • WebSocketClient class:
    • Connect to ws://host/ws (auto-detect protocol for HTTPS → WSS)
    • Auto-reconnect with exponential backoff (1s, 2s, 4s, 8s, max 30s)
    • Connection state tracking: connecting, connected, disconnected, reconnecting
    • send(message) — JSON serialize and send
    • subscribe(tournamentID) — send subscription message
    • onMessage(callback) — register message handler
    • onStateChange(callback) — register connection state handler
    • Parse incoming messages: {type: string, data: any}
    • Route messages to the tournament state store

4. HTTP API Client (frontend/src/lib/api.ts):

  • api object with methods:
    • get(path), post(path, body), put(path, body), delete(path)
    • Base URL: auto-detect from current host
    • Automatically attach JWT from auth store as Authorization: Bearer header
    • Handle 401 responses: clear auth state, redirect to login
    • Handle errors: parse error response, throw typed errors
    • Response parsing: auto-parse JSON
    • Loading state tracking (for UI loading indicators)

5. Auth State (frontend/src/lib/stores/auth.svelte.ts):

  • Using Svelte 5 runes:
    class AuthState {
        token = $state<string | null>(null);
        operator = $state<Operator | null>(null);
    
        get isAuthenticated() { return this.token !== null; }
        get isAdmin() { return this.operator?.role === 'admin'; }
        get isFloor() { return ['admin', 'floor'].includes(this.operator?.role ?? ''); }
    
        login(token: string, operator: Operator) { ... }
        logout() { ... }
    }
    export const auth = new AuthState();
    
    • Persist token to localStorage
    • Load token from localStorage on app startup
    • Clear on logout or 401 response

6. Tournament State (frontend/src/lib/stores/tournament.svelte.ts):

  • Using Svelte 5 runes per 01-RESEARCH.md pattern:
    class TournamentState {
        clock = $state<ClockSnapshot | null>(null);
        players = $state<Player[]>([]);
        tables = $state<Table[]>([]);
        financials = $state<FinancialSummary | null>(null);
        activity = $state<ActivityEntry[]>([]);
        rankings = $state<PlayerRanking[]>([]);
        balanceStatus = $state<BalanceStatus | null>(null);
    
        get remainingPlayers() { return this.players.filter(p => p.status === 'active').length; }
        get isBalanced() { ... }
    
        handleMessage(msg: WSMessage) {
            switch (msg.type) {
                case 'clock.tick': this.clock = msg.data; break;
                case 'player.bust': ... break;
                case 'state.snapshot': this.loadFullState(msg.data); break;
                // ... all message types
            }
        }
    }
    export const tournament = new TournamentState();
    

7. Login Page (frontend/src/routes/login/+page.svelte):

  • PIN input with 4 large digit buttons (48px+ touch targets)
  • PIN display with dots (masked)
  • Submit button
  • Error display for wrong PIN / rate limited
  • Auto-redirect to overview on successful login
  • Catppuccin Mocha styling

8. Update Makefile:

  • Update make frontend target to actually build the SvelteKit project:
    • cd frontend && npm install && npm run build
  • Update make all to build frontend first, then Go binary

Verification:

  • cd frontend && npm run build produces frontend/build/index.html
  • make all builds the full binary with embedded frontend
  • Login page renders with dark theme
  • WebSocket client connects and logs messages to console
  • API client sends requests with JWT header

Verification Criteria

  1. SvelteKit builds to static SPA with npm run build
  2. Catppuccin Mocha dark theme applied with CSS custom properties
  3. 48px minimum touch targets defined in base styles
  4. WebSocket client connects with auto-reconnect and exponential backoff
  5. HTTP API client sends requests with JWT Authorization header
  6. Auth state persists to localStorage and clears on 401
  7. Tournament state store handles all WebSocket message types
  8. Login page works with PIN authentication
  9. make all builds the full binary with embedded frontend

Must-Haves (Goal-Backward)

  • Catppuccin Mocha dark theme as default with semantic color variables
  • 48px minimum touch targets on all interactive elements (base CSS)
  • WebSocket client with auto-reconnect for real-time updates
  • HTTP API client with JWT auth and error handling
  • Tournament state store reactive via Svelte 5 runes
  • Auth state with localStorage persistence
  • PIN login page