strat-gameplay-webapp/.claude/implementation/frontend-sba-vision.md
Cal Corum eab61ad966 CLAUDE: Phases 3.5, F1-F5 Complete - Statistics & Frontend Components
This commit captures work from multiple sessions building the statistics
system and frontend component library.

Backend - Phase 3.5: Statistics System
- Box score statistics with materialized views
- Play stat calculator for real-time updates
- Stat view refresher service
- Alembic migration for materialized views
- Test coverage: 41 new tests (all passing)

Frontend - Phase F1: Foundation
- Composables: useGameState, useGameActions, useWebSocket
- Type definitions and interfaces
- Store setup with Pinia

Frontend - Phase F2: Game Display
- ScoreBoard, GameBoard, CurrentSituation, PlayByPlay components
- Demo page at /demo

Frontend - Phase F3: Decision Inputs
- DefensiveSetup, OffensiveApproach, StolenBaseInputs components
- DecisionPanel orchestration
- Demo page at /demo-decisions
- Test coverage: 213 tests passing

Frontend - Phase F4: Dice & Manual Outcome
- DiceRoller component
- ManualOutcomeEntry with validation
- PlayResult display
- GameplayPanel orchestration
- Demo page at /demo-gameplay
- Test coverage: 119 tests passing

Frontend - Phase F5: Substitutions
- PinchHitterSelector, DefensiveReplacementSelector, PitchingChangeSelector
- SubstitutionPanel with tab navigation
- Demo page at /demo-substitutions
- Test coverage: 114 tests passing

Documentation:
- PHASE_3_5_HANDOFF.md - Statistics system handoff
- PHASE_F2_COMPLETE.md - Game display completion
- Frontend phase planning docs
- NEXT_SESSION.md updated for Phase F6

Configuration:
- Package updates (Nuxt 4 fixes)
- Tailwind config enhancements
- Game store updates

Test Status:
- Backend: 731/731 passing (100%)
- Frontend: 446/446 passing (100%)
- Total: 1,177 tests passing

Next Phase: F6 - Integration (wire all components into game page)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 09:52:30 -06:00

41 KiB

SBA Frontend - Long-Term Vision & Implementation Roadmap

Project: Paper Dynasty Real-Time Game Engine - SBA Frontend Framework: Vue 3 + Nuxt 3 + TypeScript Status: Planning Phase Created: 2025-01-10 Backend Status: Phase 3 Complete (730/731 tests passing, all WebSocket handlers implemented)


Executive Summary

The SBA (Strat-O-Matic Baseball Association) frontend is a mobile-first, real-time web application that connects to our production-ready FastAPI backend via WebSockets and REST APIs. With the backend fully operational (15 WebSocket event handlers, complete game engine, 99.9% test coverage), we're ready to build the user-facing interface that brings Paper Dynasty baseball to life.

Key Objectives:

  • Replace legacy Google Sheets system with modern web UI
  • Deliver seamless real-time multiplayer baseball gameplay
  • Prioritize mobile experience (60%+ target usage)
  • Support live, async, and AI game modes
  • Enable spectator viewing of active games
  • Achieve sub-500ms action latency

Success Metrics:

  • 90% player migration within 1 month of launch
  • 60%+ games played on mobile devices
  • < 500ms average action latency
  • System usability scale (SUS) score > 75
  • Zero critical bugs requiring rollback

Current State Assessment

What We Have

Backend (Production-Ready):

  • Complete game engine with all strategic decisions
  • 15 WebSocket event handlers (defensive, offensive, substitutions)
  • Manual outcome workflow (dice rolling + card reading)
  • Position ratings integration (PD league)
  • Box score statistics (materialized views)
  • 730/731 tests passing (99.9% coverage)
  • PostgreSQL persistence with async operations
  • Discord OAuth integration ready

Frontend (Basic Skeleton):

  • Nuxt 3 project initialized
  • Dependencies installed (Pinia, Tailwind, Socket.io, Axios)
  • Basic configuration (runtime config, TypeScript strict mode)
  • Tailwind CSS configured
  • Development environment ready

🔲 What We Need to Build

Core Application (Phases F1-F4):

  • Authentication flow (Discord OAuth)
  • WebSocket connection management
  • Game state synchronization
  • Real-time game interface
  • Decision input workflows
  • Mobile-optimized UI components
  • Type definitions matching backend models

Advanced Features (Phases F5-F6):

  • Spectator mode
  • Game history and replay
  • Polish and animations
  • Performance optimization

Technical Architecture

Frontend Stack

