Files
python-tdd/CLAUDE.md
Disco DeDisco 599d40decd auth urls: mount apps.lyric.urls under /dashboard/ to mirror gameboard/epic & billboard/drama convention
- core/urls.py: replace `path('lyric/', …)` with second `path('dashboard/', include('apps.lyric.urls'))` alongside existing dashboard mount; no path-name collision (lyric paths: send_login_email, login, logout, dev-login/<key>/)
- IT test URL strings flipped /lyric/ → /dashboard/ (test_views.py)
- setup_sig_session + setup_sea_session pre-auth URL builders updated
- CLAUDE.md doc note updated
- Templates use unnamespaced `{% url 'logout' %}` / `{% url 'send_login_email' %}` so they auto-resolve; no template edits needed
- /admin/lyric/user/ admin URL untouched (driven by app_label, not URL conf)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:18:36 -04:00

144 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/<frontend-app>/`, 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 <uuid>
```
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 `/dashboard/dev-login/<session_key>/` 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 `<h2>` — browser default + body color inherited; no extra SCSS needed
- Clickable headings: `<h2><a href="...">Text</a></h2>` — global `body a` rule supplies gold + hover glow
- Page titles: `<span>Dash</span>suffix` pattern (Dashwallet, Dashnote, Dashnotes)
### Position vs Seat terminology
Circles around the table hex are **positions** (gate slot order, 16). 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/<uuid>/`. `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).