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

1427 lines
41 KiB
Markdown

# 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)**:
```typescript
// 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)**:
```typescript
// 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**:
```typescript
// 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**:
```typescript
// 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**:
```typescript
// 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**:
```typescript
// 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**:
```typescript
// 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):
```typescript
// 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**:
```typescript
// 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)**:
```vue
<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+)**:
```vue
<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
```typescript
// 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
```typescript
// 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
```typescript
// 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:
```bash
# 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**:
```typescript
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:
```bash
# 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**:
```typescript
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**:
```vue
<!-- 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**:
```typescript
// ✅ 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**:
```typescript
// ✅ 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**:
```bash
# 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**:
```markdown
## 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
```bash
# Production build
npm run build
# Output: .output/ directory
# Contains:
# - Server files (Nitro)
# - Static assets
# - Pre-rendered pages (if any)
```
### Environment Variables
```bash
# .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
# 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**:
- Documentation: https://nuxt.com/docs
- Examples: https://nuxt.com/docs/examples
- Modules: https://nuxt.com/modules
**Vue 3**:
- Composition API: https://vuejs.org/guide/extras/composition-api-faq.html
- TypeScript: https://vuejs.org/guide/typescript/overview.html
- Best Practices: https://vuejs.org/style-guide/
**Socket.io**:
- Client docs: https://socket.io/docs/v4/client-api/
- TypeScript: https://socket.io/docs/v4/typescript/
**Tailwind CSS**:
- Documentation: https://tailwindcss.com/docs
- Customization: https://tailwindcss.com/docs/configuration
### 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