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>
- 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 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>
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>
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>
## Authentication Implementation
### Backend
- Implemented complete Discord OAuth flow in auth.py:
* POST /api/auth/discord/callback - Exchange code for tokens
* POST /api/auth/refresh - Refresh JWT tokens
* GET /api/auth/me - Get authenticated user info
* GET /api/auth/verify - Verify auth status
- JWT token creation with 7-day expiration
- Refresh token support for session persistence
- Bearer token authentication for Discord API calls
### Frontend
- Created auth/login.vue - Discord OAuth initiation page
- Created auth/callback.vue - OAuth callback handler with states
- Integrated with existing auth store (already implemented)
- LocalStorage persistence for tokens and user data
- Full error handling and loading states
### Configuration
- Updated backend .env with Discord OAuth credentials
- Updated frontend .env with Discord Client ID
- Fixed redirect URI to port 3001
## SBA API Integration
### Backend
- Extended SbaApiClient with get_teams(season, active_only=True)
- Added bearer token auth support (_get_headers method)
- Created /api/teams route with TeamResponse model
- Registered teams router in main.py
- Filters out IL (Injured List) teams automatically
- Returns team data: id, abbrev, names, color, gmid, division
### Integration
- Connected to production SBA API: https://api.sba.manticorum.com
- Bearer token authentication working
- Successfully fetches ~16 active Season 3 teams
## Documentation
- Created SESSION_NOTES.md - Current session accomplishments
- Created NEXT_SESSION.md - Game creation implementation guide
- Updated implementation/NEXT_SESSION.md
## Testing
- ✅ Discord OAuth flow tested end-to-end
- ✅ User authentication and session persistence verified
- ✅ Teams API returns real data from production
- ✅ All services running and communicating
## What Works Now
- User can sign in with Discord
- Sessions persist across reloads
- Backend fetches real teams from SBA API
- Ready for game creation implementation
## Next Steps
See .claude/NEXT_SESSION.md for detailed game creation implementation plan.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Backend fixes:
- state_manager: Properly recover current_pitcher and current_catcher from
fielding team during game state recovery (fixes pitcher badge not showing)
- handlers: Add headshot field to lineup data, use lineup_service for proper
player data loading on cache miss
- lineup_service: Minor adjustments for headshot support
Frontend fixes:
- player.ts: Update Lineup type to match WebSocket event format
- lineup_id (was 'id'), card_id fields
- player.headshot for UI circles
- Optional fields for event variations
- CurrentSituation.vue: Adapt to updated type structure
- Substitution selectors: Use updated Lineup type fields
This fixes the issue where pitcher badge wouldn't show after game recovery
because current_pitcher was being set from batting team instead of fielding team.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created centralized services for SBA player data fetching at lineup creation:
Backend - New Services:
- app/services/sba_api_client.py: REST client for SBA API (api.sba.manticorum.com)
with batch player fetching and caching support
- app/services/lineup_service.py: High-level service combining DB operations
with API calls for complete lineup entries with player data
Backend - Refactored Components:
- app/core/game_engine.py: Replaced raw API calls with LineupService,
reduced _prepare_next_play() from ~50 lines to ~15 lines
- app/core/substitution_manager.py: Updated pinch_hit(), defensive_replace(),
change_pitcher() to use lineup_service.get_sba_player_data()
- app/models/game_models.py: Added player_name/player_image to LineupPlayerState
- app/services/__init__.py: Exported new LineupService components
- app/websocket/handlers.py: Enhanced lineup state handling
Frontend - SBA League:
- components/Game/CurrentSituation.vue: Restored player images with fallback
badges (P/B letters) for both mobile and desktop layouts
- components/Game/GameBoard.vue: Enhanced game board visualization
- composables/useGameActions.ts: Updated game action handling
- composables/useWebSocket.ts: Improved WebSocket state management
- pages/games/[id].vue: Enhanced game page with better state handling
- types/game.ts: Updated type definitions
- types/websocket.ts: Added WebSocket type support
Architecture Improvement:
All SBA player data fetching now goes through LineupService:
- Lineup creation: add_sba_player_to_lineup()
- Lineup loading: load_team_lineup_with_player_data()
- Substitutions: get_sba_player_data()
All 739 unit tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed all references to the defensive alignment field across frontend codebase
after backend removal in Session 1. The alignment field was determined to be unused
and was removed from DefensiveDecision model.
Changes:
- types/websocket.ts: Removed alignment from DefensiveDecisionRequest interface
- composables/useGameActions.ts: Removed alignment from submit handler
- pages/demo-decisions.vue: Updated demo state and summary text (alignment → depths)
- pages/games/[id].vue: Updated decision history text for both defensive and offensive
* Defensive: Now shows "infield depth, outfield depth" instead of "alignment, infield"
* Offensive: Updated to use new action field with proper labels (swing_away, hit_and_run, etc.)
- Test files (3): Updated all test cases to remove alignment references
* tests/unit/composables/useGameActions.spec.ts
* tests/unit/store/game-decisions.spec.ts
* tests/unit/components/Decisions/DefensiveSetup.spec.ts
Also updated offensive decision handling to match Session 2 changes (approach/hit_and_run/bunt_attempt → action field).
Total: 7 files modified, all alignment references removed
Verified: Zero remaining alignment references in .ts/.vue/.js files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated all 4 demo pages to use new action field and added navigation footers
for easy cross-linking between demo pages.
Changes:
- demo-decisions.vue: Updated to use action field, added runner prop bindings
- demo.vue: Added footer with links to other demos
- demo-gameplay.vue: Added footer with links to other demos
- demo-substitutions.vue: Added footer with links to other demos
Demo page updates:
- OffensiveDecision now uses action field instead of approach/hit_and_run/bunt
- Action labels properly mapped (swing_away, steal, check_jump, etc.)
- Added runner state props (runnerOnFirst, runnerOnSecond, runnerOnThird)
- Added outs prop for smart filtering
Footer features:
- 3-column grid layout responsive to mobile
- Icons and descriptions for each demo
- Hover effects on links
- Consistent styling across all pages
Demo pages now fully connected:
- /demo → Game State Demo (ScoreBoard, GameBoard, etc.)
- /demo-decisions → Decision Components Demo (new action-based)
- /demo-gameplay → Gameplay Components Demo (DiceRoller, ManualOutcome)
- /demo-substitutions → Substitution Components Demo
Files modified:
- pages/demo-decisions.vue
- pages/demo.vue
- pages/demo-gameplay.vue
- pages/demo-substitutions.vue
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Frontend refactor complete - updated TypeScript interfaces and OffensiveApproach
component to use new action-based system with smart filtering.
Changes:
- TypeScript interfaces: Replaced approach/hit_and_run/bunt_attempt with action field
- OffensiveApproach.vue: Complete refactor with 6 action choices and smart filtering
- Smart filtering: Automatically disables invalid actions based on game state
- Auto-reset: If current action becomes invalid, resets to swing_away
TypeScript updates (types/game.ts, types/websocket.ts):
- OffensiveDecision.action: 6 valid choices (swing_away, steal, check_jump,
hit_and_run, sac_bunt, squeeze_bunt)
- Removed deprecated fields: approach, hit_and_run, bunt_attempt
- OffensiveDecisionRequest updated to match
Component features:
- Smart filtering based on game state (runners, outs)
- Visual feedback for disabled actions with explanatory text
- Special handling notes for steal and squeeze_bunt
- Auto-reset to swing_away when actions become invalid
- Clean, modern UI with action icons and descriptions
Action requirements enforced in UI:
- check_jump: requires runner on base
- hit_and_run: requires runner on base
- sac_bunt: disabled with 2 outs
- squeeze_bunt: requires R3, disabled with 2 outs
- steal/swing_away: always available
Files modified:
- types/game.ts
- types/websocket.ts
- components/Decisions/OffensiveApproach.vue
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed the unused alignment field from DefensiveDecision model and all
related code across backend and frontend.
Backend changes:
- models/game_models.py: Removed alignment field and validator
- terminal_client/display.py: Removed alignment from display
- core/ai_opponent.py: Updated log message
- tests/unit/models/test_game_models.py: Removed alignment tests
- tests/unit/core/test_validators.py: Removed alignment validation test
Frontend changes:
- types/game.ts: Removed alignment from DefensiveDecision interface
- components/Decisions/DefensiveSetup.vue:
* Removed alignment section from template
* Removed alignment from localSetup initialization
* Removed alignmentOptions array
* Removed alignmentDisplay computed property
* Removed alignment from hasChanges comparison
* Removed alignment from visual preview (reorganized to col-span-2)
Rationale: Defensive alignment is not active in the game and will not be
used. Per Cal's decision, remove completely rather than keep as dead code.
Tests: All 728 backend unit tests passing (100%)
Session 1 Part 3 - Change #6 complete
Part of cleanup work from demo review
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Initialize both Nuxt 3 frontends (SBA and PD) with full configuration:
Frontend Setup:
- Initialized Nuxt 3 projects for both leagues (SBA and PD)
- Installed dependencies: Tailwind CSS, Pinia, Socket.io-client, Axios
- Configured league-specific settings in nuxt.config.ts
- Created WebSocket plugins for real-time communication
- Set up TypeScript with strict mode and type checking
- Configured Tailwind CSS for styling
Backend Updates:
- Updated database models documentation in backend/CLAUDE.md
- Enhanced db_models.py with additional relationship patterns
Documentation:
- Updated Phase 1 completion checklist (12/12 items - 100% complete)
- Marked all infrastructure objectives as complete
Running Services:
- Backend (FastAPI + Socket.io): http://localhost:8000
- Frontend SBA: http://localhost:3000
- Frontend PD: http://localhost:3001
- Redis: port 6379
- PostgreSQL: Connected to remote server
Phase 1 is now complete. Ready to proceed to Phase 2 (Game Engine Core).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive project documentation and Docker infrastructure for
Paper Dynasty Real-Time Game Engine - a web-based multiplayer baseball
simulation platform replacing the legacy Google Sheets system.
Documentation Added:
- Complete PRD (Product Requirements Document)
- Project README with dual development workflows
- Implementation guide with 5-phase roadmap
- Architecture docs (backend, frontend, database, WebSocket)
- CLAUDE.md context files for each major directory
Infrastructure Added:
- Root docker-compose.yml for full stack orchestration
- Dockerfiles for backend and both frontends (multi-stage builds)
- .dockerignore files for optimal build context
- .env.example with all required configuration
- Updated .gitignore for Python, Node, Nuxt, and Docker
Project Structure:
- backend/ - FastAPI + Socket.io game engine (Python 3.11+)
- frontend-sba/ - SBA League Nuxt 3 frontend
- frontend-pd/ - PD League Nuxt 3 frontend
- .claude/implementation/ - Detailed implementation guides
Supports two development workflows:
1. Local dev (recommended): Services run natively with hot-reload
2. Full Docker: One-command stack orchestration for testing/demos
Next: Phase 1 implementation (backend/frontend foundations)