Commit Graph

9 Commits

Author SHA1 Message Date
Disco DeDisco
5e78e6b832 A.3 my_sign.html image-rendering — first visible surface — TDD. Sprint A.3 of [[project-image-based-deck-face-rendering]]. When the user's equipped deck has has_card_images=True (Minchiate Fiorentine 1860-1890 today), the saved-sig stage card on /billboard/my-sign/ renders as an <img> over the irregular-shape transparent PNG with a contour-following arcana-colored stroke — not the text fan-card scaffold. First of 6 surfaces in the image-rendering rollout (my_sea + both billboard applets + room + game_kit follow in A.5+). New TarotCard.image_url property (consumes A.2's image_filename + DeckVariant.has_card_images + django.templatetags.static.static() to produce a full static-asset URL) — empty string when has_card_images=False so legacy text-only decks (Earthman, RWS) pass through transparently. my_sign.html picker grid .sig-card elements gain data-image-url + data-arcana-key attrs (the latter for stroke-color CSS selection); the .sig-stage-card scaffold gains a hidden <img class="sig-stage-card-img"> slot that JS swaps visible when image-mode is active. stage-card.js extends fromDataset to read image_url + arcana_key; new _setImageMode(stageCard, card) toggles the .sig-stage-card--image marker class + sets data-arcana-key on the stage card + populates the img src/alt; called from populateCard so all existing sig-stage flows pick up image rendering automatically (text-mode decks still pass through since image_url is empty). SCSS: new .sig-stage-card.sig-stage-card--image rule hides the .fan-card-corner + .fan-card-face text scaffold, strips the rectangular border/padding, and applies a 4-cardinal-direction filter: drop-shadow() stack to the <img> so the stroke FOLLOWS the alpha contour of the PNG instead of tracing a rectangular bounding box (per user spec 2026-05-25 PM clarification — early draft used a rectangular border which doesn't match the irregular-card aesthetic). Stroke color is driven by a CSS custom prop --img-stroke-color defaulting to rgba(var(--quiUser), 1) (cream — minor + middle arcana); [data-arcana-key="MAJOR"] override flips it to rgba(var(--terUser), 1) (gold) per Q2 lock. mobile-safe — filter on raster images works cross-browser (the [[feedback-mobile-svg-glow]] dead-end was specifically SVG glow, not raster drop-shadows). New _seed_minchiate_image_fixtures() helper in functional_tests/sig_page.py re-seeds the minimal Minchiate fixture (DeckVariant + Il Matto + Papa Uno) needed for image FTs after TransactionTestCase's flush wipes migration data — mirrors the existing _seed_earthman_sig_pile pattern per [[feedback-transactiontestcase-flush]]. New MySignImageRenderingTest.test_saved_sig_renders_as_img_for_image_deck FT seeds Minchiate + creates a superuser test gamer (superuser auto-gets super-nomad + super-schizo Notes via the User post_save signal, which _filter_major_unlocks then lets through to expose Il Matto in the picker grid — otherwise Minchiate's sig pool is empty since it has no MIDDLE arcana cards), equips Minchiate, saves Il Matto as sig, visits /billboard/my-sign/, asserts the stage card displays + contains an <img> w. src ending in the v2-convention filename minchiate-fiorentine-1860-1890-trumps-00-il-matto.png + carries .sig-stage-card--image marker class. Out of scope for this commit (deferred to A.3 follow-up polish + A.5+): the full stat-block restructure (top-left rank+suit chip Q♥ inline w. EMANATION/REVERSAL header; title in arcana-color font; keyword reposition; FYI panel re-anchor — per the locked Q3 spec) — image card-face ships now w. the existing stat-block layout to land the visible-win first. Tests: 1 new FT green; 15/15 my_sign FT class green (no regression on the 14 existing tests); 1289/1289 IT+UT total green (68s, unchanged from A.2 since no new ITs in this commit — FT covers the wiring end-to-end). Sprint A backend foundation (A.0+A.1+A.2) + first visible surface (A.3) all landed; 5 surfaces remain (A.5-A.8 + A.4's card-deck icon)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:04:18 -04:00
Disco DeDisco
f6093136f1 fix: shop tooltip price flex-pinned right (cross-file #id_tooltip_portal .tt-title { display: block } was clobbering the flex h4) + My Sign page collapses to read-only card+stat-block when sig is saved + My Sign applet card gets proper 5:8 shell + Game Kit row space-evenly. Five visual polish items batched.
(1) **Shop tooltip price right-align — root-cause fix.** Earlier today's `feat: shop tooltip price moves to the title row` (commit e90f10f) left `.tt-price` visually adjacent to the name instead of pinned right despite `<h4 class="tt-title">` carrying flex+space-between in `_tooltips.scss:38-46`'s `.token-tooltip, .tt { h4 { ... } }` block. **The bug was in a totally unrelated file**: `_palette-picker.scss:88-95` opens `#id_tooltip_portal { .tt-title, .tt-description, .tt-date, .tt-lock { display: block; } }` for the palette swatch tooltip — `#id_tooltip_portal .tt-title` has specificity **1,1,0** which beats `.token-tooltip h4`'s **0,1,1**, ID wins regardless of source order. The palette tooltip's h4 only carries a text node (no flex children) so `block` vs `flex` looked identical for that surface + the rule sat there as quiet defensive scaffolding for months. The wallet Shop's two-`<span>` h4 finally exercised it. Fix: drop `.tt-title` from the palette override list (left `.tt-description`/`.tt-date`/`.tt-lock` alone — those are `<p>` siblings, already block, redundant but harmless). Also keeps `margin-left: auto !important` on `.tt-price` (today's earlier failed-first-attempt fix) — now redundant w. the flex parent's space-between but documents intent + survives any future flex-direction tweak. Generalizable trap: an ID-scoped child rule in any consumer's SCSS file can silently override shared base rules for every consumer of that portal.

(2) **Game Kit row space-evenly.** `#id_game_kit` in `_gameboard.scss:61-72` was `justify-content: center` + `gap: 0.75rem` so 7 trinket icons clumped left-of-center. Switched to `justify-content: space-evenly` (no gap) — matches the established convention in `.token-row` (`_wallet-tokens.scss:46`) + `.shop-grid` (`_wallet-tokens.scss:92`) which both space-evenly the wallet's parallel rows. Items now spread across the applet width same as the wallet's tokens row + shop row.

(3) **My Sign page collapses to read-only when sig is committed.** Per user spec — once a sig is saved, the SCAN SIGN btn + table hex + chair are meaningless (you can't draw a new sig until you DEL the current one) so the landing renders only the saved-sig preview on the stage + the DEL btn pinned bottom-right. `my_sign.html:91-110` wraps the `.room-shell > .room-table > ... > #id_scan_sign_btn + .table-seat` chain in `{% if not current_significator %}`. `.my-sign-clear-form` stays unconditional (its own `{% if current_significator %}` block) — its `position: absolute; bottom: 0.75rem; right: 1rem` against the now-empty `.my-sign-landing` (which keeps `position: relative` from `_card-deck.scss:671`) lands the DEL in the same bottom-right corner whether the hex is present or not.

(4) **Stat block reveals next to saved-sig preview on landing.** `_populateStage(savedCardEl)` on init was filling the stage card data but `.sig-stat-block` stayed `display: none` because only `.sig-stage--frozen` (added by JS on OK-confirm in picker phase) reveals it via `_card-deck.scss:609`'s `&.sig-stage--frozen .sig-stat-block { display: block; }`. Added `stage.classList.add('sig-stage--frozen');` after the saved-sig `_populateStage` call at `my_sign.html:386`. Stat block now sits flex-row-adjacent to the stage card (stage's `flex-direction: row` + `gap: 0.75rem` from `_card-deck.scss:505-508` does the layout work) — emanation keywords + SPIN + FYI all visible alongside the saved card.

(5) **My Sign applet card — proper 5:8 card shell.** The applet's `<div class="my-sign-applet-card">` markup (corner-rank top-left + name) was rendering bg-less + collapsed to the applet's top-left corner because **no SCSS rule existed** for `.my-sign-applet-card` / `.my-sign-applet-body` / `.my-sign-applet-empty`. Added `#id_applet_my_sign` block at `_billboard.scss:434+` — scaled-down clone of `.sig-stage-card`'s shape language: `--applet-card-w: 5rem` knob drives all child sizing via the same calc-fractions used by `.sig-stage-card`'s `--sig-card-w`, 5:8 aspect-ratio, `--priUser` bg, `--secUser` border, corner-rank absolute top-left, `.fan-card-name` flex-centered, `&.stage-card--reversed { transform: rotate(180deg); }` for the reversed-sig case. `.my-sign-applet-body` flex-centers the card in the 4×6 applet aperture; `.my-sign-applet-empty` flex-centers the 'No sign chosen yet.' empty state. Layered visually consistent w. the room sig-select card + Shop tiles.

(6) **Misc visual cleanup bundled in.** `#id_scan_sign_btn` in `_card-deck.scss:677` lost its `font-size: 0.75rem` + `line-height: 1.1` overrides — the default `.btn-primary` sizing scales fine w. the 4rem circle now that the SCAN/SIGN wordmark fits cleanly w. just `white-space: normal`. `.tt-buy-btn` lost `line-height: 1.1` in `_wallet-tokens.scss:156` — Shop microbutton renders cleanly w. the default.

**TDD coverage**: `test_landing_previews_saved_sig_on_stage` in `test_bill_my_sign.py` rewritten to match the new contract (stage frozen → stat block visible, no SCAN SIGN btn, no `.table-hex` when sig is saved); other 28 my-sign FTs unaffected (they exercise the no-sig path which still renders the hex + SCAN SIGN). 3 traps caught + linked in memory: [[feedback-cross-file-id-scoped-override]] (this commit's #1), the pre-existing [[feedback-margin-auto-needs-flex-parent]] (correctly predicted today's bug — `!important` ladder was a tell to audit the parent's cascade), [[feedback-scss-import-order-specificity]] (related but different: same-specificity source order; this one was specificity-driven w. source order irrelevant).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:42:03 -04:00
Disco DeDisco
4d1c74a2af FT helper: sig_page.py — _seed_earthman_sig_pile + _assign_sig — Sprint 4c of My Sea roadmap — TDD
Pure test-infra refactor. Sprint 5+ (sea-select / DRAW SEA / latest-draw rendering) all need a "user has sig" precondition without walking the picker — extracting it once now prevents Sprint 5's setUps from copy-pasting the inline assignment dance again.

Two helpers in new `src/functional_tests/sig_page.py` (mirrors the `room_page.py` / `post_page.py` / `my_posts_page.py` convention — underscored to signal "test infrastructure, not API surface", public within `functional_tests/`):

- `_seed_earthman_sig_pile()` — re-seeds Earthman DeckVariant + the 16 MIDDLE court cards (Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS) that `personal_sig_cards(user)` returns. Hoisted verbatim from the duplicate definitions in `test_bill_my_sign.py` + `test_game_my_sea.py` introduced in [[sprint_serialized_rollback_ft_fix_may19]]. Major 0/1 are deliberately NOT seeded — `_filter_major_unlocks` in `personal_sig_cards()` strips them for users w.o the matching Note unlocks, which is the default state in solo FTs.
- `_assign_sig(user, card=None, reversed_flag=False)` — sets `user.significator` + `significator_reversed` directly, bypassing the picker UI. Returns the assigned card so downstream assertions can use it. `card=None` defaults to `personal_sig_cards(user)[0]` (the same target the picker happy-path FT uses).

Call sites updated:
- `test_bill_my_sign.py` — drops the local `_seed_earthman_sig_pile` definition (32 lines); imports from `sig_page`. `MySignClearTest.setUp` now uses `_assign_sig(self.gamer)` instead of the 4-line manual sig-assignment block.
- `test_game_my_sea.py` — drops the local `_seed_earthman_sig_pile` definition (22 lines); imports from `sig_page`. `MySeaSignGateTest`'s two "user w. sig" tests (#4 + #6) swap their 2-line `self.gamer.significator = ... ; .save(...)` blocks for `_assign_sig(self.gamer, self.target_card)`.

Diff stat: +69 lines (new helper module), -59 lines (duplicate code removed). Net +10 LOC but the duplication trap is closed — single source of truth for sig-state FT setup.

20/20 FT green across both files in 174s post-refactor.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:22:49 -04:00
Disco DeDisco
c8a603484e My Sign DEL btn: clear-sign affordance on SCAN SIGN landing — Sprint 4b-adjacent of My Sea roadmap — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
Pre-spec'd in [[sprint-my-sea-sign-gate-may19]] as the unblocker for tomorrow's visual verification of 4b's no-sig branch — admin user (@disco) had a saved sig from Sprint 4a testing & there was no in-UI affordance to undo it short of DB surgery. Lands ahead of the deferred 4b visual verify so dev users can toggle between sig/no-sig states on Claudezilla.

- Endpoint: `path("my-sign/clear", views.clear_sign, name="clear_sign")` — POST sets `User.significator = None` + `significator_reversed = False`, redirects to picker; GET is a no-mutation redirect to picker (mirrors save_sign's GET handling). `login_required(login_url="/")`. No trailing slash per [[feedback_url_convention_actions_no_trailing_slash]] (action endpoint, not page).
- Template (my_sign.html): `<form id="id_clear_sign_form" class="my-sign-clear-form">` w. `<button id="id_clear_sign_btn" class="btn btn-danger">DEL</button>`, rendered ONLY when `current_significator` is set; sits inside `.my-sign-landing` as a sibling of `.room-shell` so it's bound to the landing-phase UI alone (picker phase already has its own NVM unlock affordance on focused thumbnails).
- SCSS: anchored bottom-right of `.my-sign-landing` via `position: absolute; bottom: .75rem; right: 1rem` — `.my-sign-landing` gains `position: relative` to scope the absolute. `.btn-danger` carries the destructive treatment; "DEL" mirrors post.html gear menu's DEL convention from [[sprint-post-polish-may13]].
- 3 FTs in new `MySignClearTest` class — covers: btn renders on landing when sig saved (T1, asserts text "DEL" + `.btn-danger` class); btn absent when no sig (T2); click POSTs, reloads, & wipes `User.significator` + `significator_reversed` in DB (T3).
- 6 ITs in new `ClearSignViewTest` + `MySignClearAffordanceTemplateTest` — covers: login_required gate, POST wipes both fields w. redirect-back, GET redirects w.o mutation, POST-w/o-existing-sig is idempotent no-op, template renders btn only when sig set, template's form action targets `clear_sign` reverse.
- 1029 IT/UT green in 47s (+6 from baseline); 20/20 FT green across test_bill_my_sign + test_game_my_sea in 165s.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:13:20 -04:00
Disco DeDisco
a636e940b7 fix CI FT regression: My Sign + My Sea setUpClass ContentType collision — pipeline #313
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
Sprint 4a-cont (400762c) + 4b (cd0add1) introduced `serialized_rollback = True` on `MySignPickerTest`, `MySignBackupDeckTest`, and `MySeaSignGateTest` to keep migration-seeded `DeckVariant` + `TarotCard` rows alive across TransactionTestCase flushes. Locally each file ran clean, but in the full pipeline #313 `test-FTs-non-room` stage all three classes errored in setUpClass:

  django.db.utils.IntegrityError: UNIQUE constraint failed:
  django_content_type.app_label, django_content_type.model

Mechanism: every prior `TransactionTestCase`-derived class in the bucket flushed without `inhibit_post_migrate`, so Django's post-migrate signal recreated `django_content_type` rows. When these three classes hit `_fixture_setup` & tried to deserialize the saved DB snapshot, the inserts collided on `(app_label, model)`. No serialized rollback ⇒ no collision; mixing in the same DB run is the trap.

Fix: drop `serialized_rollback = True` from all 3 classes & inline-reseed via `get_or_create` per the canonical pattern already used by [test_admin_tarot.py](src/functional_tests/test_admin_tarot.py) & [room_page.py](src/functional_tests/room_page.py) (see [[feedback_transactiontestcase_flush]]) — new `_seed_earthman_sig_pile()` module-level helper in each file restores `DeckVariant(slug='earthman')` + the 16 MIDDLE court cards (Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS) that `personal_sig_cards(user)` returns.

Adjacent bug uncovered once the snapshot reload was gone: [test_game_my_sea.py](src/functional_tests/test_game_my_sea.py) `_seed_gameboard_applets` had been seeding `my-palette` w. `context='gameboard'` — but `my-palette` is not a gameboard applet (it's a dashboard slug; the real palette applet is `palette` per migration 0003). The applets template iterates every applet & includes `apps/<context>/_partials/_applet-<slug>.html`, so seeding the bogus row made /gameboard/ try to load a partial that doesn't exist → TemplateDoesNotExist 500 → `#id_applet_my_sea` never rendered. `serialized_rollback = True` had been masking this because the snapshot restored the migration-correct applet rows (which never had my-palette as a gameboard entry to begin with). Swapped `my-palette` for the real `new-game` gameboard applet & corrected grid-cols/rows on `game-kit` to match the 0003 seed (4×3, not 4×6).

Tests: full sweep across all three classes runs green locally (17/17 in 142s).

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:05:39 -04:00
Disco DeDisco
5b06d902a8 My Sign picker iteration 3 — SCAN SIGN landing hex + OK/NVM thumbnail two-step + duoUser picker bg — Sprint 4a-cont — TDD
Two-phase picker. Landing phase renders the DRY 1-chair table hex w. a central SCAN SIGN .btn-primary; clicking it swaps the page to picker phase (hex hides, sig-card grid + always-present stage frame + SAVE SIGN visible). Stage frame previews the saved sig on landing if User.significator is set ; sig-card selection lifts the room's two-step OK/NVM-on-thumbnail pattern via `.sig-card-actions` w. `.sig-ok-btn`/`.sig-nvm-btn`: click thumb → `.sig-focused` (CSS reveals OK badge, stage previews card, no lock); click OK → `.sig-reserved--own` (CSS swaps OK→NVM badge, `.sig-stage--frozen` reveals stat block + FLIP, SAVE SIGN enables); click NVM → unlock + clear focus + disable SAVE SIGN ; SAVE SIGN form pinned `position:absolute; bottom:0.75rem; right:1rem` to .my-sign-stage so it stops shifting across the stage row when the stat block reveals on lock (was getting shoved left as a flex item alongside the stat-block reveal) ; .my-sign-page mirrors .room-page's `flex:1; min-height:0; display:flex; flex-direction:column` so the DRY hex container chain propagates real height down into #id_game_table for room.js's scaleTable() to compute against (was reading 0 + leaving the hex unscaled at 200×231 in a 360×320 scene) ; stage min-height gated to picker phase (`.my-sign-page[data-phase="picker"] .my-sign-stage`) — landing-phase stage is natural-sized so the hex centers in the bigger available area instead of being bottom-anchored by a 376px stage reservation ; picker-phase bg uses `rgba(var(--duoUser), 1)` so the transition from "hex face" → "card pile on felt" reads as a continuous surface rather than a context swap ; room sig-select media queries re-scoped to `.sig-overlay .sig-deck-grid` so they don't bleed into my-sign — my-sign gets its own breakpoint cascade: 6×3rem (portrait) → 9×3rem (≥900px landscape) → 18×3rem (≥1600px) → 18×5rem (≥2200px); thresholds bumped from sig-select's 1400/1800px so 18×col + sidebar/footer margins clear the viewport at fluid-rem ceiling (rem=22 → 18×3rem=1188px + 220px margins=1408, safe with 1600px floor) ; default `repeat(6, 1fr)` collapsed to 0-width when paired w. `align-self:center` (no parent width for `fr` to resolve against, hence the dotted-line miniscule cards in portrait); fixed `repeat(6, 3rem)` at portrait default fixes it ; SCAN SIGN font-size 0.75rem (vs .btn-primary's default 0.875rem) so the 2-line "SCAN/SIGN" label fits inside the 4rem circle without crowding the border — treated as a smaller variant via `#id_scan_sign_btn` rule scoped under .my-sign-landing ; room.js's scaleTable() runs on DOMContentLoaded before flex layout flushes (#id_game_table.clientWidth/Height read 0 at that moment) — added `requestAnimationFrame → dispatchEvent('resize')` tick at the end of the inline IIFE so scaleTable re-fires once layout settles ; tests — 6 FTs in test_bill_my_sign.py rewritten for the new flow: test_landing_renders_dry_hex_with_scan_sign_button pins the 1-chair hex + central SCAN SIGN + hidden picker grid; test_scan_sign_click_transitions_to_picker_phase pins the phase swap (hex hides, grid shows); test_click_thumbnail_shows_OK_btn_without_locking pins step 1 (focus + OK appears, no lock yet); test_OK_click_locks_thumbnail_and_enables_save_sign pins step 2 (lock + NVM appears + SAVE SIGN enables + persists to /billboard/ applet); test_NVM_click_deselects_and_disables_save_sign pins NVM unlock cycle; test_landing_previews_saved_sig_on_stage pins the on-load saved-sig preview behavior — all green visually verified across portrait + landscape

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 01:11:41 -04:00
Disco DeDisco
39767c72c2 fix CARTE multi-seat Role-Select bug on navigate-away + back; My Sign applet rename
**CARTE bug** (user-reported on iPhone): a CARTE gamer who contributed their deck to multiple gate slots could fill ≥1 role for ≥1 seat, navigate away (BYE → dashboard, CONT GAME → return, etc.), come back to the room — and the JS guard on .card-stack would wrongly fire "Equip card deck before Role select" + block further role picks, even though the deck was demonstrably in play on existing seats. Symmetric for the "stay in room during Role Select" variant the user thought we'd squashed before (the prior fix was 759ce8d for the multi-slot SELECT path, but the room VIEW context never got the same treatment) ; **root cause**: `select_role()` at epic/views.py:619-621 clears `user.equipped_deck` after the first role pick ("deck committed to room"). The room view's role-select context at epic/views.py:286 then passes `equipped_deck_id = user.equipped_deck_id` to the template — which is now None — and the template renders `data-equipped-deck=""` → JS guard at role-select.js:165 sees the empty string and fires the "no deck" warning. The deck IS in play; the context just isn't recognizing seat-level deck assignment as a deck source ; **fix** (epic/views.py:286ish): when `user.equipped_deck_id` is None, fall back to the deck_variant of any of the user's seats in this room (order_by slot_number for determinism). The guard now sees a non-empty id and the fan opens. Storage-side unchanged — seat.deck_variant remains the canonical "this deck is in play on this seat" signal, and the user's deck-third contribution per role (PC=levity brands+crowns / NC=levity trumps / SC=levity grails+blades / AC=gravity grails+blades / EC=gravity trumps / BC=gravity brands+crowns) flows from existing `select_role` logic that inherits deck_variant from the first seat ; **TDD trail** — 2 new ITs in `SelectRoleMultiSeatTest` (apps.epic.tests.integrated.test_views): T1 pins the context (`response.context["equipped_deck_id"]` equals the existing seat's deck_variant_id after `user.equipped_deck` clears); T2 pins the template (rendered `data-equipped-deck="<id>"` not `""`). Initial reds — `None != 2` + `data-equipped-deck=""` substring assertion. Fix lands both green ; **bundled: My Sign applet rename** — user clarified naming convention 2026-05-18: **applets** use the "My X" prefix (My Sign, My Sea, My Posts), **standalone pages** use the "Game/Dash/Bill X" prefix (Game Sign page, Game Sea page, Game Kit page). Sprint 4a's initial migration set the applet name to "Game Sign" — corrected after the user saw the gear-menu toggle list reading the wrong word. Applet template header link "Game Sign" → "My Sign" (user-edited); migration 0010 added to update the Applet row's `name` in already-migrated DBs (dev + staging); applets/0009 frontmatter + defaults updated to "My Sign" in case of a fresh migrate-from-zero; test seed helpers in billboard test_views.py + functional_tests/test_bill_my_sign.py updated to "My Sign". Slug stays `my-sign` (URL + selectors stable) ; **bundled: rootvars.scss** — user-modified mid-session (pre-staged) ; 1022 IT/UT green in 46s — no regressions; 4 ITs in SelectRoleMultiSeatTest green (2 pre-existing CARTE multi-seat ITs + 2 new return-trip context ITs)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:18:32 -04:00
Disco DeDisco
66b2947e8c My Sign: Brief banner + Earthman [Shabby Cardstock] backup deck when no equipped — TDD
User-reported gap on /billboard/my-sign/ — admin user's only deck was in-use as `TableSeat.deck_variant` in another room (Wonderbeard) → `equipped_deck` cleared → previous my-sign template showed "Equip a card deck first…" w. no actionable next step. User scoped fix: don't force equip, just nudge via a Brief banner "Look!—no deck is equipped. Navigate to the Game Kit to equip one (FYI) or (NVM) proceed with the Earthman [Shabby Cardstock] deck.", title "Default deck warning". NVM dismisses + picker proceeds against an Earthman card pile labeled in-copy as the temporary backup; FYI links to /gameboard/ (Game Kit equip). User-modified line text in template: "Paperboard" → "Cardstock" mid-session ; **helper fallback** (epic/models.py): `personal_sig_cards(user)` now falls back to `DeckVariant.objects.filter(slug='earthman').first()` when `user.equipped_deck` is None — same 16-or-18 card pile, just sourced from the canonical Earthman deck rather than the empty FK. No new DeckVariant row needed; "Shabby Cardstock" is purely UX framing (cards are the same TarotCard records the room sig-select uses). Preserves the existing helper signature so no callers had to change ; **view + template** (billboard/views.py + my_sign.html): view passes `no_equipped_deck` + `show_backup_intro_banner` flags. Template removes the old `{% if not equipped_deck %}` forced-equip branch — picker now renders unconditionally w. cards from the backup helper when no deck is equipped. Brief banner fires via `Brief.showBanner({...})` on DOMContentLoaded when `show_backup_intro_banner` is true — gets h2-overlay positioning + NVM behavior + portal styling for free (per [[sprint-baltimorean-note-unlock-may18]] portrait h2 measurement in note.js's `_alignToH2`). Added `<script src="note.js">` to my_sign.html since the page didn't load it before. Post-render JS tags the Brief w. a `.my-sign-intro-banner` class so FTs (and any future my-sign-specific styling) can distinguish this nudge from other Briefs on the page ; **TDD trail** — 4 new FTs in `MySignBackupDeckTest` (test_bill_my_sign.py): T1 banner renders w. "Default deck warning" title + "no deck is equipped" + "Shabby Cardstock" copy + both action btns visible; T2 picker still populates 16 cards from backup; T3 NVM click removes the banner from the DOM; T4 FYI href ends w. /gameboard/. Initial reds (`NoSuchElementException` on all 4) confirmed before implementation. Plus 1 new IT in `PersonalSigCardsTest` pinning the helper fallback (16 cards w. all `c.deck_variant.slug == "earthman"`) ; pre-existing change picked up: `static_src/scss/rootvars.scss` (user-modified mid-session) ; 1020 IT/UT green; 7 FTs green (3 picker happy-path + 4 backup deck) in 56s. Sprint 4a-follow complete — primary deferral from Sprint 4a (deck-source fallback UX) now landed. Unblocks Sprint 4b (My Sea gating w. --terUser link to /billboard/my-sign/ when no sig set)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:49:49 -04:00
Disco DeDisco
400762c0e5 Game Sign picker @ /billboard/my-sign/ + billboard applet — Sprint 4a of My Sea roadmap — TDD
User scope (per design conv this session): split the room's sig-select responsibility off into a standalone billboard-context "My Significator" applet — branded "Game Sign" on the surface. Same 18-card pile as room sig-select (16 middle arcana + Major 0 & 1 filtered by Note unlocks); polarity collapses to a single FLIP choice (the FLIP btn in the picker carousel toggles User.significator_reversed). Selection persists globally on the User model + propagates to the billboard's Game Sign applet ; **naming convention locked**: "significator" stays at storage (User.significator FK + User.significator_reversed) + room sig-select context (DRY w. existing template/JS); "Sign" / "Game Sign" is the billboard-surface branding (file my_sign.html, URL /billboard/my-sign/, URL names my_sign + save_sign, applet name "Game Sign", page wordmark "Game Sign", btn label SAVE SIGN). Action URLs don't carry a trailing slash per project convention (/billboard/my-sign/save vs the page's /billboard/my-sign/) ; **schema**: User gains 2 fields — `significator: FK → epic.TarotCard (nullable, on_delete=SET_NULL)` + `significator_reversed: BooleanField(default=False)`. Migration lyric/0006_user_significator_user_significator_reversed.py auto-generated; reversible. Applet seed in applets/0009_seed_my_sig_applet.py adds the row (slug='my-sign', name='Game Sign', context='billboard', default_visible=True, grid_cols=4, grid_rows=6), idempotent update_or_create, reversible unseed() ; **picker page** (my_sign.html): solo lift of `_sig_select_overlay.html` — sig-stage-card scaffold + sig-stat-block + 18-card grid + SAVE SIGN form. Stripped: countdown / WebSocket / polarity / multi-user / reservations. Empty-state branch covers no-equipped-deck (link back to Game Kit; full Brief-redirect + Earthman-Backup fallback deferred to a follow-up sub-sprint). Minimal inline JS: click .sig-card → mark .sig-focused + set hidden card_id + enable SAVE SIGN; FLIP btn toggles .is-reversed + the hidden reversed input. Stage-card preview (name/qualifier population + keyword swap on FLIP) deferred — Sprint 4a follow-up will lift stage-card.js's populator into a non-room context ; **applet partial** (_applet-my-sign.html): renders user.significator's corner-rank + suit-icon + name_title if set; `.my-sign-applet-empty` "No sign chosen yet." otherwise. Header `<h2><a href="{% url 'billboard:my_sign' %}">Game Sign</a></h2>` links to the picker ; **helper refactor** (epic/models.py): extracted `_sig_unique_cards_for_deck(deck_variant)` from `_sig_unique_cards(room)`. New public `personal_sig_cards(user)` parallels `levity_sig_cards / gravity_sig_cards` but pulls from `user.equipped_deck` instead of `room.deck_variant`. Same Note-unlock filtering. No behavior change to existing room callers (3-line wrapper preserves the room signature) ; **TDD trail** — user called out mid-sprint that I'd skipped FTs; pivoted to FT-first. test_bill_my_sign.py (new, 3 FTs): T1 picker renders w. wordmark + target card present in grid; T2 click card → SAVE SIGN enables → POST persists → applet shows the card; T3 fresh user → applet renders empty-state. Initial reds — (a) setUp's `personal_sig_cards(user)` returned [] because StaticLiveServerTestCase → TransactionTestCase flushes migration-seeded DeckVariant + TarotCard between tests; fixed w. `serialized_rollback = True` on the test class (per [[feedback_transactiontestcase_flush]]); (b) h2 wordmark assertion against `MYSIGNIFICATOR` failed against the renamed "Game Sign" + the letter-splitter spreading chars across <span> children — switched to whitespace-stripped substring check `GAMESIGN`; (c) `.fan-corner-rank` text is CSS-hidden so Selenium returns "" — replaced corner-rank assertions w. data-card-id selectors (already-proven reliable from the parent .sig-card lookup) ; ITs (+12, in apps.billboard.tests.integrated.test_views): MySignViewTest (6 — login redirect, 200 + template, 16-card pile, save persists, invalid card_id → 403, GET save redirects); BillboardAppletMySignTest (3 — applet rendered, empty-state w/o sig, card+reversed class w. sig). PersonalSigCardsTest in apps.epic.tests.integrated.test_models (3 — happy path 16 cards, no-equipped-deck → [], schizo Note unlocks Major 1) ; pre-existing change picked up by the commit: my_sea.html branding "Game Sea" (user-modified mid-session; was "My Sea" in Sprint 3 — divergence captured in MEMORY.md follow-up) ; 1020 IT/UT green (+12) in 46s; 3 FTs green in 24s. Sprint 4a unblocks Sprint 4b (My Sea gating w. --terUser link to /billboard/my-sign/) + Sprint 4c (FT helper for mocking the sig choice across other FTs)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:23:24 -04:00