SBA Frontend (Vue 3 + Nuxt 3)
├── Runtime Environment
│   ├── Nuxt 3 (latest) - Meta framework with SSR/SPA
│   ├── Vue 3 Composition API - Reactive UI layer
│   └── TypeScript (strict mode) - Type safety
│
├── State Management
│   ├── Pinia - Application state stores
│   ├── Composables - Reusable logic hooks
│   └── WebSocket sync - Real-time state updates
│
├── Communication Layer
│   ├── Socket.io-client - WebSocket for real-time events
│   ├── Axios - REST API calls (game creation, history)
│   └── Discord OAuth - Authentication flow
│
├── UI/UX Layer
│   ├── Tailwind CSS - Utility-first styling
│   ├── Mobile-first design - Portrait optimization
│   ├── Touch gestures - Swipe navigation
│   └── Responsive breakpoints - xs/sm/md/lg/xl
│
└── Development Tools
    ├── Vue DevTools - Component inspection
    ├── Nuxt DevTools - Framework debugging
    ├── ESLint + Prettier - Code quality
    └── TypeScript compiler - Type checking

Data Flow Architecture

┌─────────────────────────────────────────────────────────────┐
│                    SBA Frontend (Nuxt 3)                     │
│                                                              │
│  ┌────────────┐    ┌──────────────┐    ┌───────────────┐  │
│  │   Pages    │───▶│  Composables │───▶│  Pinia Stores │  │
│  │ (Routes)   │    │   (Logic)    │    │    (State)    │  │
│  └────────────┘    └──────────────┘    └───────────────┘  │
│         │                  │                    │           │
│         │                  ▼                    │           │
│         │          ┌──────────────┐            │           │
│         └─────────▶│  Components  │◀───────────┘           │
│                    │   (UI/UX)    │                        │
│                    └──────────────┘                        │
│                           │                                 │
└───────────────────────────┼─────────────────────────────────┘
                            │
                ┌───────────┴───────────┐
                │                       │
                ▼                       ▼
    ┌──────────────────┐    ┌──────────────────┐
    │  WebSocket API   │    │    REST API      │
    │  (Socket.io)     │    │   (FastAPI)      │
    └──────────────────┘    └──────────────────┘
                │                       │
                └───────────┬───────────┘
                            │
                            ▼
            ┌──────────────────────────────┐
            │   Game Backend (FastAPI)     │
            │                              │
            │  • Game Engine (in-memory)   │
            │  • PostgreSQL (persistence)  │
            │  • WebSocket Handlers (15)   │
            │  • Discord OAuth             │
            └──────────────────────────────┘

WebSocket Event Flow

Client → Server (Actions):

// Defensive decisions
socket.emit('set_defense', { game_id, positioning })
socket.emit('set_pitcher_focus', { game_id, focus })

// Offensive decisions
socket.emit('set_stolen_base_attempt', { game_id, runner, attempt })
socket.emit('set_offensive_approach', { game_id, approach })

// Manual workflow
socket.emit('request_manual_roll', { game_id })
socket.emit('submit_manual_outcome', { game_id, outcome_data })

// Substitutions
socket.emit('substitute_player', { game_id, ...sub_data })
socket.emit('change_pitcher', { game_id, new_pitcher_id })

Server → Client (Updates):

// State updates
socket.on('game_state_update', (state: GameState) => {})
socket.on('decision_required', (decision: DecisionPrompt) => {})
socket.on('play_completed', (play: PlayResult) => {})

// Manual workflow
socket.on('manual_roll_ready', (roll_data: RollData) => {})
socket.on('awaiting_outcome_input', (prompt: OutcomePrompt) => {})

// Game events
socket.on('inning_change', (inning: number, half: string) => {})
socket.on('game_ended', (result: GameResult) => {})

// Errors
socket.on('invalid_action', (error: ActionError) => {})
socket.on('connection_error', (error: ConnectionError) => {})

Implementation Phases

Phase F1: Core Infrastructure (Week 1)

Goal: Establish foundation for all future development

Deliverables:

  • Project structure (pages, components, composables, stores)
  • TypeScript type definitions (game models, API responses)
  • Discord OAuth authentication flow
  • WebSocket connection management composable
  • REST API client utility (Axios wrapper)
  • Pinia stores (auth, game, ui)
  • Basic routing (home, login, game view)
  • Environment configuration (.env setup)

Key Files:

types/
  ├── game.ts          # GameState, PlayResult, etc.
  ├── player.ts        # SbaPlayer, Lineup
  ├── api.ts           # API request/response types
  └── websocket.ts     # Socket event types

composables/
  ├── useAuth.ts       # Auth state & Discord OAuth
  ├── useWebSocket.ts  # Socket connection management
  ├── useGameState.ts  # Game state synchronization
  └── useApi.ts        # REST API calls

store/
  ├── auth.ts          # Authentication state
  ├── game.ts          # Active game state
  └── ui.ts            # UI state (modals, toasts)

pages/
  ├── index.vue        # Landing/dashboard
  ├── auth/
  │   ├── login.vue    # Login page
  │   └── callback.vue # OAuth callback handler
  └── games/
      └── [id].vue     # Game view (placeholder)

Acceptance Criteria:

  • User can log in via Discord OAuth
  • WebSocket connects successfully to backend
  • TypeScript compilation succeeds with no errors
  • All stores properly typed and functional
  • Environment variables loaded correctly
  • Unit tests written and passing (20+ tests for composables and stores)

