Update project plans and documentation for Phase 3 completion

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>
This commit is contained in:
Cal Corum 2026-01-28 15:26:01 -06:00
parent 7d397a2e22
commit ebe776d54d
3 changed files with 87 additions and 45 deletions

View File

@ -150,6 +150,42 @@ class CollectionService:
---
## UNSET Sentinel Pattern
For nullable fields that can be explicitly cleared (set to `None`), use the `UNSET` sentinel to distinguish between "not provided" (keep existing) and "set to null" (clear the value).
### Pattern
```python
from app.repositories.protocols import UNSET
# In repository/service method signatures
async def update(
self,
deck_id: UUID,
name: str | None = None, # None means "don't change"
description: str | None = UNSET, # type: ignore[assignment]
# UNSET = keep existing, None = clear, str = set new value
) -> DeckEntry | None:
...
if description is not UNSET:
record.description = description # Could be None (clear) or string (set)
```
### API Layer Usage
```python
# Check if field was explicitly provided in request
description = deck_in.description if "description" in deck_in.model_fields_set else UNSET
```
### When to Use
- Fields that can be meaningfully set to `None` (descriptions, notes, optional refs)
- Not needed for fields where `None` means "don't update" (name, cards, etc.)
---
## Configuration Classes
Game rules are defined in `app/core/config.py` as Pydantic models. These are passed from the frontend as request parameters.

View File

@ -7,8 +7,8 @@
"projectName": "Mantimon TCG - Backend Services",
"description": "Live service backend for play.mantimon.com - server-authoritative multiplayer TCG with campaign mode, free-play, and F2P monetization",
"totalPhases": 6,
"completedPhases": 3,
"status": "Phases 0-2 complete, Phase 3 (Collections + Decks) up next"
"completedPhases": 4,
"status": "Phases 0-3 complete, Phase 4 (Game Service + WebSocket) up next"
},
"architectureDecisions": {
@ -121,20 +121,26 @@
{
"id": "PHASE_3",
"name": "Collections + Decks",
"status": "NOT_STARTED",
"status": "COMPLETE",
"description": "Card ownership, deck building, starter deck selection",
"planFile": "project_plans/PHASE_3_COLLECTION_DECKS.json",
"estimatedWeeks": "1-2",
"dependencies": ["PHASE_2"],
"deliverables": [
"CollectionService (owned cards CRUD)",
"DeckService (create, edit, validate, delete)",
"Mode-aware deck validation (owned vs full collection)",
"Deck slot limits (free vs premium)",
"Starter deck selection flow",
"5 starter deck definitions",
"REST endpoints"
]
"CollectionService with repository pattern",
"DeckService with DI and DeckConfig from request",
"DeckValidator pure function with comprehensive rules",
"Collections API (/collections/me, /collections/me/cards/{id}, admin endpoint)",
"Decks API (/decks CRUD, /decks/validate)",
"Starter deck endpoints (/users/me/starter-deck, /users/me/starter-status)",
"5 starter deck definitions (grass, fire, water, psychic, lightning)",
"UNSET sentinel pattern for nullable field updates",
"Race condition protection via unique partial index",
"Admin API key authentication",
"Energy type validation",
"127 new tests, 1199 total tests"
],
"completedDate": "2026-01-28"
},
{
"id": "PHASE_4",

View File

@ -9,8 +9,8 @@
"description": "Card ownership (collections), deck building, validation, and starter deck selection",
"totalEstimatedHours": 29,
"totalTasks": 14,
"completedTasks": 4,
"status": "in_progress",
"completedTasks": 14,
"status": "completed",
"masterPlan": "../PROJECT_PLAN_MASTER.json"
},
@ -149,8 +149,8 @@
"description": "Service layer for card collection CRUD operations",
"category": "critical",
"priority": 4,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-001"],
"files": [
{"path": "app/services/collection_service.py", "status": "create"}
@ -182,8 +182,8 @@
"description": "Service layer for deck CRUD with validation",
"category": "critical",
"priority": 5,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-002", "COLL-003", "COLL-004"],
"files": [
{"path": "app/services/deck_service.py", "status": "create"}
@ -222,8 +222,8 @@
"description": "Define 5 starter decks with real card IDs from scraped data",
"category": "high",
"priority": 6,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": [],
"files": [
{"path": "app/data/starter_decks.py", "status": "create"}
@ -273,8 +273,8 @@
"description": "REST endpoints for collection management",
"category": "high",
"priority": 7,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-004"],
"files": [
{"path": "app/api/collections.py", "status": "create"}
@ -308,8 +308,8 @@
"description": "REST endpoints for deck CRUD and validation",
"category": "high",
"priority": 8,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-005"],
"files": [
{"path": "app/api/decks.py", "status": "create"}
@ -359,8 +359,8 @@
"description": "Endpoint for new user starter deck selection flow",
"category": "high",
"priority": 9,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-004", "COLL-005", "COLL-006"],
"files": [
{"path": "app/api/users.py", "status": "modify"}
@ -393,8 +393,8 @@
"description": "Mount collections and decks routers",
"category": "high",
"priority": 10,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-007", "COLL-008"],
"files": [
{"path": "app/main.py", "status": "modify"}
@ -441,8 +441,8 @@
"description": "Integration tests for collection operations",
"category": "high",
"priority": 12,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-004"],
"files": [
{"path": "tests/services/test_collection_service.py", "status": "create"}
@ -470,8 +470,8 @@
"description": "Integration tests for deck operations",
"category": "high",
"priority": 13,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-005"],
"files": [
{"path": "tests/services/test_deck_service.py", "status": "create"}
@ -504,8 +504,8 @@
"description": "Integration tests for collection and deck endpoints",
"category": "high",
"priority": 14,
"completed": false,
"tested": false,
"completed": true,
"tested": true,
"dependencies": ["COLL-007", "COLL-008", "COLL-009"],
"files": [
{"path": "tests/api/test_collections_api.py", "status": "create"},
@ -551,18 +551,18 @@
},
"acceptanceCriteria": [
{"criterion": "User can view their card collection", "met": false},
{"criterion": "User can create a deck with name, cards, and energy", "met": false},
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": false},
{"criterion": "Deck validation reports all errors (not just first)", "met": false},
{"criterion": "Free users limited to 5 decks", "met": false},
{"criterion": "Premium users have unlimited decks", "met": false},
{"criterion": "New user can select a starter deck (one time)", "met": false},
{"criterion": "Starter deck selection grants cards and creates deck", "met": false},
{"criterion": "Campaign mode validates card ownership", "met": false},
{"criterion": "Freeplay mode skips ownership validation", "met": false},
{"criterion": "All endpoints require authentication", "met": false},
{"criterion": "All tests pass with high coverage", "met": false}
{"criterion": "User can view their card collection", "met": true},
{"criterion": "User can create a deck with name, cards, and energy", "met": true},
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": true},
{"criterion": "Deck validation reports all errors (not just first)", "met": true},
{"criterion": "Free users limited to 5 decks", "met": true},
{"criterion": "Premium users have unlimited decks", "met": true},
{"criterion": "New user can select a starter deck (one time)", "met": true},
{"criterion": "Starter deck selection grants cards and creates deck", "met": true},
{"criterion": "Campaign mode validates card ownership", "met": true},
{"criterion": "Freeplay mode skips ownership validation", "met": true},
{"criterion": "All endpoints require authentication", "met": true},
{"criterion": "All tests pass with high coverage", "met": true}
],
"securityConsiderations": [