# EarthmanRPG — Project Context Originally built following Harry Percival's *Test-Driven Development with Python* (3rd ed., complete through ch. 25). Now an ongoing game app — EarthmanRPG — extended well beyond the book. ## Browser Integration **Claudezilla** is installed — a Firefox extension + native host for browser automation. See `.claude/skills/claudezilla-browser/SKILL.md` for tool list, startup protocol, and setup reference. **STARTUP RULE:** Call `mcp__claudezilla__firefox_diagnose` at the start of every conversation before any browser tool. If tools aren't listed in a session, open a new Claude Code conversation (MCP servers load at startup only). ## Stack - **Python 3.13 / Django 6.0 / Django Channels** (ASGI via Daphne/uvicorn) - **Celery + Redis** (async email, channel layer) - **django-compressor + SCSS** (`src/static_src/scss/core.scss`) - **Selenium** (functional tests) + Django test framework (integration/unit tests) - **Stripe** (payment, sandbox only so far) - **Hosting:** DigitalOcean staging (`staging.earthmanrpg.me`) | CI: Gitea + Woodpecker ## Project Layout The app pairs follow a tripartite structure: - **1st-person** (personal UX): `lyric` (backend — auth, user, tokens) · `dashboard` (frontend — notes, applets, wallet UI) - **3rd-person** (game table UX): `epic` (backend — rooms, gates, role select, game logic) · `gameboard` (frontend — room listing, gameboard UI) - **2nd-person** (inter-player events): `drama` (backend — activity streams, provenance) · `billboard` (frontend — provenance feed, embeddable in dashboard/gameboard) ``` src/ apps/ lyric/ # auth (magic-link email), user model, token economy dashboard/ # Notes (formerly Lists), applets, wallet UI [1st-person frontend] epic/ # rooms, gates, role select, game logic [3rd-person backend] gameboard/ # room listing, gameboard UI [3rd-person frontend] drama/ # activity streams, provenance system [2nd-person backend] billboard/ # provenance feed, embeds in dashboard/gameboard [2nd-person frontend] api/ # REST API applets/ # Applet model + context helpers core/ # settings, urls, asgi, runner static_src/ # SCSS source templates/ functional_tests/ ``` ### Template directory convention Templates live under `templates/apps//`, not under the backend app that owns the view logic. Specifically: - `lyric/` views → `templates/apps/dashboard/` - `epic/` views → `templates/apps/gameboard/` - `drama/` views → `templates/apps/billboard/` Backend apps (`lyric`, `epic`, `drama`) have **no** `templates/` subdirectory. ## Dev Commands ```bash # Dev server (ASGI — required for WebSockets; no npm/webpack build step) cd src uvicorn core.asgi:application --host 0.0.0.0 --port 8000 --reload --app-dir src # Integration + unit tests (exclude channels) python src/manage.py test src/apps --exclude-tag=channels # Functional tests python src/manage.py test src/functional_tests ``` See `.claude/skills/TDD/SKILL.md` for the full TDD cycle, test file conventions, base classes, and per-layer run commands. See `.claude/skills/dev-server/SKILL.md` for server startup options. ### Multi-user manual testing — `setup_sig_session` Creates (or reuses) a room at `table_status=SIG_SELECT` with all 6 slots filled. Prints one pre-auth URL per gamer. ```bash python src/manage.py setup_sig_session python src/manage.py setup_sig_session --base-url http://localhost:8000 python src/manage.py setup_sig_session --room ``` Fixed gamers: `founder@test.io` (discoman), `amigo@test.io`, `bud@test.io`, `pal@test.io`, `dude@test.io`, `bro@test.io` — all superusers with Earthman deck. URLs use `/lyric/dev-login//` pre-auth pattern. ## CI/CD + Hosting - Git remote: `git@gitea:discoman/python-tdd.git` (port 222, key `~/.ssh/gitea_keys/id_ed25519_python-tdd`) - Gitea: `https://gitea.earthmanrpg.me` | Woodpecker CI: `https://ci.earthmanrpg.me` - Push to `main` triggers Woodpecker → deploys to staging (`staging.earthmanrpg.me`) - Prod deploy: `git tag v1.0.0 && git push --tags` → triggers `deploy-prod` step (tag-based gate) - Two CI pipelines run in parallel: `.woodpecker/main.yaml` (main app) + `.woodpecker/pyswiss.yaml` (PySwiss at charts.earthmanrpg.me) - Multi-browser FTs tagged `@tag("two-browser")` run in a dedicated CI stage (`test-two-browser-FTs`) alongside `--tag=channels`; `test-FTs` stage is parallel-only - Hosting: DigitalOcean — main app on staging droplet; PySwiss on separate droplet (167.172.154.66) - Email: Mailgun (`adman@howdy.earthmanrpg.me`) | DNS: NameCheap ## UI / Layout Conventions ### Sidebar layout (`$sidebar-w: 4rem`) Navbar is a fixed left sidebar; footer is a fixed right sidebar. Both are `4rem` wide. Main container uses `margin-left: $sidebar-w; margin-right: $sidebar-w`. Landscape layout resets `min-width` to `0` on `.gameboard-page` and `#id_dash_content` (override of the `@media (min-width: 738px)` block that sets `min-width: 666px`). ### Applet headings + page titles - Section headings: plain `