Phase F2: Game State Display (Week 2)

Goal: Display real-time game state with proper UI components

Deliverables:

  • Game board component (baseball diamond)
  • Scoreboard component (score, inning, count)
  • Current situation display (batter, pitcher, runners)
  • Play-by-play feed component
  • Game state synchronization logic
  • Real-time WebSocket updates
  • Mobile-responsive layout
  • Loading and error states
  • Unit tests for all components and state sync logic

Key Components:

components/Game/
  ├── GameBoard.vue       # Baseball diamond visualization
  ├── ScoreBoard.vue      # Score + inning + count
  ├── CurrentBatter.vue   # Batter card display
  ├── CurrentPitcher.vue  # Pitcher card display
  ├── RunnersDisplay.vue  # Base runners visual
  ├── PlayByPlay.vue      # Play history feed
  └── GameStatus.vue      # Game status indicator

components/UI/
  ├── PlayerCard.vue      # Simple SBA player card
  ├── LoadingSpinner.vue  # Loading state
  └── ErrorAlert.vue      # Error display

Acceptance Criteria:

  • Game state displays correctly from backend
  • WebSocket updates reflected in real-time (< 200ms)
  • Baseball diamond shows runners accurately
  • Play-by-play updates smoothly
  • Responsive on mobile (portrait mode)
  • Handles connection loss gracefully
  • Unit tests passing for all components (10+ tests)

Phase F3: Decision Input Workflow (Week 3)

Goal: Enable players to make strategic decisions during gameplay

Deliverables:

  • Defensive positioning input
  • Pitcher focus input
  • Stolen base attempt inputs
  • Offensive approach selection
  • Decision validation logic
  • Turn indicator (whose turn)
  • Decision timeout warnings
  • Mobile-friendly input forms
  • Unit tests for decision components and validation logic

Key Components:

components/Decisions/
  ├── DefensiveSetup.vue        # Positioning selection
  ├── PitcherFocusInput.vue     # Pitcher focus options
  ├── StolenBaseInputs.vue      # Per-runner SB attempts
  ├── OffensiveApproach.vue     # Swing/bunt/hit-and-run
  ├── DecisionPanel.vue         # Container for decision UI
  └── TurnIndicator.vue         # Whose turn indicator

components/UI/
  ├── ActionButton.vue          # Primary action button
  ├── ButtonGroup.vue           # Radio-style button group
  └── DecisionTimer.vue         # Countdown for decisions

WebSocket Integration:

// composables/useGameActions.ts
export const useGameActions = () => {
  const { socket } = useWebSocket()

  const setDefense = async (positioning: string) => {
    socket.emit('set_defense', {
      game_id: gameId.value,
      positioning
    })
  }

  const setOffensiveApproach = async (approach: string) => {
    socket.emit('set_offensive_approach', {
      game_id: gameId.value,
      approach
    })
  }

  // ... other actions
}

Acceptance Criteria:

  • All decision inputs emit correct WebSocket events
  • Backend validates and accepts decisions
  • Mobile touch targets are 44x44px minimum
  • Decision UI shows when it's player's turn
  • Clear feedback when waiting for opponent
  • Timeout warnings display appropriately
  • Unit tests passing for decision validation and components (8+ tests)

Phase F4: Manual Outcome Workflow (Week 4)

Goal: Implement dice rolling and outcome reading interface

Deliverables:

  • Dice roll button and animation
  • Roll result display
  • Outcome input form (hit type, bases, runs)
  • Play result submission
  • Dice roll history display
  • Manual workflow state management
  • Error handling for invalid outcomes
  • Mobile-optimized outcome input
  • Unit tests for manual workflow and outcome validation

Key Components:

components/Manual/
  ├── DiceRollButton.vue       # Request roll button
  ├── DiceRollDisplay.vue      # Roll result with animation
  ├── OutcomeInputForm.vue     # Manual outcome entry
  ├── OutcomePreview.vue       # Preview before submit
  └── RollHistory.vue          # Recent rolls display

components/UI/
  ├── DiceAnimation.vue        # Animated dice roll
  ├── NumberInput.vue          # Numeric input (bases, runs)
  └── OutcomeSelect.vue        # Outcome type dropdown

WebSocket Flow:

// 1. Request dice roll
socket.emit('request_manual_roll', { game_id })

// 2. Receive roll result
socket.on('manual_roll_ready', (data) => {
  // Display: d6_1=3, d6_2a=4, d6_2b=2, d20_chaos=15, d20_resolution=8
  showRollResult(data)
})

// 3. Await outcome input
socket.on('awaiting_outcome_input', (prompt) => {
  // Show form: hit type, bases advanced, runs scored
  showOutcomeForm(prompt)
})

// 4. Submit outcome
socket.emit('submit_manual_outcome', {
  game_id,
  outcome_data: {
    hit_type: 'SINGLE_1',
    bases_advanced: {...},
    runs_scored: 1,
    outs_recorded: 0
  }
})

// 5. Play completed
socket.on('play_completed', (result) => {
  // Update game state, show result
  updateGameState(result)
})

