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>
This commit is contained in:
130
CLAUDE.md
130
CLAUDE.md
@@ -3,43 +3,10 @@
|
||||
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.
|
||||
**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.
|
||||
|
||||
### 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`.
|
||||
**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)
|
||||
@@ -87,62 +54,66 @@ Backend apps (`lyric`, `epic`, `drama`) have **no** `templates/` subdirectory.
|
||||
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
|
||||
# Integration + unit tests (exclude channels)
|
||||
python src/manage.py test src/apps --exclude-tag=channels
|
||||
|
||||
# Functional tests only
|
||||
# Functional tests
|
||||
python src/manage.py test src/functional_tests
|
||||
|
||||
# All tests (integration + unit + FT)
|
||||
python src/manage.py test src
|
||||
```
|
||||
|
||||
### Multi-user manual testing — `setup_sig_session`
|
||||
`src/functional_tests/management/commands/setup_sig_session.py`
|
||||
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.
|
||||
|
||||
Creates (or reuses) a room with all 6 gate slots filled, roles assigned, and `table_status=SIG_SELECT`. Prints one pre-auth URL per gamer for pasting into 6 Firefox Multi-Account Container tabs.
|
||||
### 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> # reuse existing room
|
||||
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 created as superusers with Earthman deck equipped. URLs use `/lyric/dev-login/<session_key>/` pre-auth pattern.
|
||||
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.
|
||||
|
||||
**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
|
||||
## 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
|
||||
- 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
|
||||
|
||||
### 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`.
|
||||
|
||||
@@ -157,5 +128,16 @@ Selenium `.text` returns visually rendered text. CSS `text-transform: uppercase`
|
||||
- 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.
|
||||
### 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).
|
||||
|
||||
Reference in New Issue
Block a user