` — browser default + body color inherited; no extra SCSS needed - Clickable headings: `

Text

` — global `body a` rule supplies gold + hover glow - Page titles: `Dashsuffix` pattern (Dashwallet, Dashnote, Dashnotes) ### Position vs Seat terminology Circles around the table hex are **positions** (gate slot order, 1–6). After role assignment they become **seats** (PC→NC→EC→SC→AC→BC). CSS carries both: `.table-seat.table-position`. `SLOT_ROLE_LABELS = {1:"PC", 2:"NC", 3:"EC", 4:"SC", 5:"AC", 6:"BC"}` in `epic/views.py`. ## Game Architecture ### Token priority chain `select_token(user)` in `apps.epic.models`: **PASS → COIN → FREE → TITHE → None**. `debit_token` handles each type's consumption rules (Coin cooldown, Free/Tithe expiry). ### Two-step gate token flow Drop → RESERVED → confirm/reject. `_gate_context()` builds slot state; `_expire_reserved_slots()` clears stale reservations after 60s. Views: `confirm_token`, `reject_token` (renamed `return_token`). ### Room URL routing `epic:room` view at `/gameboard/room//`. `gatekeeper` redirects there when `table_status` is set. Error redirects in `select_role`/`select_sig` use `epic:room` if `table_status` is set, else `epic:gatekeeper`. ## SCSS Import Order `core.scss`: `rootvars → applets → base → button-pad → dashboard → gameboard → palette-picker → room → card-deck → natus → tray → billboard → tooltips → game-kit → wallet-tokens` ## Critical Gotchas ### Tooltip portal pattern `mask-image` on grid containers clips `position: absolute` tooltips. Use `#id_tooltip_portal` (`position: fixed; z-index: 9999`) at page root. See `gameboard.js` + `wallet.js`. ### Applet menus + container-type `container-type: inline-size` creates a containing block for all positioned descendants — applet menus must live OUTSIDE `#id_applets_container` to be viewport-fixed. ### ABU session auth `create_pre_authenticated_session` must set `HASH_SESSION_KEY = user.get_session_auth_hash()` and hardcode `BACKEND_SESSION_KEY` to the passwordless backend string. ### Magic login email mock paths - View tests: `apps.lyric.views.send_login_email_task.delay` - Task unit tests: `apps.lyric.tasks.requests.post` - FTs: mock both with `side_effect=send_login_email_task` ### game-kit.js selection persistence `window._kitTokenId` must NOT be cleared on kit-bag close — users close the dialog before clicking the rails button. Selection persists until page navigation. No `clearSelection()` in `game-kit.js`. ### Billboard timezone cookie `document.cookie = 'user_tz=' + Intl.DateTimeFormat().resolvedOptions().timeZone` — **no `encodeURIComponent`**. Slashes in TZ names (`America/New_York`) are cookie-safe; encoding breaks the `ZoneInfo` lookup in `TimezoneMiddleware`. ### CSS `:has()` for child-dependent styling Use `.parent:has(.child-class)` to style a parent based on its contents without template changes. Example: `.gate-slot:has(.drop-token-btn)` makes CARTE OK-button circles match `.reserved` circles. ### Plausible FT noise Plausible analytics script in `base.html` fires a beacon during Selenium tests → harmless console error. Fix: `{% if not debug %}` guard around the script tag. See `.claude/skills/TDD/SKILL.md` for test-specific gotchas (TransactionTestCase flush, static files in tests, Selenium text-transform, multi-browser CI, msgpack integer keys).