Acceptance Criteria:

  • Dice roll request triggers backend roll generation
  • Roll results display with clear visual feedback
  • Outcome form validates input before submission
  • Play result reflects in game state immediately
  • Mobile form input is intuitive (large touch targets)
  • Error messages clear when invalid outcome submitted
  • Unit tests passing for workflow and validation (8+ tests)

Phase F5: Substitutions & Advanced Actions (Week 5)

Goal: Enable player substitutions and pitching changes

Deliverables:

  • Pinch hitter substitution UI
  • Pinch runner substitution UI
  • Defensive replacement UI
  • Pitching change interface
  • Bench/bullpen display
  • Substitution validation
  • Lineup position display
  • Player availability indicators
  • Unit tests for substitution logic and components

Key Components:

components/Substitutions/
  ├── SubstitutionModal.vue      # Main substitution interface
  ├── PinchHitterSelect.vue      # Select pinch hitter
  ├── PinchRunnerSelect.vue      # Select pinch runner
  ├── DefensiveReplacementSelect.vue # Defensive sub
  ├── PitchingChangeModal.vue    # Bullpen selection
  ├── BenchDisplay.vue           # Available bench players
  ├── BullpenDisplay.vue         # Available pitchers
  └── LineupDisplay.vue          # Current lineup status

components/UI/
  ├── PlayerList.vue             # Scrollable player list
  ├── PlayerAvailability.vue     # Availability indicator
  └── Modal.vue                  # Reusable modal component

WebSocket Integration:

// Pinch hitter
socket.emit('substitute_player', {
  game_id,
  substitution_type: 'pinch_hitter',
  lineup_spot_id: 123,
  new_player_id: 456,
  batting_order_position: 3
})

// Pitching change
socket.emit('change_pitcher', {
  game_id,
  new_pitcher_lineup_id: 789
})

// Defensive replacement
socket.emit('substitute_player', {
  game_id,
  substitution_type: 'defensive_replacement',
  lineup_spot_id: 234,
  new_player_id: 567,
  position: '2B'
})

Acceptance Criteria:

  • Substitution modals display available players
  • Backend validates substitution eligibility
  • Lineup updates reflect substitution immediately
  • Substituted players marked as inactive
  • Mobile interface allows easy player selection
  • Clear indication of player availability
  • Unit tests passing for substitution validation (8+ tests)

Phase F6: Game Management & History (Week 6)

Goal: Complete game lifecycle from creation to history

Deliverables:

  • Game creation form
  • Game lobby/pre-game interface
  • Active games list
  • Completed games history
  • Game detail view (spectator-style)
  • Play-by-play history export
  • Game search and filtering
  • Mobile-optimized lists
  • Unit tests for game management and API integration

Key Pages:

pages/
  ├── games/
  │   ├── create.vue          # Create new game form
  │   ├── lobby/[id].vue      # Pre-game lobby
  │   ├── active.vue          # List of active games
  │   ├── history.vue         # Completed games list
  │   └── [id]/
  │       ├── index.vue       # Live game view
  │       ├── history.vue     # Play-by-play history
  │       └── stats.vue       # Box score stats
  └── spectate/
      └── [id].vue            # Spectator view

Key Components:

components/GameManagement/
  ├── GameCreationForm.vue    # Create game form
  ├── GameLobby.vue           # Pre-game interface
  ├── GamesList.vue           # List of games
  ├── GameCard.vue            # Game summary card
  ├── GameFilters.vue         # Filter controls
  └── PlayByPlayHistory.vue   # Full play history

components/Stats/
  ├── BoxScore.vue            # Full box score display
  ├── BattingStats.vue        # Batting statistics table
  ├── PitchingStats.vue       # Pitching statistics table
  └── GameSummary.vue         # Game summary card

REST API Integration:

// composables/useGameApi.ts
export const useGameApi = () => {
  const api = useApi()

  const createGame = async (gameData: CreateGameRequest) => {
    return await api.post('/api/games', gameData)
  }

  const getActiveGames = async () => {
    return await api.get('/api/games/active')
  }

  const getGameHistory = async (filters?: GameFilters) => {
    return await api.get('/api/games/completed', { params: filters })
  }

  const getBoxScore = async (gameId: string) => {
    return await api.get(`/api/games/${gameId}/box_score`)
  }
}

Acceptance Criteria:

  • Users can create new games with configuration
  • Game lobby shows both lineups before start
  • Active games list updates in real-time
  • Game history loads with pagination
  • Box scores display correctly after games
  • Mobile lists scroll smoothly
  • Search and filter work correctly
  • Unit tests passing for game API and components (10+ tests)

Phase F7: Spectator Mode (Week 7)

Goal: Enable real-time spectating of active games

Deliverables:

  • Spectator view (read-only game interface)
  • Spectator WebSocket connection
  • Spectator count display
  • Join spectator mode from games list
  • Spectator-specific UI elements
  • Real-time updates for spectators
  • Mobile-optimized spectator view
  • Unit tests for spectator mode logic

