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>
1427 lines
41 KiB
Markdown
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
|