Files
python-tdd/CLAUDE.md
Disco DeDisco b8ac004fb6
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
sky wheel: element contributor display; sign + house tooltips — TDD
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>
2026-04-21 20:07:40 -04:00

8.4 KiB
Raw Blame History

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

# 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.

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, 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.

document.cookie = 'user_tz=' + Intl.DateTimeFormat().resolvedOptions().timeZoneno 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).