sky_save now re-fetches from PySwiss server-side on save so stored chart_data always carries enriched element format (contributors/stellia/ parades). New sky/data endpoint serves fresh PySwiss data to the My Sky applet on load, replacing the stale inline json_script approach. natus-wheel.js: sign ring slices (data-sign-name) and house ring slices (data-house) now have click handlers with _activateSign/_activateHouse; em-dash fallback added for classic elements with empty contributor lists. Action URLs sky/preview, sky/save, sky/data lose trailing slashes. Jasmine: T12 sign tooltip, T13 house tooltip, T14 enriched element contributor display (symbols, Stellium/Parade formations, em-dash fallback). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
144 lines
8.4 KiB
Markdown
144 lines
8.4 KiB
Markdown
# 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 `/lyric/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, 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/<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).
|