8.2 KiB
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 tabmcp__claudezilla__firefox_navigate— navigate to URLmcp__claudezilla__firefox_get_page_state— structured JSON (faster than screenshot)mcp__claudezilla__firefox_create_window— open new tab (returnstabId)mcp__claudezilla__firefox_diagnose— check connection statusmcp__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
- Firefox open with Claudezilla extension active (native host must be running)
- Open a new Claude Code conversation → tools appear as
mcp__claudezilla__firefox_* - Call
firefox_diagnoseto 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.batfile - Registry:
HKCU\SOFTWARE\Mozilla\NativeMessagingHosts\claudezilla→ manifest path - MCP server: registered in
~/.claude.json(NOT~/.claude/settings.jsonor~/.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.jsonpermissions.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/
Dev Commands
# 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
maintriggers 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/<app>/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.