Key Features:

pages/spectate/
  └── [id].vue               # Spectator game view

components/Spectator/
  ├── SpectatorGameView.vue  # Read-only game display
  ├── SpectatorIndicator.vue # "Spectating" badge
  ├── SpectatorCount.vue     # Number of spectators
  └── SpectatorControls.vue  # Leave, fullscreen, etc.

WebSocket Integration:

// Join as spectator
socket.emit('join_game', {
  game_id,
  role: 'spectator'
})

// Receive same updates as players
socket.on('game_state_update', (state) => {})
socket.on('play_completed', (play) => {})
socket.on('inning_change', (inning) => {})

// Spectator-specific events
socket.on('spectator_joined', (count) => {})
socket.on('spectator_left', (count) => {})

Acceptance Criteria:

  • Spectators can view active games in real-time
  • No decision inputs shown to spectators
  • Spectator count visible to players (optional)
  • Updates arrive with same latency as players
  • Mobile spectator view clear and readable
  • Can spectate multiple games (browser tabs)
  • Unit tests passing for spectator permissions (5+ tests)

Phase F8: Polish & Animation (Week 8)

Goal: Enhance UX with animations, transitions, and polish

Deliverables:

  • Dice roll animation (3D or 2D)
  • Play result animations
  • Page transitions
  • Loading skeletons
  • Toast notifications
  • Error state illustrations
  • Confetti for home runs (optional)
  • Sound effects (optional)
  • Unit tests for animation triggers and timing

Key Components:

components/Animations/
  ├── DiceRoll3D.vue          # 3D dice animation
  ├── PlayResultAnimation.vue # Hit/out animations
  ├── RunnerMovement.vue      # Animated base running
  ├── ScoreIncrement.vue      # Score change animation
  └── Confetti.vue            # Home run celebration

components/UI/
  ├── SkeletonLoader.vue      # Loading skeleton
  ├── Toast.vue               # Toast notification
  ├── Transition.vue          # Page transition wrapper
  └── EmptyState.vue          # Empty state illustration

Animation Libraries (potential):

// Consideration: Add animation libraries
// - @vueuse/motion - Vue animation utilities
// - gsap - Advanced animations (dice roll)
// - lottie-vue - Lottie animations

Acceptance Criteria:

  • Dice roll feels satisfying and realistic
  • Play results animate smoothly
  • Transitions enhance UX without delay
  • Loading states prevent confusion
  • Toasts provide clear feedback
  • Mobile animations perform well (60fps)
  • Unit tests passing for animation logic (5+ tests)

Phase F9: Performance Optimization (Week 9)

Goal: Optimize for fast load times and smooth interactions

Deliverables:

  • Code splitting optimization
  • Lazy loading for heavy components
  • Image optimization (player cards)
  • Bundle size analysis and reduction
  • Lighthouse performance audit
  • WebSocket message throttling
  • State update debouncing
  • Memory leak prevention
  • E2E tests with Playwright (full user flows)
  • Performance benchmarks and automated audits

Optimization Tasks:

// 1. Lazy load heavy components
const GameBoard = defineAsyncComponent(() =>
  import('~/components/Game/GameBoard.vue')
)

// 2. Throttle WebSocket updates
const throttledUpdate = useThrottle((state) => {
  gameStore.setGameState(state)
}, 100)

// 3. Memoize expensive computations
const scoreDiff = computed(() => {
  return gameState.value.home_score - gameState.value.away_score
})

// 4. Virtual scrolling for long lists
<VirtualList :items="playHistory" :item-height="60">
  <template #default="{ item }">
    <PlayItem :play="item" />
  </template>
</VirtualList>

Performance Targets:

  • Initial page load: < 3 seconds on 3G
  • Time to interactive: < 5 seconds
  • Lighthouse score: > 90
  • Bundle size: < 500KB (gzipped)
  • WebSocket latency: < 200ms
  • Action response: < 500ms
  • Memory usage: < 100MB sustained

Acceptance Criteria:

  • Lighthouse audit passes all core metrics
  • Bundle size reduced by 30%+ from baseline
  • No memory leaks detected
  • Smooth scrolling on mobile (60fps)
  • WebSocket updates don't block UI
  • E2E tests passing for critical user flows (10+ tests)
  • Performance benchmarks meet or exceed targets

Mobile-First Design Principles

Layout Strategy

Portrait Mobile (375px - 640px):

<template>
  <div class="mobile-game-view">
    <!-- Sticky header: Score + inning -->
    <div class="sticky top-0 z-20 bg-white shadow-lg">
      <ScoreBoard compact />
    </div>

    <!-- Main content: Single column -->
    <div class="flex flex-col gap-4 p-4">
      <!-- Baseball diamond -->
      <GameBoard size="compact" />

      <!-- Current situation -->
      <CurrentSituation mobile />

      <!-- Decision panel (when active) -->
      <DecisionPanel v-if="isMyTurn" mobile />

      <!-- Play-by-play (collapsible) -->
      <Disclosure title="Recent Plays">
        <PlayByPlay :limit="5" />
      </Disclosure>
    </div>

    <!-- Bottom navigation (optional) -->
    <div class="fixed bottom-0 w-full">
      <BottomNav />
    </div>
  </div>
