- Add vuedraggable for mobile-friendly lineup building
- Add touch delay and threshold settings for better mobile UX
- Add drag ghost/chosen/dragging visual states
- Add replacement mode visual feedback when dragging over occupied slots
- Add getBench action to useGameActions for substitution panel
- Add BN (bench) to valid positions in LineupPlayerState
- Update lineup service to load full lineup (active + bench)
- Add touch-manipulation CSS to UI components (ActionButton, ButtonGroup, ToggleSwitch)
- Add select-none to prevent text selection during touch interactions
- Add mobile touch patterns documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements schedule viewing from SBA production API with week navigation
and game creation from scheduled matchups. Groups games by team matchup
horizontally with games stacked vertically for space efficiency.
Backend:
- Add schedule routes (/api/schedule/current, /api/schedule/games)
- Add SBA API client methods for schedule data
- Fix multi-worker state isolation (single worker for in-memory state)
- Add Redis migration TODO for future scalability
- Support custom team IDs in quick-create endpoint
Frontend:
- Add Schedule tab as default on home page
- Week navigation with prev/next and "Current Week" jump
- Horizontal group layout (2-6 columns responsive)
- Completed games show score + "Final" badge (no Play button)
- Incomplete games show "Play" button to create webapp game
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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 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>
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>
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>
- 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>
- 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>
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>
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>
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>