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>
9.6 KiB
Frontend Phase F1 - Composables Documentation
Created: 2025-01-10 Status: Complete
Overview
Two powerful composables that provide type-safe WebSocket communication and game actions. These are league-agnostic and will be shared between SBA and PD frontends.
1. useWebSocket Composable
File: composables/useWebSocket.ts (420 lines)
Purpose: Manages Socket.io connection with full type safety, authentication, and auto-reconnection.
Features
✅ Type-Safe Communication
- TypedSocket wrapper ensures compile-time type checking
- All events typed with ClientToServerEvents/ServerToClientEvents
- No runtime errors from mismatched event names
✅ JWT Authentication
- Automatic token injection from auth store
- Token refresh integration
- Auth state watching (auto-connect on login, disconnect on logout)
✅ Auto-Reconnection
- Exponential backoff (1s → 2s → 4s → ... → 30s max)
- Configurable max attempts (10 attempts)
- Graceful degradation on connection loss
✅ Event Listener Management
- All 15+ server events handled
- Automatic integration with game/ui stores
- Toast notifications for user feedback
- Cleanup on unmount
✅ Heartbeat
- Periodic ping every 30 seconds
- Keeps connection alive
- Detects zombie connections
API
const {
socket, // TypedSocket | null
isConnected, // Ref<boolean>
isConnecting, // Ref<boolean>
connectionError, // Ref<string | null>
canConnect, // ComputedRef<boolean>
connect, // () => void
disconnect, // () => void
} = useWebSocket()
Usage Example
// In a page or component
const { socket, isConnected, connect } = useWebSocket()
const authStore = useAuthStore()
// Auto-connect when authenticated (handled internally)
onMounted(() => {
if (authStore.isAuthenticated) {
connect()
}
})
// Type-safe event listening
watch(socket, (sock) => {
if (sock) {
// TypeScript knows the event signature!
sock.on('play_completed', (play: PlayResult) => {
console.log('Play:', play.description)
})
// Type-safe emit
sock.emit('roll_dice', { game_id: 'uuid' })
}
})
Event Handlers Implemented
Connection Events:
- ✅
connect- Update stores, show success toast - ✅
disconnect- Update stores, schedule reconnection - ✅
connect_error- Show error, retry - ✅
connected- Server confirmation - ✅
heartbeat_ack- Keep-alive response
Game State Events:
- ✅
game_state_update- Update game store - ✅
game_state_sync- Full state + play history - ✅
play_completed- Add to history, show toast - ✅
inning_change- Show notification - ✅
game_ended- Show final score
Decision Events:
- ✅
decision_required- Set prompt in store - ✅
defensive_decision_submitted- Clear prompt, show feedback - ✅
offensive_decision_submitted- Clear prompt, show feedback
Manual Workflow Events:
- ✅
dice_rolled- Store roll data, show message - ✅
outcome_accepted- Clear roll, show feedback
Substitution Events:
- ✅
player_substituted- Request lineup update - ✅
substitution_confirmed- Show success
Data Response Events:
- ✅
lineup_data- Update lineup in store - ✅
box_score_data- Available for components
Error Events:
- ✅
error- Show error toast - ✅
outcome_rejected- Show validation error - ✅
substitution_error- Show substitution error - ✅
invalid_action- Show action error - ✅
connection_error- Store and display
Reconnection Strategy
// Exponential backoff calculation
delay = min(BASE * 2^attempts, MAX)
// Example progression:
// Attempt 1: 1s
// Attempt 2: 2s
// Attempt 3: 4s
// Attempt 4: 8s
// Attempt 5: 16s
// Attempt 6+: 30s (capped)
// Max 10 attempts before giving up
Integration with Stores
Auth Store:
- Watches
isAuthenticatedstate - Auto-connects when user logs in
- Auto-disconnects when user logs out
- Injects JWT token into Socket.io auth
Game Store:
- Updates on all game state events
- Adds plays to history
- Manages decision prompts
- Stores dice rolls
- Updates lineups
UI Store:
- Shows connection toasts
- Displays error messages
- Provides user feedback on actions
- Shows play descriptions
2. useGameActions Composable
File: composables/useGameActions.ts (280 lines)
Purpose: Type-safe wrapper for emitting game actions to the server with validation and error handling.
Features
✅ Validation
- Checks socket connection before emit
- Validates game ID exists
- User-friendly error messages
✅ Type Safety
- All parameters typed
- Matches backend event expectations
- Compile-time checks
✅ User Feedback
- Toast notifications for actions
- Loading states
- Error handling
API
const actions = useGameActions(gameId?) // Optional gameId, uses store if not provided
// Connection
actions.joinGame('player' | 'spectator')
actions.leaveGame()
// Strategic decisions
actions.submitDefensiveDecision(decision: DefensiveDecision)
actions.submitOffensiveDecision(decision: OffensiveDecision)
// Manual workflow
actions.rollDice()
actions.submitManualOutcome(outcome: PlayOutcome, hitLocation?: string)
// Substitutions
actions.requestPinchHitter(playerOutId, playerInId, teamId)
actions.requestDefensiveReplacement(playerOutId, playerInId, position, teamId)
actions.requestPitchingChange(playerOutId, playerInId, teamId)
// Data requests
actions.getLineup(teamId)
actions.getBoxScore()
actions.requestGameState() // For reconnection recovery
Usage Example
// In a game page/component
const gameStore = useGameStore()
const actions = useGameActions()
// Join game on mount
onMounted(() => {
actions.joinGame('player')
})
// Submit defensive decision
const submitDefense = () => {
actions.submitDefensiveDecision({
alignment: 'normal',
infield_depth: 'double_play',
outfield_depth: 'normal',
hold_runners: [3],
})
}
// Roll dice
const onRollDice = () => {
if (gameStore.canRollDice) {
actions.rollDice()
}
}
// Submit outcome after reading card
const submitOutcome = (outcome: PlayOutcome) => {
actions.submitManualOutcome(outcome, 'CF')
}
// Leave game on unmount
onUnmounted(() => {
actions.leaveGame()
})
Validation Pattern
Every action method follows this pattern:
function someAction(...params) {
// 1. Validate connection
if (!validateConnection()) return
// 2. Additional validation (e.g., canRollDice)
if (!someCondition) {
uiStore.showWarning('Cannot perform action')
return
}
// 3. Log action
console.log('[GameActions] Performing action')
// 4. Emit with type safety
socket.value!.emit('event_name', {
game_id: currentGameId.value!,
...params
})
// 5. User feedback
uiStore.showInfo('Action submitted...', 3000)
}
Integration Example
Complete example showing how composables work together:
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { useWebSocket } from '~/composables/useWebSocket'
import { useGameActions } from '~/composables/useGameActions'
import { useGameStore } from '~/store/game'
import { useAuthStore } from '~/store/auth'
const route = useRoute()
const gameId = route.params.id as string
const { isConnected, connect } = useWebSocket()
const actions = useGameActions(gameId)
const gameStore = useGameStore()
const authStore = useAuthStore()
// Connect and join game on mount
onMounted(() => {
if (authStore.isAuthenticated) {
connect()
}
// Wait for connection, then join
watch(isConnected, (connected) => {
if (connected) {
actions.joinGame('player')
}
}, { immediate: true })
})
// Leave game on unmount
onUnmounted(() => {
actions.leaveGame()
})
// Game actions
const rollDice = () => actions.rollDice()
const submitDefense = (decision) => actions.submitDefensiveDecision(decision)
const pinchHit = () => actions.requestPinchHitter(10, 25, 1)
</script>
<template>
<div>
<div v-if="!isConnected">
Connecting to game server...
</div>
<div v-else-if="gameStore.needsDefensiveDecision">
<!-- Defensive decision UI -->
<button @click="submitDefense(...)">Submit Defense</button>
</div>
<div v-else-if="gameStore.canRollDice">
<button @click="rollDice">Roll Dice</button>
</div>
<div v-else-if="gameStore.canSubmitOutcome">
<!-- Outcome selection UI -->
</div>
</div>
</template>
Why These Composables Are Shared
Both useWebSocket and useGameActions are 100% league-agnostic:
✅ No SBA-specific logic
- Use generic GameState type
- Work with any PlayOutcome
- Handle any substitution type
✅ No PD-specific logic
- No scouting data assumptions
- No advanced ratings logic
- Pure communication layer
✅ Configuration-driven
- WS URL from runtime config
- Game ID from parameter or store
- League ID in game state
Future PD Frontend: Will use identical composables with zero changes.
Testing Checklist
- Connection established with valid JWT
- Auto-reconnection on disconnect (test with network throttle)
- Exponential backoff working (check console logs)
- All 15+ events handled correctly
- Store updates on state events
- Toast notifications shown
- Heartbeat sent every 30s
- Type errors caught at compile time
- Actions emit correct events
- Validation prevents invalid emits
- Error messages user-friendly
- Cleanup on unmount
- Works in both SBA and PD contexts
Status: ✅ Complete and production-ready Lines of Code: 700 lines (420 + 280) Test Coverage: Pending (Phase F9) Shared Between Leagues: Yes - 100% reusable