</template>

Desktop (1024px+):

<template>
  <div class="desktop-game-view">
    <div class="grid grid-cols-2 gap-6 p-6">
      <!-- Left column: Game state -->
      <div class="flex flex-col gap-4">
        <ScoreBoard detailed />
        <GameBoard size="large" />
        <CurrentSituation expanded />
      </div>

      <!-- Right column: Actions + history -->
      <div class="flex flex-col gap-4">
        <DecisionPanel v-if="isMyTurn" />
        <PlayByPlay infinite-scroll />
        <GameStats />
      </div>
    </div>
  </div>
</template>

Touch Interaction Guidelines

  1. Minimum Touch Targets: 44x44px for all interactive elements
  2. Swipe Gestures:
    • Swipe left/right to navigate between plays
    • Pull to refresh game state
  3. Bottom Sheets: Use for modals on mobile (easier one-handed use)
  4. Haptic Feedback: Vibrate on important actions (optional)
  5. Avoid Hover: Don't rely on hover states for functionality

Type Safety & API Contracts

Core Type Definitions

// types/game.ts
export interface GameState {
  game_id: string
  league_id: string
  status: 'pending' | 'active' | 'completed'

  // Game situation
  inning: number
  half: 'top' | 'bottom'
  outs: number
  balls: number
  strikes: number

  // Score
  home_score: number
  away_score: number

  // Runners (lineup IDs)
  runners: {
    first: number | null
    second: number | null
    third: number | null
  }

  // Current players
  current_batter_lineup_id: number | null
  current_pitcher_lineup_id: number | null

  // Decisions
  defensive_positioning: string | null
  pitcher_focus: string | null
  stolen_base_attempts: Record<string, boolean>
  offensive_approach: string | null

  // Teams
  home_team_id: number
  away_team_id: number
}

export interface Lineup {
  id: number
  game_id: string
  team_id: number
  card_id: number
  position: string
  batting_order: number | null
  is_starter: boolean
  is_active: boolean
  player: SbaPlayer
}

export interface SbaPlayer {
  id: number
  name: string
  image: string
  team?: string
  manager?: string
}

export interface PlayResult {
  play_number: number
  outcome: string
  description: string
  runs_scored: number
  outs_recorded: number
  new_state: Partial<GameState>
}

export interface DecisionPrompt {
  phase: 'defense' | 'stolen_base' | 'offensive_approach' | 'manual_outcome'
  role: 'home' | 'away'
  timeout_seconds: number
  options?: string[]
}

export interface RollData {
  roll_id: string
  d6_one: number
  d6_two_a: number
  d6_two_b: number
  chaos_d20: number
  resolution_d20: number
}

API Response Types

// types/api.ts
export interface CreateGameRequest {
  league_id: string
  home_team_id: number
  away_team_id: number
  game_mode: 'live' | 'async' | 'vs_ai'
  visibility: 'public' | 'private'
}

export interface CreateGameResponse {
  game_id: string
  status: string
  created_at: string
}

export interface GameListItem {
  game_id: string
  home_team_id: number
  away_team_id: number
  status: string
  current_inning: number
  home_score: number
  away_score: number
  started_at: string
}

export interface BoxScoreResponse {
  game_stats: {
    home_runs: number
    away_runs: number
    home_hits: number
    away_hits: number
    linescore_home: number[]
    linescore_away: number[]
  }
  batting_stats: BattingStatLine[]
  pitching_stats: PitchingStatLine[]
}

WebSocket Event Types

// types/websocket.ts
export type SocketEventMap = {
  // Client → Server
  'join_game': (data: { game_id: string, role: 'home' | 'away' | 'spectator' }) => void
  'set_defense': (data: { game_id: string, positioning: string }) => void
  'set_pitcher_focus': (data: { game_id: string, focus: string }) => void
  'set_stolen_base_attempt': (data: { game_id: string, runner: string, attempt: boolean }) => void
  'set_offensive_approach': (data: { game_id: string, approach: string }) => void
  'request_manual_roll': (data: { game_id: string }) => void
  'submit_manual_outcome': (data: { game_id: string, outcome_data: OutcomeData }) => void

  // Server → Client
  'game_state_update': (state: GameState) => void
  'decision_required': (decision: DecisionPrompt) => void
  'play_completed': (play: PlayResult) => void
  'manual_roll_ready': (roll: RollData) => void
  'awaiting_outcome_input': (prompt: OutcomePrompt) => void
  'inning_change': (data: { inning: number, half: string }) => void
  'game_ended': (result: GameResult) => void
  'invalid_action': (error: ActionError) => void
  'connection_error': (error: ConnectionError) => void
}

Testing Strategy

