Files
python-tdd/CLAUDE.md
Disco DeDisco 2fd3ec9ab2
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
added header_text to billboard.html; restored L+R .container padding after last fix (still 0 T+B)
2026-03-22 15:06:54 -04:00

7.1 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 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 session to pick them up.

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: ~/.claude/settings.json and ~/.claude/mcp.json — both register node E:/ClaudeLibrary/claudezilla/mcp/server.js
  • Permission: mcp__claudezilla__* in ~/.claude/settings.json permissions.allow

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 main triggers Woodpecker → deploys to staging

SCSS Import Order

core.scss: rootvars → applets → base → button-pad → dashboard → gameboard → room → palette-picker → 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.