# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands | Task | Command | |------|---------| | Install dependencies | `make init` (installs Python via uv + npm packages, loads platform fixtures) | | Development server | `make dev` (runs Django runserver + Tailwind CSS watcher) | | Production-like dev | `make dev-prod` (Caddy + Gunicorn/Uvicorn + Django-Q cluster) | | Run tests | `make test` (or `uv run --with pytest-django pytest`) | | Make migrations | `make makemigrations` | | Apply migrations | `make migrate` | | CSS (Tailwind) | `make css` | | Django shell | `make shell` | | Create superuser | `make createsuperuser` | | Format Python | `make format` (or `uv run ruff format`) | | Lint Python | `make lint` (or `uv run ruff check`) | | Auto-fix lint | `make lint-fix` (`ruff check --fix`) | | Lint + format check + tests | `make check` (CI-style aggregate) | | Sync uv.lock | `uv sync` (after editing pyproject.toml) | | Load platform fixtures | `make loadplatforms` | | Load sample data | `make loadsample` | | Dump games data | `make dumpgames` | ## Architecture A Django 6+ monolith (v1.7.0) with a single app (`games/`) for tracking video game purchases, play sessions, and statistics. Uses HTMX for interactivity with a pure-Python server-side component system, plus a Django Ninja REST API. ### Directory layout ``` games/ — Django app: models, views, templates, forms, signals, tasks, API, filters common/ — Shared utilities: time formatting, component system, criteria, layout, icons timetracker/ — Django project: settings, URL root, ASGI/WSGI tests/ — Pytest tests e2e/ — Playwright browser tests (run via `make test-e2e`) contrib/ — One-off scripts (exchange rate import) docs/ — Additional documentation ``` ### Models (in `games/models.py`) - **Game** — `name`, `platform` (FK), `status` (u/p/f/r/a), `mastered`, `playtime` (DurationField updated via signal), `year_released`, `sort_name`, `wikidata` - **Platform** — `name`, `group`, `icon` (slug, auto-generated from name) - **Purchase** — ownership type, prices, currency conversion (`converted_price`, `price_per_game` is a `GeneratedField`), links to Game via M2M. `num_purchases` counts linked games. DLC/SeasonPass/BattlePass must have a `related_purchase` - **Session** — `timestamp_start`/`timestamp_end`, `duration_manual`, `device` (FK), `note`, `emulated`. `duration_calculated` and `duration_total` are `GeneratedField`s (cannot be written directly) - **Device** — `name`, `type` (PC/Console/Handheld/Mobile/SBC/Unknown) - **PlayEvent** — marks when a game was started/finished (separate from Sessions), `days_to_finish` is a `GeneratedField` - **ExchangeRate** — cached FX rates per currency pair per year - **GameStatusChange** — audit log of status transitions, ordered by `-timestamp` - **FilterPreset** — saved filter configuration; `mode` (games/sessions/purchases/playevents), `find_filter`, `object_filter`, `ui_options` (all JSON). Follows Stash's SavedFilter pattern **Sentinel objects**: `get_sentinel_platform()` returns an "Unspecified" platform used when a Game has no platform. A similar sentinel Device ("Unknown") is created when a Session has no device. **GeneratedField constraint**: `duration_calculated`, `duration_total`, `price_per_game`, `days_to_finish` are computed by the database and cannot be written from application code. ### Key patterns **Layout system** (`common/layout.py`): Views call `render_page(request, content, title=...)` instead of Django's `render()`. This assembles a full HTML document via `Page()` — analogous to FastHTML's `fast_app()`. `Page()` handles the `
`, navbar, toast container, FOUC-prevention script, and **JS includes**: it calls `collect_media(content)` to gather every component's declared `Media` and emits the `