Philosophy: Unit tests are written in each phase alongside feature development, not deferred to Phase F9. This ensures immediate feedback, prevents regressions, and maintains code quality throughout the project.

Unit Testing (Phases F1-F8)

Each phase includes comprehensive unit tests for all new code:

# Run all unit tests
npm run test

# Watch mode for development
npm run test:watch

# Coverage report
npm run test:coverage

# Target: > 80% coverage

What to Test Each Phase:

  • Composables: Logic, validation, error handling, state management
  • Stores: State mutations, computed properties, actions, persistence
  • Components: Props, emits, rendering, user interactions
  • Utilities: Pure functions, formatters, validators

Testing Tools:

  • Vitest: Fast unit test runner with Vue support
  • @vue/test-utils: Official Vue testing library
  • happy-dom: Lightweight DOM implementation

Example Component Test:

import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import ScoreBoard from '~/components/Game/ScoreBoard.vue'

describe('ScoreBoard', () => {
  it('displays correct score', () => {
    const wrapper = mount(ScoreBoard, {
      props: {
        homeScore: 5,
        awayScore: 3,
        inning: 7,
        half: 'bottom'
      }
    })

    expect(wrapper.text()).toContain('5')
    expect(wrapper.text()).toContain('3')
    expect(wrapper.text()).toContain('Bottom 7')
  })
})

E2E Testing (Phase F9)

End-to-end tests validate complete user flows across the entire application:

# E2E tests with Playwright
npm run test:e2e

# Headed mode (see browser)
npm run test:e2e:headed

# Specific browser
npm run test:e2e --project=chromium

# Test scenarios:
# - Full game flow (create → play → complete)
# - WebSocket reconnection and recovery
# - Mobile responsive behavior
# - Multi-user synchronization
# - Authentication flow

Example E2E Test:

import { test, expect } from '@playwright/test'

test('can create and join game', async ({ page }) => {
  // Login
  await page.goto('/auth/login')
  await page.click('button:has-text("Login with Discord")')

  // Create game
  await page.goto('/games/create')
  await page.selectOption('#home-team', '123')
  await page.selectOption('#away-team', '456')
  await page.click('button:has-text("Create Game")')

  // Verify game view loaded
  await expect(page.locator('.game-board')).toBeVisible()
})

Manual Testing Checklist

Mobile Testing (Real Devices):

  • iPhone 12/13/14 (Safari)
  • Samsung Galaxy S21/S22 (Chrome)
  • iPad (Safari)
  • Various screen sizes (375px - 768px)

Desktop Testing:

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Network Conditions:

  • Fast 3G
  • Slow 3G
  • Offline behavior
  • Connection loss/recovery

Development Guidelines

Code Quality Standards

Vue/TypeScript Best Practices:

<!--  Good: Composition API with TypeScript -->
<script setup lang="ts">
interface Props {
  gameId: string
  isActive: boolean
}

const props = defineProps<Props>()
const emit = defineEmits<{
  update: [value: string]
}>()

const localState = ref('')
const displayValue = computed(() =>
  props.isActive ? 'Active' : 'Inactive'
)
</script>

<!--  Bad: Options API without types -->
<script>
export default {
  props: ['gameId', 'isActive'],
  data() {
    return { localState: '' }
  }
}
</script>

Composable Best Practices:

// ✅ Good: Returns reactive refs/computeds
export const useGameState = (gameId: Ref<string>) => {
  const state = ref<GameState | null>(null)
  const isLoading = ref(false)

  const currentInning = computed(() =>
    state.value?.inning ?? 1
  )

  const loadState = async () => {
    isLoading.value = true
    // ... fetch state
    isLoading.value = false
  }

  return {
    state: readonly(state),
    isLoading: readonly(isLoading),
    currentInning,
    loadState
  }
}

// ❌ Bad: Returns non-reactive values
export const useGameState = (gameId: string) => {
  let state = null // Not reactive!
  return { state }
}

Store Best Practices:

// ✅ Good: Composition API store with types
export const useGameStore = defineStore('game', () => {
  const state = ref<GameState | null>(null)

  const currentInning = computed(() => state.value?.inning ?? 1)

  const setState = (newState: GameState) => {
    state.value = newState
  }

  return {
    state: readonly(state),
    currentInning,
    setState
  }
})

// ❌ Bad: Options API store without types
export const useGameStore = defineStore('game', {
  state: () => ({ state: null }),
  actions: {
    setState(state) { this.state = state }
  }
})

Git Workflow

Branch Strategy:

# Feature branches from main
git checkout -b frontend/auth-flow
git checkout -b frontend/game-board
git checkout -b frontend/decision-inputs

# Commit prefix: "FRONTEND:"
git commit -m "FRONTEND: Implement Discord OAuth login flow"
git commit -m "FRONTEND: Add GameBoard component with responsive layout"

Pull Request Template:

## Changes
- Added Discord OAuth authentication flow
- Created login page and callback handler
- Implemented auth store with Pinia

## Testing
- [x] Tested on mobile (iPhone 13)
- [x] Tested on desktop (Chrome)
- [x] TypeScript compilation passes
- [x] Component tests pass

