- Rename project instructions file to Claude Code standard name
- Add .gitignore to exclude IDE files and frontend-poc/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- REST endpoints for auth, users, collections, decks, games
- WebSocket events (client→server and server→client)
- Key backend files to reference
- Type definitions to mirror from backend schemas
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update status to COMPLETE with completedDate 2026-01-30
- Increment completedPhases from 4 to 5
- Update deliverables list with actual implementations
- 306 new tests added, 1505 total tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add UserRepository and LinkedAccountRepository protocols to protocols.py
- Add UserEntry and LinkedAccountEntry DTOs for service layer decoupling
- Implement PostgresUserRepository and PostgresLinkedAccountRepository
- Refactor UserService to use constructor-injected repositories
- Add get_user_service factory and UserServiceDep to API deps
- Update auth.py and users.py endpoints to use UserServiceDep
- Rewrite tests to use FastAPI dependency overrides (no monkey patching)
This follows the established repository pattern used by DeckService and
CollectionService, enabling future offline fork support.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create 16 integration tests across 7 test classes covering:
- JWT authentication (valid/invalid/expired tokens)
- Game join flow with Redis connection tracking
- Action execution and state broadcasting
- Turn timeout Redis operations (start/get/cancel/extend)
- Disconnection cleanup
- Spectator filtered state
- Reconnection tracking
Uses testcontainers for real Redis/Postgres integration. Completes
Phase 4 (Game Service + WebSocket) with all 18 tasks finished.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add REST API endpoints for game lifecycle operations:
- POST /games - Create new game between two players
- GET /games/{game_id} - Get game info for reconnection
- GET /games/me/active - List user's active games
- POST /games/{game_id}/resign - Resign from game via HTTP
Includes proper reverse proxy support for WebSocket URL generation
(X-Forwarded-* headers -> settings.base_url -> Host header fallback).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add GameNamespaceHandler with full event handling for real-time gameplay:
- handle_join: Join/rejoin games with visibility-filtered state
- handle_action: Execute actions and broadcast state to participants
- handle_resign: Process resignation and end game
- handle_disconnect: Notify opponent of disconnection
- Broadcast helpers for state, game over, and opponent status
Includes 28 unit tests covering all handler methods.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GameEndResult dataclass with winner, loser, final views, duration
- Add _map_end_reason() to map core GameEndReason to DB EndReason
(raises ValueError for unknown reasons to catch missing enum sync)
- Enhance end_game() to build replay data and return comprehensive result
- Add archive_to_history() to GameStateManager for complete game archival:
- Creates GameHistory record with replay data
- Deletes ActiveGame record
- Clears Redis cache
- All in single transaction
- Add ArchiveResult dataclass for archive operation metadata
- Add TODO for session_factory DI refactor in GameStateManager
- Update tests: 5 new end_game tests, 6 new archive_to_history tests
Phase 4 progress: 10/18 tasks complete
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add pending forced action and game_over fields to GameJoinResult:
- pending_forced_action: Included when player must complete a forced
action (e.g., select new active after KO). Essential for reconnection
so client knows what action is required.
- game_over: Boolean indicating if game has already ended.
- is_your_turn: Now True when player has pending forced action, even if
it's technically opponent's turn.
The join_game method now handles both initial joins and reconnections
(resume). The last_event_id parameter is accepted for future event
replay support.
Tests: 4 new tests for forced action handling and game_over flag.
Total 51 tests for GameService.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Standardizes the phased development workflow:
- /backend-phase status - Show current phase and progress
- /backend-phase next - Show details of next task
- /backend-phase start <id> - Begin working on task
- /backend-phase done <id> - Mark task complete with tests
- /backend-phase plan - Create plan for next NOT_STARTED phase
Reads from PROJECT_PLAN_MASTER.json and project_plans/PHASE_*.json.
Ensures dependencies are met before starting tasks and tests exist
before marking complete.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix audit warnings about empty string defaults hiding data corruption:
1. get_connection_info(): Validate required fields (user_id, connected_at,
last_seen) exist before creating ConnectionInfo. Return None and log
warning for corrupted records instead of returning invalid data.
2. unregister_connection(): Log warning if user_id is missing during
cleanup. Existing code safely handles this (skips cleanup), but now
we have visibility into data corruption.
Test added for corrupted data case.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Three changes to fail fast instead of silently degrading:
1. GameService.create_game: Raise GameCreationError when energy card
definition not found instead of logging warning and continuing.
A deck with missing energy cards is fundamentally broken.
2. CardService.load_all: Collect all card file load failures and raise
CardServiceLoadError at end with comprehensive error report. Prevents
startup with partial card data that causes cryptic runtime errors.
New exceptions: CardLoadError, CardServiceLoadError
3. GameStateManager.recover_active_games: Return RecoveryResult dataclass
with recovered count, failed game IDs with error messages, and total.
Enables proper monitoring and alerting for corrupted game state.
Tests added for energy card error case. Existing tests updated for
new RecoveryResult return type.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add forced action handling, turn boundary detection, and DB persistence:
- Check for pending forced actions before allowing regular actions
- Only specified player can act during forced action (except resign)
- Only specified action type allowed during forced action
- Detect turn boundaries (turn number OR current player change)
- Persist to Postgres at turn boundaries for durability
- Include pending_forced_action in GameActionResult for client
New exceptions: ForcedActionRequiredError
Tests: 11 new tests covering forced actions, turn boundaries, and
pending action reporting. Total 47 tests for GameService.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add create_game method that loads decks via DeckService, converts
CardDefinitions to CardInstances, and persists to Redis/Postgres
- Build card registry from only the cards in play (not all cards)
- Add GameCreationError exception and GameCreateResult dataclass
- Add creation_engine_factory for DI-based testing (no monkey patching)
- Add helper methods: _cards_to_instances, _build_card_registry
- Update tests with proper mocks for success, deck failure, engine failure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migrate all usages to the proper mode/value fields:
- Weakness: mode=MULTIPLICATIVE, value=2
- Resistance: mode=ADDITIVE, value=-30
Remove backwards compatibility code and legacy test.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ConnectionManager: Add redis_factory constructor parameter
- GameService: Add engine_factory constructor parameter
- AuthHandler: New class replacing standalone functions with
token_verifier and conn_manager injection
- Update all tests to use constructor DI instead of patch()
- Update CLAUDE.md with factory injection patterns
- Update services README with new patterns
- Add socketio README documenting AuthHandler and events
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WebSocket Message Schemas (WS-002):
- Add Pydantic models for all client/server WebSocket messages
- Implement discriminated unions for message type parsing
- Include JoinGame, Action, Resign, Heartbeat client messages
- Include GameState, ActionResult, Error, TurnStart server messages
Connection Manager (WS-003):
- Add Redis-backed WebSocket connection tracking
- Implement user-to-sid mapping with TTL management
- Support game room association and opponent lookup
- Add heartbeat tracking for connection health
Socket.IO Authentication (WS-004):
- Add JWT-based authentication middleware
- Support token extraction from multiple formats
- Implement session setup with ConnectionManager integration
- Add require_auth helper for event handlers
Socket.IO Server Setup (WS-001):
- Configure AsyncServer with ASGI mode
- Register /game namespace with event handlers
- Integrate with FastAPI via ASGIApp wrapper
- Configure CORS from application settings
Game Service (GS-001):
- Add stateless GameService for game lifecycle orchestration
- Create engine per-operation using rules from GameState
- Implement action-based RNG seeding for deterministic replay
- Add rng_seed field to GameState for replay support
Architecture verified:
- Core module independence (no forbidden imports)
- Config from request pattern (rules in GameState)
- Dependency injection (constructor deps, method config)
- All 1090 tests passing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create tests/conftest.py with testcontainers for Postgres and Redis
- Auto-detect Docker Desktop socket and disable Ryuk for compatibility
- Update tests/db/conftest.py and tests/services/conftest.py to use shared fixtures
- Fix test_resolve_effect_logs_exceptions: logger was disabled by pytest
- Fix test_save_and_load_with_real_redis: use redis_url fixture
- Minor lint fix in engine_validation.py
Tests now auto-start containers on run - no need for `docker compose up`
All 1199 tests passing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Project plan updates:
- Mark all 14 Phase 3 tasks as completed
- Update acceptance criteria to met
- Update master plan status to Phase 4 next
- Add detailed deliverables list for Phase 3
Documentation updates:
- Add UNSET sentinel pattern to CLAUDE.md
- Document when to use UNSET vs None for nullable fields
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
UNSET sentinel pattern:
- Add UNSET sentinel in protocols.py for nullable field updates
- Fix inability to clear deck description (UNSET=keep, None=clear)
- Fix repository inability to clear validation_errors
Starter deck improvements:
- Remove unused has_starter_deck from CollectionService
- Add deprecation notes to old starter deck methods
Validation improvements:
- Add energy type validation in deck_validator.py
- Add energy type validation in deck schemas
- Add VALID_ENERGY_TYPES constant
Game loading fix:
- Fix get_deck_for_game silently skipping invalid cards
- Now raises ValueError with clear error message
Tests:
- Add TestEnergyTypeValidation test class
- Add TestGetDeckForGame test class
- Add tests for validate_energy_types utility function
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Critical fixes:
- Add admin API key authentication for admin endpoints
- Add race condition protection via unique partial index for starter decks
- Make starter deck selection atomic with combined method
Moderate fixes:
- Fix DI pattern violation in validate_deck_endpoint
- Add card ID format validation (regex pattern)
- Add card quantity validation (1-99 range)
- Fix exception chaining with from None (B904)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implemented with Repository Protocol pattern for offline fork support:
- CollectionService with PostgresCollectionRepository
- DeckService with PostgresDeckRepository
- DeckValidator with DeckConfig + CardService injection
- Starter deck definitions (5 types: grass, fire, water, psychic, lightning)
- Pydantic schemas for collection and deck APIs
- Unit tests for DeckValidator (32 tests passing)
Architecture follows pure dependency injection - no service locator patterns.
Added CLAUDE.md documenting DI requirements and patterns.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Discord OAuth callback now redirects to frontend with tokens in URL fragment
(more secure than query params - fragment not sent to server)
- Error responses also redirect with error in query params
- Fix deps.py import: get_session_dependency -> get_session
Part of Phase 2 Authentication completion.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete OAuth-based authentication with JWT session management:
Core Services:
- JWT service for access/refresh token creation and verification
- Token store with Redis-backed refresh token revocation
- User service for CRUD operations and OAuth-based creation
- Google and Discord OAuth services with full flow support
API Endpoints:
- GET /api/auth/{google,discord} - Start OAuth flows
- GET /api/auth/{google,discord}/callback - Handle OAuth callbacks
- POST /api/auth/refresh - Exchange refresh token for new access token
- POST /api/auth/logout - Revoke single refresh token
- POST /api/auth/logout-all - Revoke all user sessions
- GET/PATCH /api/users/me - User profile management
- GET /api/users/me/linked-accounts - List OAuth providers
- GET /api/users/me/sessions - Count active sessions
Infrastructure:
- Pydantic schemas for auth/user request/response models
- FastAPI dependencies (get_current_user, get_current_premium_user)
- OAuthLinkedAccount model for multi-provider support
- Alembic migration for oauth_linked_accounts table
Dependencies added: email-validator, fakeredis (dev), respx (dev)
84 new tests, 1058 total passing
- Derive image_path from card ID instead of raw data image_file field
- Use simplified CDN paths: /<set>/<card>.webp (e.g., a1/033-charmander.webp)
- Energy cards use basic/<type>.webp paths
- Fix undefined variable bug in transform_trainer_card
- Update tests to match new path format
- Regenerate all 382 card definitions with correct image_url fields
The converter now falls back to using the source image_url from raw
data when image_file is not available (images not downloaded locally).
- Pokemon cards: use source image_url when image_file is null
- Trainer cards: same fallback behavior
- Regenerated all 382 card definitions with image URLs
Images point to pokemon-zone.com assets for now. When we host our
own images, cards with image_file will use our CDN instead.
- Add lifespan context manager to app/main.py with startup/shutdown hooks
- Wire startup: init_db(), init_redis(), CardService.load_all()
- Wire shutdown: close_db(), close_redis()
- Add /health/ready endpoint for readiness checks
- Add CORS middleware with configurable origins
- Disable docs in production (only available in dev)
- Export get_session_dependency from app/db/__init__.py for FastAPI DI
- Add game_cache_ttl_seconds to Settings (configurable, was hardcoded)
- Fix datetime.utcnow() deprecation (4 occurrences) -> datetime.now(UTC)
- Update test to match S3 image URL (was placeholder CDN)
All 974 tests passing.
The source website uses <span class='energy-text energy-text--type-fire'>
to render inline energy icons. BeautifulSoup's get_text() was stripping
these spans, losing the energy type information and causing merged text
like 'Discard aEnergy' instead of 'Discard a Fire Energy'.
Changes:
- Add ENERGY_TEXT_TYPES mapping for inline energy references
- Add replace_energy_text_spans() to convert spans to text before extraction
- Add extract_effect_text() helper with proper text joining (separator=' ')
- Update parse_attack(), parse_ability(), _parse_trainer_details() to use it
- Fix JSON encoding in convert_cards.py to use UTF-8 (ensure_ascii=False)
Before: 'Discard an Energy from this Pokémon'
After: 'Discard a Fire Energy from this Pokémon'
Re-scraped all 372 cards and regenerated 382 definitions.
Scraper fixes:
- Detect fossil cards (Helix/Dome Fossil, Old Amber) as Trainer/Item cards
- Add text artifact cleaning for stripped energy icons:
- 'aEnergy' -> 'an Energy'
- 'extraEnergy' -> 'extra Energy'
- 'BenchedPokémon' -> 'Benched Pokémon'
- And 20+ other common patterns
Converter improvements:
- Add evolution chain validation to detect broken evolves_from references
- Track conversion errors and validation warnings in _index.json
- Return errors from convert_set() for better debugging
Data fixes:
- Fixed 4 fossil cards (now correctly typed as trainer/item)
- Fixed text artifacts in 46 raw card files
- Regenerated all 382 card definitions
- All evolution chains now valid
Added fix_raw_text.py utility script for batch text cleanup.
- Rename data/cards/ to data/raw/ for scraped data
- Add data/definitions/ as authoritative card data source
- Add convert_cards.py script to transform raw -> definitions
- Generate 378 card definitions (344 Pokemon, 24 Trainers, 10 Energy)
- Add CardService for loading and querying card definitions
- In-memory indexes for fast lookups by type, set, pokemon_type
- search() with multiple filter criteria
- get_all_cards() for GameEngine integration
- Add SetInfo model for set metadata
- Update Attack model with damage_display field for variable damage
- Update CardDefinition with image_path, illustrator, flavor_text
- Add 45 tests (21 converter + 24 CardService)
- Update scraper output path to data/raw/
Card data is JSON-authoritative (no database) to support offline fork goal.
Phase 1 Database Implementation (DB-001 through DB-012):
Models:
- User: OAuth support (Google/Discord), premium subscriptions
- Collection: Card ownership with CardSource enum
- Deck: JSONB cards/energy_cards, validation state
- CampaignProgress: One-to-one with User, medals/NPCs as JSONB
- ActiveGame: In-progress games with GameType enum
- GameHistory: Completed games with EndReason enum, replay data
Infrastructure:
- Alembic migrations with sync psycopg2 (avoids async issues)
- Docker Compose for Postgres (5433) and Redis (6380)
- App config with Pydantic settings
- Redis client helper
Test Infrastructure:
- 68 database tests (47 model + 21 relationship)
- Async factory pattern for test data creation
- Sync TRUNCATE cleanup (solves pytest-asyncio event loop mismatch)
- Uses dev containers instead of testcontainers for reliability
Key technical decisions:
- passive_deletes=True for ON DELETE SET NULL relationships
- NullPool for test sessions (no connection reuse)
- expire_on_commit=False with manual expire() for relationship tests
- Fix is_ex detection to only match ' ex' suffix (fixes Exeggutor false positive)
- Fix trainer card detection with specific 'Trainer | Type' pattern
- Improve trainer effect text extraction from content body
- Scrape full A1 set: 286 cards
- Scrape full A1a set: 86 cards
- Total: 372 cards with images (some images failed due to rate limiting)
- Re-scraped affected cards to fix is_ex and trainer type issues
- Add --images flag to download images during scraping
- Add --download-images command to fetch images for existing card data
- Images saved to data/images/{set}/ directory
- Card JSON updated with image_file field (relative path)
- Uses Referer header for asset server compatibility
- Supports skip-if-exists for incremental downloads
- Add scrape_pokemon_pocket.py script to fetch card data from pokemon-zone.com
- Scrapes Pokemon, Trainer, and Energy cards with full metadata
- Includes image URLs for offline caching support
- Supports --set, --card, --limit, and --reindex CLI options
- Add beautifulsoup4 and requests as dev dependencies
- Create data/cards/ directory structure for card JSON files