After max reconnection attempts (10), the stuck state detector was
continuing to retry forever. Now:
- Add permanentlyFailed state flag to track when we've given up
- Set flag and stop stuck state detector when max attempts reached
- Add manualRetry() method for UI to reset state and try again
- Expose permanentlyFailed in public API for error boundary (HIGH-001)
Also marks CRIT-002 and CRIT-002-BACKEND as completed in project plan.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit Pinia store imports to layouts/game.vue. Nuxt 4 requires
explicit imports for stores - auto-imports no longer work.
This fixes: ReferenceError: useAuthStore is not defined
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend:
- Add get_teams_by_owner() to SBA API client
- Update /api/auth/me to return user's teams from SBA API
- Add sba_current_season config setting (default: 13)
Frontend:
- Replace hardcoded myTeamId with computed from auth store teams
- Fix isMyTurn logic to check actual team ownership
- Update REFACTORING_PLAN.json
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add submitSubstitution() method that routes to appropriate internal method
- Remove individual substitution methods from exports (now internal only)
- Add validation for defensive_replacement requiring position parameter
- Update tests to use unified wrapper, add test for validation error
Fixes CRIT-001: Game page called non-existent submitSubstitution method
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Frontend UX improvements:
- Single-click Discord OAuth from home page (no intermediate /auth page)
- Auto-redirect authenticated users from home to /games
- Fixed Nuxt layout system - app.vue now wraps NuxtPage with NuxtLayout
- Games page now has proper card container with shadow/border styling
- Layout header includes working logout with API cookie clearing
Games list enhancements:
- Display team names (lname) instead of just team IDs
- Show current score for each team
- Show inning indicator (Top/Bot X) for active games
- Responsive header with wrapped buttons on mobile
Backend improvements:
- Added team caching to SbaApiClient (1-hour TTL)
- Enhanced GameListItem with team names, scores, inning data
- Games endpoint now enriches response with SBA API team data
Docker optimizations:
- Optimized Dockerfile using --chown flag on COPY (faster than chown -R)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create frontend-sba/.env.example and frontend-pd/.env.example templates
- Fix hardcoded allowedHosts in nuxt.config.ts (now reads NUXT_ALLOWED_HOSTS)
- Add NUXT_ALLOWED_HOSTS support to frontend-pd/nuxt.config.ts
- Update docker-compose.yml with missing env vars:
- FRONTEND_URL, DISCORD_SERVER_REDIRECT_URI
- ALLOWED_DISCORD_IDS, WS_HEARTBEAT_INTERVAL, WS_CONNECTION_TIMEOUT
- NUXT_ALLOWED_HOSTS for both frontends
- Create docker-compose.prod.yml for production overrides
- Update root .env.example with new variables
- Add "Multi-Domain Deployment" section to README.md with checklist
- Update all CLAUDE.md files with environment configuration docs
- Remove obsolete 'version' attribute from docker-compose files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive guide documenting the investigation and resolution of
intermittent WebSocket connection failures:
- NPM configuration issues (HTTP/2, access lists, custom locations)
- Conflicting socket.io plugin (JWT vs cookie auth)
- SSR/hydration state corruption (primary root cause)
- Diagnostic commands and debugging tips
- SSR-safe patterns for WebSocket in Nuxt
- Cloudflare considerations
This document serves as future reference for similar WebSocket issues.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactored useWebSocket.ts to prevent SSR-related connection issues:
- Created ClientState interface to encapsulate all client-only state
- Added getClientState() function with lazy initialization on client only
- Added SSR guards (import.meta.client) to all connection functions
- Reset reactive state on client hydration to ensure clean slate
- Moved heartbeatInterval and stuckStateCheckInterval into ClientState
- Changed NodeJS.Timeout to ReturnType<typeof setTimeout/setInterval>
- Wrapped auth watcher in import.meta.client guard
Root cause: Module-level singleton state was being initialized during
SSR, then persisting in a potentially corrupted state during hydration.
This caused intermittent "Socket exists: no" issues where the browser
wouldn't even attempt WebSocket connections.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Service Scripts:
- start-services.sh: Add pre-flight checks, health monitoring, --dev/--prod modes,
port options, dependency checks, and version logging
- stop-services.sh: Add port 3000 cleanup, verification, --quiet/--force flags
- status-services.sh: New script for monitoring service status with --watch mode
WebSocket:
- Remove conflicting socket.client.ts plugin that was interfering with
useWebSocket.ts composable (used JWT auth vs cookie auth)
- Add debugging logs to useWebSocket.ts to diagnose intermittent connection issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The outs_before field in play records was incorrectly storing the
outs count AFTER _apply_play_result() modified state.outs, rather
than the value before the play resolved.
Fix: Capture state.outs before calling _apply_play_result() and
pass it explicitly to _save_play_to_db() as a required parameter.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When recovering game state after reconnect/refresh, the old code:
1. Only looked at the last play overall (regardless of team)
2. Used that play's batting order for the currently batting team
3. Reset the non-batting team's index to 0
This caused the wrong batter to appear after inning changes - e.g.,
batter #1 showing instead of #5 in top of 2nd after bottom of 1st ended.
Fix: Now recovers each team's batter index separately by filtering
plays by half ("top" = away team, "bottom" = home team) and finding
each team's last at-bat independently.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
After changing runners_advanced from list[tuple] to list[RunnerAdvancementData],
three locations were still trying to unpack as tuples causing "cannot unpack
non-iterable RunnerAdvancementData object" error on triples/doubles with runners.
Fixed locations:
- app/core/game_engine.py:772 - _apply_play_result() advancement map
- app/core/game_engine.py:1081 - _save_play() finals lookup
- terminal_client/display.py:150 - Runner movement display
Also renamed refactor_overview.md to game-engine-play-tracking-design.md
for clarity and updated the example code to use dataclass access pattern.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: WebSocket handler had stale state reference after
resolve_manual_play() completed. The handler's state object still had
the old batter index, and calling state_manager.update_state()
overwrote the game engine's updated state.
Fix: Re-fetch state from state_manager AFTER resolve_manual_play()
returns, ensuring we get the state with the advanced batter index.
Also improved logging in _prepare_next_play() to show batting_order
for easier debugging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Auth middleware was commented out on game detail page
([id].vue), causing SSR to render without checking authentication.
Safari's client-side auth check wasn't reaching the backend due to
caching behavior, resulting in "Auth: Failed" display.
Changes:
- Re-enabled middleware: ['auth'] in pages/games/[id].vue
- Added /api/auth/ws-token endpoint for Safari WebSocket fallback
- Added expires_minutes param to create_token() for short-lived tokens
- Added token query param support to WebSocket handlers
- Updated SAFARI_WEBSOCKET_ISSUE.md documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced O(n²) deduplication check per play with O(1) array replacement:
- Added setPlayHistory() for game_state_sync (replaces entire array)
- Simplified addPlayToHistory() for live play_resolved events (just push)
This separates sync operations (replace) from live events (append),
eliminating the need for deduplication checks during gameplay.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Backend changes:
- Modified request_game_state handler to fetch plays from database
- Convert Play DB models to frontend-compatible PlayResult dicts
- Emit game_state_sync event with state + recent_plays array
Frontend changes:
- Added deduplication by play_number in addPlayToHistory()
- Prevents duplicate plays when game_state_sync is received
Field mapping from Play model:
- hit_type -> outcome
- result_description -> description
- batter_id -> batter_lineup_id
- batter_final -> batter_result
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced PlayByPlay with a tabbed interface:
- "Recent" tab shows plays from current half inning only
- "Scoring" tab shows all plays where runs were scored
- Badge counts on each tab show number of matching plays
- Tab-aware empty states with contextual messaging
- Footer shows total game plays count
Removed unused showFilters prop and showAllPlays toggle.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Lineouts are simple outs with no runner advancement logic - the backend
doesn't use hit_location for them (unlike flyouts for sac flies or
groundballs for double plays). Updated OUTCOMES_REQUIRING_HIT_LOCATION
to exclude lineout and added documentation explaining the rationale.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
New Tests:
- test_exceptions.py: Comprehensive tests for custom GameEngineError
hierarchy including AuthorizationError, DatabaseError, etc.
Scripts:
- stop-services.sh: Enhanced to kill entire process trees and
clean up orphan processes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Auth Improvements:
- auth.ts: Enhanced authentication store with better error handling
- auth.ts middleware: Improved redirect logic
- login.vue: Better login flow and error display
Game Display:
- PlayByPlay.vue: Enhanced play-by-play feed with player name display
- game.ts types: Additional type definitions for runner advancement
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Game Engine Improvements:
- State manager: Add game eviction with configurable max games, idle
timeout, and memory limits
- Game engine: Add resource cleanup on game completion
- Play resolver: Enhanced RunnerAdvancementData with lineup_id for
player name resolution in play-by-play
- Substitution manager: Minor improvements
Test Coverage:
- New test_game_eviction.py with 13 tests for eviction scenarios
- Updated state_manager tests for new functionality
- Updated play_resolver tests for lineup_id handling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add rate_limit.py middleware with per-client throttling and cleanup task
- Add pool_monitor.py for database connection pool health monitoring
- Add custom exceptions module (GameEngineError, DatabaseError, etc.)
- Add config settings for eviction intervals, session timeouts, memory limits
- Add unit tests for rate limiting and pool monitoring
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed cookie SameSite policy from Lax to None with Secure=true
for Safari ITP compatibility
- Fixed Nuxt composable context issue: moved useRuntimeConfig() from
connect() callback to composable setup phase (required in Nuxt 4)
- Added GET /logout endpoint for easy browser-based logout
- Improved loading overlay with clear status indicators and action
buttons (Retry, Re-Login, Dismiss)
- Added error handling with try-catch in WebSocket connect()
- Documented issue and fixes in .claude/SAFARI_WEBSOCKET_ISSUE.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: WebSocket wouldn't connect on page load because Pinia auth
state doesn't automatically transfer from SSR to client hydration.
The auth store showed isAuthenticated=false on client mount.
Solution:
- useWebSocket.onMounted now proactively calls checkAuth() if not authenticated
- This ensures auth state is initialized before attempting WebSocket connection
- Added forceReconnect() function to clear stale singleton connections
Debug UI (temporary):
- Added connection status debug info to loading overlay and banner
- Shows Auth/Connected/Connecting/Error states
- Retry button triggers auth check + reconnect
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed missing PlayOutcome.HIT_BY_PITCH handling in resolve_outcome().
HBP was being submitted via WebSocket but rejected with error:
"Unhandled outcome: PlayOutcome.HIT_BY_PITCH"
Changes:
- Added HBP case to play_resolver.py (lines 430-446)
- HBP uses same forced runner advancement logic as WALK
- HBP is NOT classified as is_walk=True (correct for stats)
- Added 2 unit tests: test_hit_by_pitch, test_hit_by_pitch_bases_loaded
Test results: 981/981 passing (100%)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Move 1st and 3rd base slightly inward for better diagonal alignment
- Adjust vertical positioning for proper edge alignment with 2nd base
- Mobile: top-[64%], Desktop: top-[60%] for optimal display per breakpoint
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Restructured mobile layout to match desktop:
- Runners+Outs column now beside Inning box in same row
- Layout: Away | [Inning][Runners/Outs] | Home
- Consistent layout between mobile and desktop views
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove Count box (not needed for this game mode)
- Stack Runners above Outs in compact vertical column
- Fix diamond orientation: bases now at corners (matching GameBoard)
- Change base markers from circles to diamond squares
- Remove home plate, keep only 1st/2nd/3rd bases
- Remove diamond outline for cleaner look
- Remove Outs label, keep only indicator circles
- Tighten spacing to align with Inning box height
- Remove unused balls/strikes props
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added OUTFIELD_ONLY_OUTCOMES constant for flyout_a, flyout_b, flyout_c
- Added showInfieldLocations computed to hide infield for flyballs
- Updated selectOutcome to clear infield location when switching to flyball
- Mirrors existing groundball logic that hides outfield positions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added rollback_play WebSocket handler (handlers.py:1632)
- Accepts game_id and num_plays (default: 1)
- Validates game state and play count
- Broadcasts play_rolled_back and game_state_update events
- Full error handling with rate limiting
- Added undoLastPlay action to useGameActions composable
- Emits rollback_play event to backend
- Added Undo button to game page ([id].vue)
- Amber floating action button with undo arrow icon
- Positioned above substitutions button
- Only visible when game is active and has plays
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Bug fix:
- Fixed hasRunners prop using wrong property path (gameState.runners.first
instead of gameState.on_first) - hit location selector was never showing
Optimization:
- Hide outfield positions (LF, CF, RF) for groundball outcomes since
groundballs by definition stay in the infield
- Auto-clear outfield selection when switching to groundball outcome
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resolved WebSocket connection issues and games list loading on iPad:
- cookies.py: Added is_secure_context() to set Secure flag when accessed
via HTTPS even in development mode (Safari requires this)
- useWebSocket.ts: Changed auto-connect from immediate watcher to
onMounted hook for safer SSR hydration
- games/index.vue: Replaced onMounted + fetchGames() with useAsyncData
for SSR data fetching with proper cookie forwarding
- Updated COOKIE_AUTH_IMPLEMENTATION.md with new issues and solutions
- Updated composables/CLAUDE.md with auto-connect pattern documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added helper scripts and documentation for improved developer experience:
1. ACCESS_CONTROL.md
- Comprehensive Discord whitelist documentation
- Configuration examples and security best practices
- Troubleshooting guide for common access issues
- Details both OAuth and test token protection points
2. start-services.sh
- One-command startup for backend + frontend
- Logs to logs/ directory with PIDs tracked
- Displays URLs and helpful information
- 3-second backend initialization delay
3. stop-services.sh
- Clean shutdown with process tree killing
- Removes orphaned processes by pattern matching
- Graceful TERM followed by KILL if needed
- Cleans up PID files
These utilities streamline local development by:
- Reducing manual startup steps
- Ensuring clean shutdown (no orphaned processes)
- Providing clear access control guidance
Scripts are now executable and ready to use:
./start-services.sh
./stop-services.sh
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added a "Quick Start Demo" button to the games list page that allows users
to instantly create and jump into a pre-configured demo game without going
through the full game creation flow.
Features:
- Green button prominently placed next to "Create New Game"
- Loading state with disabled button and "Creating..." text
- Calls /api/games/quick-create endpoint
- Auto-redirects to the created game page
- Error handling with user-friendly messages
This improves the user experience for testing and demos by reducing the
game creation flow from multiple steps to a single click.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added devServer configuration to bind on all network interfaces,
enabling access through Nginx Proxy Manager for remote testing.
Changes:
- Set host to '0.0.0.0' (listen on all interfaces)
- Keep port at 3000
This allows the frontend to be accessed via:
- Local: http://localhost:3000
- Remote: http://gameplay-demo.manticorum.com (via NPM proxy)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Two related bug fixes for gameplay accuracy:
**Backend - play_resolver.py**:
- Fixed DOUBLE2 advancement: Runners now advance exactly 2 bases
* 1st → 3rd, 2nd → home, 3rd → home
* Was incorrectly advancing all runners to home
- Fixed DOUBLE3 advancement: Runners now advance exactly 3 bases
* All runners score (1st+3=4, 2nd+3=5→4, 3rd+3=6→4)
* Updated docstrings for clarity
**Frontend - ManualOutcomeEntry.vue**:
- Fixed hit location requirement logic
* Now requires hit location when runners on base (any outs)
* Was incorrectly restricting to only when outs < 2
* Hit location determines runner advancement regardless of outs
These fixes ensure accurate Strat-O-Matic gameplay simulation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed property access bug introduced in previous commit:
**Issue**:
- DefensiveSetup.vue was accessing `gameState.runners.third` and `runners.first/second/third`
- GameState type doesn't have a `runners` property
- Caused TypeScript error: "Property 'runners' does not exist on type 'GameState'"
**Fix**:
- Changed to use correct properties: `on_first`, `on_second`, `on_third`
- Updated infieldDepthOptions: `props.gameState?.on_third` (line 163)
- Updated outfieldDepthOptions: destructure `on_first, on_second, on_third` (line 178)
- Updated hasRunners check: `on_first || on_second || on_third` (line 184)
**Why**:
GameState uses individual nullable properties for runners, not a nested object.
This matches the backend Pydantic model structure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive documentation for real-time gameplay workflow:
**New Documentation**:
1. WEBSOCKET_PROTOCOL_SPEC.md (49KB)
- Complete catalog of all 15 backend WebSocket event handlers
- Complete catalog of all frontend event listeners
- Game workflow sequences (connection → game start → play resolution)
- Critical issues identified and resolution status
- Event payload specifications with examples
- Timing and performance expectations
2. DECISION_REQUIRED_IMPLEMENTATION.md (11KB)
- Issue #1 detailed analysis and resolution
- Backend implementation of decision_required event
- Frontend integration approach
- Before/After workflow comparison
- Test validation results
3. GAMEPLAY_SESSION_HANDOFF.md (10KB)
- Session work summary and accomplishments
- Live testing results and observations
- Known issues and next steps
- Quick start guide for next session
- Technical architecture notes
**Why**:
- Provides single source of truth for WebSocket protocol
- Documents complete event flow for frontend/backend alignment
- Captures implementation decisions and rationale
- Enables faster onboarding for new developers
- Creates reference for debugging WebSocket issues
**Impact**:
- Reduces protocol confusion between frontend and backend
- Accelerates future WebSocket feature development
- Provides clear integration patterns for new events
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Frontend alignment with backend WebSocket protocol:
**Type System Fixes**:
- types/game.ts: Changed DecisionPhase to use 'awaiting_*' convention matching backend
- types/game.ts: Fixed PlayOutcome enum values to match backend string values (e.g., 'strikeout' not 'STRIKEOUT')
- types/game.ts: Added comprehensive play outcome types (groundball_a/b/c, flyout variants, x_check)
**Decision Detection**:
- store/game.ts: Updated decision detection to check both decision prompt AND gameState.decision_phase
- components: Updated all decision phase checks to use 'awaiting_defensive', 'awaiting_offensive', 'awaiting_stolen_base'
**WebSocket Enhancements**:
- useWebSocket.ts: Added game_joined event handler with success toast
- useWebSocket.ts: Fixed dice roll data - now receives d6_two_a and d6_two_b from server
- useWebSocket.ts: Request fresh game state after decision submissions to sync decision_phase
**New Constants**:
- constants/outcomes.ts: Created centralized PlayOutcome enum with display labels and descriptions
**Testing**:
- Updated test expectations for new decision phase naming
- All component tests passing
**Why**:
Eliminates confusion from dual naming conventions. Frontend now uses same vocabulary as backend.
Fixes runtime type errors from enum value mismatches.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Backend enhancements for real-time decision workflow:
**New Features**:
- decision_required event emission when game starts and after each decision
- Quick-create endpoint (/games/quick-create) for rapid testing with pre-configured lineups
- WebSocket connection manager integration in GameEngine
**Changes**:
- game_engine.py: Added _emit_decision_required() method and set_connection_manager()
- game_engine.py: Emit decision_required on game start with 5-minute timeout
- games.py: New /quick-create endpoint with Team 35 vs Team 38 lineups
- main.py: Wire connection manager to game_engine singleton
- state_manager.py: Enhanced state management for decision phases
- play_resolver.py: Improved play resolution logic
- handlers.py: Updated WebSocket handlers for new workflow
- backend/CLAUDE.md: Added WebSocket protocol spec reference
**Why**:
Eliminates polling - frontend now gets real-time notification when decisions are needed.
Quick-create saves 2 minutes of lineup setup during each test iteration.
**Testing**:
- Manual testing with terminal client
- WebSocket event flow verified with live frontend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit fixes two critical bugs in the game engine and updates tests
to match current webhook behavior:
1. State Recovery - Batter Advancement (operations.py:545-546)
- Added missing batting_order and outs_recorded fields to plays dictionary
- These fields exist in database but weren't loaded by load_game_state()
- Root cause: Batter index was correctly recovered but current_batter remained
at placeholder (batting_order=1) because recovery logic couldn't find the
actual batting_order from last play
- Fix enables proper batter advancement after backend restarts
2. Flyball Descriptions - 2 Outs Logic (runner_advancement.py)
- Made flyball descriptions dynamic based on outs and actual base runners
- FLYOUT_A (Deep): Lines 1352-1363
- FLYOUT_B (Medium): Lines 1438-1449
- FLYOUT_BQ (Medium-shallow): Lines 1521-1530
- With 2 outs: "3rd out, inning over" (no advancement possible)
- With 0-1 outs: Dynamic based on runners ("R3 scores" only if R3 exists)
- Game logic was already correct (runs_scored=0), only descriptions were wrong
- Fixed method signatures to include hit_location parameter for all flyball methods
- Updated X-check function calls to pass hit_location parameter
3. Test Updates
- Fixed test expectation in test_flyball_advancement.py to match corrected behavior
- Descriptions now only show runners that actually exist (no phantom "R2 DECIDE")
- Auto-fixed import ordering with Ruff
- Updated websocket tests to match current webhook behavior:
* test_submit_manual_outcome_success now expects 2 broadcasts (play_resolved + game_state_update)
* test_submit_manual_outcome_missing_required_location updated to reflect hit_location now optional
Testing:
- All 739 unit tests passing (100%)
- Verified batter advances correctly after state recovery
- Verified flyball with 2 outs shows correct description
- Verified dynamic descriptions only mention actual runners on base
- Verified websocket handler broadcasts work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove accidentally created backend/pages directory (Vue files belong in frontend).
Added pages/ to backend .gitignore to prevent future confusion.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete implementation of pre-game setup flow allowing players to create games
and submit lineups before gameplay starts.
Backend Changes:
- Extended games.py with create game, lineup submission, and game start endpoints
- Added teams.py roster endpoint with season filtering
- Enhanced SBA API client with player data fetching and caching
- Comprehensive validation for lineup submission (position conflicts, DH rules)
Frontend Changes:
- Redesigned create.vue with improved team selection and game options
- Enhanced index.vue with active/pending game filtering and navigation
- Added lineup/[id].vue for interactive lineup builder with drag-and-drop
- Implemented auth.client.ts plugin for client-side auth initialization
- Added comprehensive TypeScript types for API contracts
- Updated middleware for better auth handling
Key Features:
- Game creation with home/away team selection
- Full lineup builder with position assignment and batting order
- DH rule validation (pitcher can be excluded from batting order)
- Season-based roster filtering (Season 3)
- Auto-start game when both lineups submitted
- Real-time game list updates
Workflow:
1. Create game → select teams → set options
2. Submit home lineup → validate positions/order
3. Submit away lineup → validate positions/order
4. Game auto-starts → navigates to game page
5. WebSocket connection → loads game state
Ready for Phase F4 - connecting gameplay UI to complete the at-bat loop.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>