## Screenshots
[Mobile login screen]
[Desktop login screen]

Deployment & DevOps

Build Process

# Production build
npm run build

# Output: .output/ directory
# Contains:
# - Server files (Nitro)
# - Static assets
# - Pre-rendered pages (if any)

Environment Variables

# .env.production
NUXT_PUBLIC_API_URL=https://api.paperdynasty.com
NUXT_PUBLIC_WS_URL=https://api.paperdynasty.com
NUXT_PUBLIC_DISCORD_CLIENT_ID=<production-client-id>
NUXT_PUBLIC_DISCORD_REDIRECT_URI=https://sba.paperdynasty.com/auth/callback

Hosting Options

Option 1: VPS with Docker

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

Option 2: Vercel/Netlify

  • Zero-config deployment
  • Automatic HTTPS
  • CDN edge caching
  • Preview deployments for PRs

Recommendation: Start with Vercel for simplicity, migrate to VPS if needed for control.


Success Metrics & KPIs

Launch Success (First Month)

Metric Target Measurement
Player Migration 90% Users active on web vs Sheets
Mobile Usage 60%+ Mobile vs desktop sessions
Action Latency < 500ms p95 WebSocket response time
Page Load Time < 3s Lighthouse audit on 3G
System Uptime 99.5% Uptime monitoring
Critical Bugs 0 Zero rollback-required bugs

User Experience (Ongoing)

Metric Target Measurement
SUS Score > 75 User survey (10 questions)
Net Promoter Score > 50 "How likely to recommend?"
Game Completion Rate > 90% Games completed vs started
Average Session 30-60 min Analytics tracking
Support Tickets < 5/week Support system

Technical Performance (Ongoing)

Metric Target Measurement
Lighthouse Score > 90 Automated audit
Bundle Size < 500KB Build analysis
WebSocket Latency < 200ms Network monitoring
Memory Usage < 100MB Browser DevTools
Error Rate < 0.1% Error tracking (Sentry)

Risk Mitigation

Technical Risks

Risk Mitigation
WebSocket disconnections Auto-reconnect with exponential backoff, state recovery from backend
Type mismatches with backend Generate types from backend OpenAPI spec, integration tests
Mobile performance issues Performance budget, bundle size limits, lazy loading
Browser compatibility Polyfills, feature detection, graceful degradation
State desynchronization Periodic state refresh, conflict resolution

User Experience Risks

Risk Mitigation
Confusing UI for Sheets users Onboarding tutorial, help documentation, familiar terminology
Mobile input difficulties Large touch targets, mobile-first design, user testing
Slow adoption Beta testing period, migration guide, user training sessions
Connection issues misunderstood Clear connection status indicator, helpful error messages

Next Steps

Immediate Actions (This Session)

  1. Review Backend WebSocket Specifications

    • Read backend/app/websocket/handlers.py
    • Review event payloads and responses
    • Understand decision workflow sequences
  2. Create Core Type Definitions

    • Start with types/game.ts
    • Match backend Pydantic models exactly
    • Add JSDoc comments for clarity
  3. Set Up Basic Auth Flow

    • Create Discord OAuth configuration
    • Implement login page
    • Test callback handling
  4. Initialize Pinia Stores

    • Auth store with Discord user
    • UI store for modals/toasts
    • Game store skeleton
  5. Create First Component

    • Simple ScoreBoard component
    • Test with mock data
    • Ensure TypeScript types work

Week 1 Priorities

  • Complete Phase F1 (Core Infrastructure)
  • WebSocket connection working
  • Type system fully defined
  • Auth flow functional
  • Basic routing in place

Long-Term Milestones

  • Week 2: Phase F2 complete (Game state display working)
  • Week 3: Phase F3 complete (Decision inputs functional)
  • Week 4: Phase F4 complete (Manual workflow end-to-end)
  • Week 5: Phase F5 complete (Substitutions working)
  • Week 6: Phase F6 complete (Full game lifecycle)
  • Week 7: Phase F7 complete (Spectator mode)
  • Week 8: Phase F8 complete (Polish & animations)
  • Week 9: Phase F9 complete (Performance optimized)

Target Launch: 9-10 weeks from start (Beta), +2 weeks for production hardening


Appendix

Useful Resources

Nuxt 3:

Vue 3:

Socket.io:

Tailwind CSS:

Backend API Reference

REST Endpoints:

POST   /api/games              # Create new game
GET    /api/games/:id          # Get game details
GET    /api/games/active       # List active games
GET    /api/games/completed    # List completed games
GET    /api/games/:id/box_score # Get box score

WebSocket Events (Full list in backend docs):

  • Connection: join_game, leave_game
  • Decisions: set_defense, set_stolen_base_attempt, set_offensive_approach
  • Manual: request_manual_roll, submit_manual_outcome
  • Substitutions: substitute_player, change_pitcher

Document Version: 1.0 Last Updated: 2025-01-10 Next Review: After Phase F1 completion