# 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 that lets Claude observe and interact with the browser directly. ### Tool names Tools are available as `mcp__claudezilla__firefox_*`, e.g.: - `mcp__claudezilla__firefox_screenshot` — capture current tab - `mcp__claudezilla__firefox_navigate` — navigate to URL - `mcp__claudezilla__firefox_get_page_state` — structured JSON (faster than screenshot) - `mcp__claudezilla__firefox_create_window` — open new tab (returns `tabId`) - `mcp__claudezilla__firefox_diagnose` — check connection status - `mcp__claudezilla__firefox_set_private_mode` — disable private mode to use session cookies All tools require a `tabId` except `firefox_create_window` and `firefox_diagnose`. ### If tools aren't available in a session MCP servers load at session startup only. **Start a new Claude Code conversation** (hit "+" in the sidebar) — no need to reboot VSCode, just open a fresh chat. Always call `firefox_diagnose` first to confirm the connection is live. ### Correct startup sequence 1. Firefox open with Claudezilla extension active (native host must be running) 2. Open a new Claude Code conversation → tools appear as `mcp__claudezilla__firefox_*` 3. Call `firefox_diagnose` to confirm before depending on any tool ### Setup (already done — for reference) The native messaging host requires a `.bat` wrapper on Windows (Firefox can't execute `.js` directly): - Wrapper: `E:\ClaudeLibrary\claudezilla\host\claudezilla.bat` — contains `@echo off` / `node "%~dp0index.js" %*` - Manifest: `C:\Users\adamc\AppData\Roaming\claudezilla\claudezilla.json` — points to the `.bat` file - Registry: `HKCU\SOFTWARE\Mozilla\NativeMessagingHosts\claudezilla` → manifest path - MCP server: registered in `~/.claude.json` (NOT `~/.claude/settings.json` or `~/.claude/mcp.json`) — use the CLI to register: ``` claude mcp add --scope user claudezilla "D:/Program Files/nodejs/node.exe" "E:/ClaudeLibrary/claudezilla/mcp/server.js" ``` - Permission: `mcp__claudezilla__*` in `~/.claude/settings.json` `permissions.allow` **Config file gotcha:** The Claude Code CLI and VSCode extension read user-level MCP servers from `~/.claude.json` (home dir, single file) — NOT from `~/.claude/settings.json` or `~/.claude/mcp.json`. Always use `claude mcp add --scope user` to register; never hand-edit. Verify registration with `claude mcp list`. **BOM gotcha:** PowerShell writes JSON files with a UTF-8 BOM, which causes `JSON.parse` to throw. Never use PowerShell `Set-Content` to write any Claude config JSON — use the Write tool or the CLI instead. Native host: `E:\ClaudeLibrary\claudezilla\host\`. Extension: `claudezilla@boot.industries`. ## 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 only (from project root — targets src/apps, skips src/functional_tests) python src/manage.py test src/apps # Functional tests only python src/manage.py test src/functional_tests # All tests (integration + unit + FT) python src/manage.py test src ``` **Test tags:** The only tag in use is `channels` — for async consumer tests that require a live Redis channel layer. - Exclude channels tests: `python src/manage.py test src/apps --exclude-tag=channels` - Channels tests only: `python src/manage.py test src/apps --tag=channels` FTs are isolated by **directory path** (`src/functional_tests/`), not by tag. Test runner is `core.runner.RobustCompressorTestRunner` — handles the Windows compressor PermissionError by deleting stale CACHE at startup + monkey-patching retry logic. ## CI/CD - 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 ## SCSS Import Order `core.scss`: `rootvars → applets → base → button-pad → dashboard → gameboard → palette-picker → room → billboard → game-kit → wallet-tokens` ## Critical Gotchas ### TransactionTestCase flushes migration data FTs use `TransactionTestCase` which flushes migration-seeded rows. Any IT/FT `setUp` that needs `Applet` rows must call `Applet.objects.get_or_create(slug=..., defaults={...})` — never rely on migration data surviving. ### Static files in tests `StaticLiveServerTestCase` serves via `STATICFILES_FINDERS` only — NOT from `STATIC_ROOT`. JS/CSS that lives only in `src/static/` (STATIC_ROOT, gitignored) is a silent 404 in CI. All app JS must live in `src/apps//static/` or `src/static_src/`. ### msgpack integer key bug (Django Channels) `channels_redis` uses msgpack with `strict_map_key=True` — integer dict keys in `group_send` payloads raise `ValueError` and crash consumers. Always use `str(slot_number)` as dict keys. ### Multi-browser FTs in CI Any FT opening a second browser must pass `FirefoxOptions` with `--headless` when `HEADLESS` env var is set. Bare `webdriver.Firefox()` crashes in headless CI with `Process unexpectedly closed with status 1`. ### Selenium + CSS text-transform Selenium `.text` returns visually rendered text. CSS `text-transform: uppercase` causes `assertIn("Test Room", body.text)` to fail. Assert against the uppercased string or use `body.text.upper()`. ### 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` ## Teaching Style User prefers guided learning: describe what to type and why, let them write the code, then review together. But for now, given user's accelerated timetable, please respond with complete code snippets for user to transcribe directly.