Commit Graph

652 Commits

Author SHA1 Message Date
Disco DeDisco
c9563308d8 SAVE SKY provenance + sky→hex (not sky→sea) transition — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
- drama.GameEvent.SKY_SAVED verb + to_prose branch: "X beholds the skyscape of {poss} birth, which yields {obj} a unique {Cap} capacity."; tied highest scores switch "a unique" → "equal", join w. "and" (2-way) or Oxford comma (3+), and pluralize "capacity" → "capacities"; pronouns resolved from actor.pronouns at render time, same machinery as SIG_READY/ROLE_SELECTED
- epic.utils.ELEMENT_CAPACITOR_NAMES + ELEMENT_ORDER + top_capacitors(elements) helper: maps Fire→Ardor Stone→Ossum Time→Tempo Space→Nexus Air→Pneuma Water→Humor; tolerates both flat-int and enriched-dict (`{count, contributors}`) chart_data shapes; returns capacitor names tied for highest count, ordered by canonical wheel ring
- epic.natus_save: on action=confirm, records GameEvent.SKY_SAVED w. top_capacitors=[…] before _notify_sky_confirmed; per-room billscroll AND billboard Most Recent Scroll pick up the new prose
- _natus_overlay.html _onSkyConfirmed: removed sea-partial fetch+inject; now calls closeNatus() + window.location.reload() so the gamer lands on the table hex w. the PICK SKY → PICK SEA btn swap (server-side, driven by sky_confirmed=True), then opts into the sea overlay manually. The auto-launch via 39e12d6 was buried by FTs that were pinning the wrong contract — gamer never had a chance to witness PICK SEA on the hex
- test_room_sea_select.py: three FTs renamed/rewired from auto-launch assertions (sea_overlay_appears_without_page_refresh, natus_overlay_not_visible_after_sky_confirm, sea_open_class_on_html_after_confirm) to (pick_sea_btn_visible_after_sky_confirm, natus_overlay_closed_after_sky_confirm, clicking_pick_sea_btn_opens_sea_overlay) — sea overlay now requires explicit PICK SEA click

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 01:57:35 -04:00
Disco DeDisco
5413e63585 billboard Most Recent Scroll: fix SQLite NULL drop on SIG_READY exclude; pronouns flow FT; Blades middle reversal Nervous → Fickle — TDD
- billboard/views.py _billboard_context: `.exclude(verb=SIG_READY, data__retracted=True)` was silently dropping every SIG_READY event whose data had no `retracted` key — `WHERE NOT (NULL AND verb='sig_ready')` evaluates to NULL via JSON_EXTRACT, which the SQL engine treats as "row not satisfying WHERE", so the row was excluded. Fix: pull a 100-row buffer w. only the SIG_UNREADY exclude at the SQL level, then post-filter retracted SIG_READY in Python before slicing to 36; PostgreSQL handles the lookup correctly so this is a SQLite-only manifestation that explained intermittent "No events yet" in Most Recent Scroll
- CLAUDE.md gotchas: new entry warning that `.exclude(data__key=value)` / `.filter(data__key=value)` on SQLite JSONField bites on missing keys; if the predicate must require key existence, post-filter in Python
- functional_tests/test_game_kit.py PronounsAppletFlowTest: end-to-end profile-wide pronoun flip — start on per-room billscroll seeing "their" cognates, navigate to Game Kit, click bawlmorese card, assert guard portal active w. "yo/yo/yos" preview, click OK, navigate to billboard + see Most Recent Scroll re-rendered w. "yos", navigate back to billscroll + see same flip; covers the whole render-time-pronoun-resolution path on real DOM
- epic/0008_blades_reversal_fickle.py: rename Middle Arcana Blades reversal_qualifier "Nervous" → "Fickle" (RunPython forward+reverse on arcana=MIDDLE, suit=BLADES, number ∈ {11,12,13,14}); SigSelectSpec.js hardcoded "Nervous" updated to "Fickle" + collected static

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 01:27:17 -04:00
Disco DeDisco
29493c4f74 pronouns: per-user pronouns ideology + Pronouns applet on Game Kit; provenance prose uses actor.pronouns at render time — TDD
- User.pronouns CharField w. choices=[pluralism (default), bawlmorese, misogyny, misandry, misanthropy] + pronoun_subj/obj/poss properties; PRONOUN_TABLE single source of truth in apps.lyric.models; mig 0002_user_pronouns
- drama.GameEvent.to_prose() drops module-level PRONOUN_* constants; SIG_READY/SIG_UNREADY/ROLE_SELECTED now resolve poss/subj from self.actor.pronouns at render time, so flipping a user's preference rewrites all their existing scroll prose; default actor → "their"
- SIG_READY prose strips a leading "The " from card_name so "the The Wanderer" reads "the Wanderer" and "the Engraven The Nomad" reads "the Engraven Nomad"; minor arcana ("Maid of Brands") untouched
- new applets/0005 seeds 'pronouns' applet (3x3, game-kit, default visible); _game_kit_sections.html grows a #id_gk_pronouns block w. 5 .gk-pronoun-card items labeled by ideology slug (italic) and tagged data-pronoun + data-trio
- card click → window.showGuard(card, "Set pronoun preference?<span class='guard-pronoun-trio'>{trio}</span>", commitCb); on OK fetches POST /dashboard/set-pronouns w. CSRF cookie + reloads so .active class moves and provenance prose re-renders; NVM dismisses
- dashboard.set_pronouns view (POST-only, login_required, 204/400/405) at /dashboard/set-pronouns; rejects choices not in PRONOUN_TABLE
- _game-kit.scss extends shared card rule to .gk-pronoun-card w. .active fill state + italic ideology label; #id_guard_portal .guard-pronoun-trio styled small/dim/centered under the question
- billscroll aperture: padding-right 0.75rem on #id_drama_scroll inside .applet-scroll so the timestamp column no longer sits beneath the scrollbar

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 01:11:40 -04:00
Disco DeDisco
599d40decd auth urls: mount apps.lyric.urls under /dashboard/ to mirror gameboard/epic & billboard/drama convention
- core/urls.py: replace `path('lyric/', …)` with second `path('dashboard/', include('apps.lyric.urls'))` alongside existing dashboard mount; no path-name collision (lyric paths: send_login_email, login, logout, dev-login/<key>/)
- IT test URL strings flipped /lyric/ → /dashboard/ (test_views.py)
- setup_sig_session + setup_sea_session pre-auth URL builders updated
- CLAUDE.md doc note updated
- Templates use unnamespaced `{% url 'logout' %}` / `{% url 'send_login_email' %}` so they auto-resolve; no template edits needed
- /admin/lyric/user/ admin URL untouched (driven by app_label, not URL conf)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:18:36 -04:00
Disco DeDisco
2dc68c41a7 billboard applets: drop billboard- prefix from partials & ids; Most Recent → Most Recent Scroll; room_scroll → scroll — TDD
- Slug renames (mig 0004): billboard-my-scrolls → my-scrolls; billboard-my-contacts → my-contacts; billboard-most-recent → most-recent-scroll (name → "Most Recent Scroll"); billboard-notes → notes
- Partial filenames lose the `billboard-` token to mirror dashboard/gameboard convention; element ids follow (id_applet_my_scrolls, id_applet_my_contacts, id_applet_most_recent_scroll, id_applet_notes, id_applet_scroll); .applet-billboard-scroll → .applet-scroll
- View fn billboard.views.room_scroll → scroll; template apps/billboard/room_scroll.html → scroll.html (URL name `billboard:scroll` already correct)
- ITs + FTs updated to new identifiers; SCSS selectors retitled

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 23:22:01 -04:00
Disco DeDisco
b1a11504f5 game kit + role icons + tarot fan: in-use mini-portal label, FLIP cue polarity reset, role icon redraws
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
Heterogeneous pre-existing changes (carried across multiple sessions, finally committed alongside the SIG SELECT exit sprint). Grouped:

- gameboard.js: _inUseLabel(roomName) — buildMiniContent renders "In-Use: <name>" on hover (cap 24 chars; overflow → 21 + "…"). Reads token.dataset.inUseRoomName for decks & token.dataset.currentRoomName for trinkets.
- _applet-game-kit.html: removes the inline <p class="tt-token-room-name"> + <p class="tt-deck-game-name"> paragraphs (now redundant — mini-portal carries the name); deck token gains data-in-use-room-name attr.
- gameboard tests: assertions retargeted at data-in-use-room-name + the mini-portal flow rather than the deleted inline paragraphs (test_views, test_deck_contribution, test_trinket_carte_blanche).

- game-kit.js: openFan + _testOpen reset _polarity = 'levity' so reopening the fan after FLIP-to-gravity always lands on the levity-painted face (the FLIP cue). The sessionStorage bookmark intentionally tracks card index only; polarity does NOT persist across reopen.
- _tarot_fan.html: SSR-default polarity flipped from levity to gravity (levity_emanation → gravity_emanation, levity_qualifier → gravity_qualifier, levity_reversal → gravity_reversal across upright + reversal faces). Pairs w. the JS polarity reset above so JS repaints to levity on open.
- FanStageSpec: 2 new specs — openFan polarity reset on reopen even after FLIP-to-gravity; sessionStorage stores no levity/gravity string.

- starter-role-*.svg (Alchemist, Builder, Economist, Narrator, Player, Shepherd): redrawn / re-cropped art — viewBox tightened from 288×560 to ~154×156, paths re-traced. No new role added; existing 6 swapped in place. New starter-role-blank.svg added as fallback for unmapped role codes (referenced by tray.js _ROLE_SCRAWL default → 'Blank').

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:28:32 -04:00
Disco DeDisco
f78177778f SIG SELECT exit: 2s hang after tray closes; suppress waiting msg if PICK SKY already up — TDD
- polarity_room_done handler in sig-select.js wraps Tray.placeSig's callback in setTimeout(_settle, 2000) so the user gets a beat to register the tray closing before the overlay vanishes. Visual order now: stage → tray slides in → sig fades into the tray cell → tray slides out → 2s pause → overlay dismisses → table hex.
- _showWaitingMsg early-exits if #id_pick_sky_btn is already revealed. The cross-polarity case — the OTHER room finishes WHILE this gamer's tray sequence is mid-flight — fires pick_sky_available during the hang, which removes any waiting msg & shows PICK SKY. When _settle fires after the hang, the PICK SKY check skips the now-stale waiting msg.
- 2 SigSelectSpec specs: 2s delay before overlay dismisses; waiting msg suppressed when pick_sky_btn is visible. jasmine.clock() drives the setTimeout in both.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:11:07 -04:00
Disco DeDisco
480cb4aed6 tray: Tray.placeSig analogue of placeCard for SIG SELECT exit; rename arc-in → fade-in — TDD
After all 3 gamers in a polarity room confirm TAKE SIG and the 12s countdown
expires, sig-select.js's room:polarity_room_done handler now plays the same
tray-open / fade-in / tray-close sequence the role-select uses, then
dismisses the sig overlay & shows the waiting msg ("Gravity settling…" /
"Levity appraising…") on Tray.placeSig's completion callback. Visual order:
sig stage → tray slides in → sig fades into the second tray cell → tray
slides out → table hex w. waiting msg. Cross-polarity events (other room
finishing while we're still in our overlay) are no-op as before.

- tray.js: new Tray.placeSig(sourceEl, onComplete). Mutates the SECOND
  .tray-cell in place (sig slot), copies aria-label / data-energies /
  data-operations / corner-rank + suit-icon markup from the source
  .sig-stage-card, then runs the shared open → fade-in → close sequence.
  Extracted _runFadeInSequence helper so placeCard + placeSig share the
  same animation glue. reset() now also clears .tray-sig-card from cells.

- _tray.scss: .tray-sig-card.fade-in > .sig-stage-card animates via the
  existing tray-role-fade-in keyframes.

- sig-select.js polarity_room_done handler: Tray.placeSig(stageCard,
  _settle); _settle runs the existing _dismissSigOverlay + _showWaitingMsg.
  Falls back to immediate dismiss when Tray is undefined (test environments
  without the tray).

- arc-in → fade-in rename across tray.js, role-select.js, _tray.scss
  (incl. @keyframes tray-role-arc-in → tray-role-fade-in), TraySpec.js
  spec descriptions + assertions, & test_room_role_select.py docstrings.
  The original "arc-in" name suggested a curved-path animation; the actual
  behaviour is a 1s opacity fade, so fade-in is the accurate label.

- TraySpec: 10 new placeSig specs mirroring placeCard (second-cell mutation,
  data + markup copy, tabIndex, fade-in class, animationend-triggered close,
  onComplete callback, landscape parity, reset cleanup).

- SigSelectSpec: 3 new specs (Tray.placeSig called w. stageCard on own
  polarity; not called on other polarity; overlay dismiss deferred to the
  Tray.placeSig completion callback).

344 specs / 4 pending green; RoleSelectTrayTest FT still green.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:58:44 -04:00
Disco DeDisco
9b93b9d31b tray tooltips: tilt persists while portal is open; PRV|NXT pinned to corners — TDD
- TrayTooltip adds .tt-active to the .tray-role-card / .tray-sig-card cell while its tooltip is open & removes it on _hide. The hover-tilt selectors gain .tt-active alongside :hover, :focus so the card stays tilted while the user is hovering the portal itself rather than the cell.
- #id_tooltip_portal: .fyi-prev / .fyi-next pinned to the bottom corners w. 1rem outside the panel (bottom: -1rem; left/right: -1rem) — same anchor the @stat-block-shared mixin uses for fan / sig / sea, restated here since the portal isn't covered by that mixin.
- 2 new TrayTooltipSpec specs (.tt-active added on hover, removed on _hide; for both role & sig branches).

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:18:09 -04:00
Disco DeDisco
b29bcf5c38 tray sig-card tooltip: portal w. PRV|NXT pager — TDD
Phase 2 of the apps.tooltips integration on the tray. Hovering
.tray-sig-card > .sig-stage-card opens #id_tooltip_portal w. an FYI panel
that mirrors #id_fan_fyi_panel (Energy / Operation entries cycled via
PRV|NXT), but w.o. the stage block, w.o. Reversal entries, & w.o. the fan
stage's click-to-dismiss handler — the panel-body click is reserved for
future drag-and-drop on .tray-sig-card:active.

- _partials/_sig_fyi_panel.html — new partial, the .sig-info + PRV|NXT
  block extracted out of game_kit.html, _sig_select_overlay.html, &
  _sea_overlay.html. {% include %}d back from those 3 callers; pure
  copy-paste extraction (no behavioural change to fan stage, sig select,
  or sea select).
- room.html: .tray-sig-card > .sig-stage-card gains data-energies +
  data-operations (the only attrs StageCard.buildInfoData reads), keyed
  off my_tray_sig.energies_json / .operations_json (existing TarotCard
  properties).
- tray-tooltip.js: new sig branch — _showSig() builds the panel inline,
  paints via StageCard.renderFyi, & wires PRV|NXT cycle handlers; the
  mousemove union now covers the .fyi-prev / .fyi-next btn rects (the
  btns hang past the portal's left & right edges) so mouse-over them
  keeps the panel alive. Click stopPropagation on the btns prevents the
  panel-body click from reaching anything else.
- TrayTooltipSpec: 6 new sig-branch specs (panel structure; first energy
  entry rendered; PRV|NXT cycling; body click no-dismiss; pointer over
  btn rects keeps panel alive; pointer outside full union clears).
- test_component_tray_tooltip.py: 4 sig FTs (hover populates portal w.
  Energy/TESTLIBIDO/effect/1-of-2; PRV|NXT cycle; body click does NOT
  dismiss; mouseleave clears).

FT helper note — the sig FT's _hover dispatches a synthetic mouseenter
via JS rather than ActionChains.move_to_element, because the role-card
& sig-card cells sit side-by-side in the tray grid: the pointer's
animated path crosses the role-card on its way to the sig-card &
opens the role tooltip mid-flight, which then occludes the sig stage
by the time the move lands. Direct dispatch lands the event on the
intended trigger w.o. the cross-cell drag-by.

313 epic ITs + 335 Jasmine specs (incl. 6 new) + 6 tray-tooltip FTs all
green.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:07:33 -04:00
Disco DeDisco
08243d109d tray cards: shadow, hover-tilt w. focus persistence, role-card tooltip — TDD
- _tray.scss: drop-shadow on cell child elements (img → filter:drop-shadow so the silhouette is the shadow caster, div → box-shadow); 7° hover-tilt on .tray-role-card > img (-7°) and .tray-sig-card > .sig-stage-card (+7° via the standalone `rotate` property so the existing -5° baseline transform composes); :focus persists the tilt after click; cursor: pointer
- tray.js: set tabIndex=0 on placeCard's role cell + on template-rendered .tray-role-card / .tray-sig-card cells at init() so :focus latches the hover state; clear tabindex in reset() for Jasmine afterEach
- TraySpec: 4 new specs covering placeCard tabindex, reset cleanup, init-time tabindex on template-rendered sig & role cards, no-tabindex on bare cells
- New tray-tooltip.js (#id_tooltip_portal) — Phase 1 of the apps.tooltips integration: hovering .tray-role-card > img copies its sibling .tt's innerHTML into the page-root portal, anchors above/below the trigger, & clamps to the viewport horizontally; mousemove outside the union of [trigger, portal] rects clears the portal (Game-Kit pattern, no btns)
- room.html: #id_tooltip_portal mounted at room-page root (outside tray's overflow:hidden); .tt block rendered inline inside .tray-role-card via {% tooltip %} templatetag w. title=role display name & description="[Placeholder description]"
- epic/views.py: my_tray_role_tooltip context dict ({title, description}) keyed off the seated role
- TrayTooltipSpec: 8 specs covering portal population, .active class, sibling-.tt fallback, viewport-edge clamp left/right, and union-rect mouseleave
- 2 FTs in test_component_tray_tooltip.py: hover role img → portal title=Player + description=Placeholder; mouseleave → portal clears

Phase 2 (sig-card tooltip mirroring #id_fan_fyi_panel via a DRY refactor) deferred per plan.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 18:40:10 -04:00
Disco DeDisco
75fcc5b34d billboard applets: single-root wrapper for HTMX swap; full context on toggle — TDD
- _applets.html wraps menu + container in one #id_billboard_applets_wrapper div; form's hx-target is now the wrapper, so OK no longer leaves a stale duplicate menu in the DOM (which previously caused the next OK to revert prior toggles)
- toggle_billboard_applets passes full context (recent_room, recent_events, viewer, my_rooms) via factored _billboard_context helper, so Most Recent + My Scrolls keep their content after a toggle instead of falling through to the empty fallback
- applets.js: register id_billboard_applets_wrapper as an applet container so post-swap menu cleanup runs
- BillboardAppletsTest: portrait viewport in setUp; FT covers content preservation, no-revert on second toggle, & post-refresh state
- 4 new ITs: Most Recent renders Coin-on-a-String after toggle; My Scrolls renders room name; response has single menu div; second toggle preserves prior hidden state

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:15:26 -04:00
Disco DeDisco
536a558f26 PICK SEA: gate PRV|NXT on .fyi-open like sig + fan
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
stale "always visible" override in .sea-stat-block was leftover from before
the FYI panel became toggle-able. sea.js already adds/removes .fyi-open
on _openInfo/_closeInfo, so SCSS-only fix.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 02:36:50 -04:00
Disco DeDisco
8b0ad545c9 collapse epic migrations 0007–0022 → 0007_finalize_earthman_deck; add reset_staging_db
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
- 16 incremental Earthman tweak migrations folded into one end-state finalize
  migration (rename mechanisms→energies / articulations→operations /
  reversal→reversal_qualifier; +italic_word; suit court reversals; Schizo
  energies+operations; card 49 polarity reversal titles; Castanedan Virtues
  trumps 6–9 + 19–21; trump 8 U+2011 hyphen; trump 9 U+00A0 nbsp; pips → MINOR)
- 22 epic migrations → 7; 748 ITs green
- new mgmt cmd `reset_staging_db` — drops schema (Postgres) / tables (sqlite)
  & re-runs migrate; refuses on prod hosts; needs `--i-mean-it` when DEBUG=False;
  interactive host-name confirmation locally; calls ensure_superuser after

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 02:22:43 -04:00
Disco DeDisco
3410f073f0 fan-card title symmetry; pips → Minor; tray Sig card
- title slot: <h3> → <p>; font-size 0.1 → 0.087 (deck) / 0.093 → 0.08 (sig/sea); text-wrap: balance — kills upright/reversal asymmetry & all per-card squeeze hacks
- trump 8 hyphen → U+2011, trump 9 space → U+00A0 (mig 0021) so titles wrap as intended
- pips (Earthman 1–10) → MINOR arcana (mig 0022); StageCard._arcanaDisplay() picks the right label
- PICK SEA: re-clicking a deposited slot now restores the server-rolled reversed state (sea.js _populate toggle)
- tray Sig card: render same .sig-stage-card.sea-sig-card (rank + icon, -5deg) as Sea center; --sig-card-w sized off --tray-cell-size
- title_squeeze_class kept as no-op for template compat
- 0020 (Self-Unimportance rename) included from prior turn

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 02:06:55 -04:00
Disco DeDisco
c264b6e3ee PICK SEA reversal axis: server-side roll + preview + deposited slot — TDD
- new apps/epic/utils.STACK_REVERSAL_PROBABILITY (=0.25) + stack_reversal_probability(user, room) helper; single source of truth across game phases & one-line swap point for forthcoming per-user-profile config
- sea_deck view rolls each card's `reversed` axis at fetch time using the helper, attaches to card JSON; matches the eager shuffle pattern (whole deal determined at phase start)
- room_view + sea_partial pass `stack_reversal_pct` into context for the new <p class="sea-reversal-hint">25% reversals</p> hint above the SPREAD combobox (italic, 0.7rem, 0.55 opacity)
- SeaDeal.openStage applies .stage-card--reversed + .is-reversed to stat block when card.reversed → preview lands face-reversed w. REVERSAL keywords
- _fillSlot adds .sea-card-slot--reversed → slot itself rotates 180° (bg + border + content stack flips, not just inner chars upside-down in place); .sea-pos-cross overrides to 270° to compose w. its existing 90°
- _fillSlot adds .sea-card-slot--rank-long when corner_rank.length ≥ 5 (XVIII / XXIII / XXVIII / XXXIII / XXXVIII / XLIII / XLVIII) → SCSS scaleX(0.7) + letter-spacing -0.05em squeezes horizontally w.o changing font-size

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 00:11:40 -04:00
Disco DeDisco
da57106d7a castanedan virtues + card 49 tweak; italic_word for trumps 19–21; sig/sea propagation — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- migration 0016: card 49 gravity_reversal All-Bestowing → Bestowing
- migration 0017: implicit virtues (trumps 6–9) Sublimating/Sedimentary qualifiers + shared reversals (Indulged Folly / Indulgent Doing / Self-Indulgence / Indulging Personal History); explicit virtues (trumps 19–21) full-string emanation/reversal overrides (The Hunter's/Sleeper's/Quarry's etc.); canonicalize trump 7 name "Not Doing" → "Not-Doing"
- migrations 0018+0019: TarotCard.italic_word field; populated for trumps 19–21 (Stalking / Dreaming / Intent)
- _tarot_fan.html: data-italic-word + |italicize:card.italic_word filter applied to all rendered title slots
- new templatetags/tarot_filters.py: italicize(text, word) — escape-safe <em> wrapping
- StageCard JS: parse data-italic-word; new _escape / _italicize / _setTitle helpers wrap matching word in <em> via innerHTML when present (textContent otherwise)
- views.py _card_dict: include polarity-split overrides + italic_word so Sea Select stage gets them via fetch JSON
- _sig_select_overlay.html: emit the five new data-* attrs on sig-card markup so Sig Select stage picks them up via StageCard.fromDataset

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 23:36:35 -04:00
Disco DeDisco
270e48ab2c cards 48–49 polarity-split titles; sea-stage mobile breakpoints; @comment fix — TDD
- migration 0015 fills card 49 levity_reversal=The Vibrational Mould of Man, gravity_reversal=The All-Bestowing Eagle (card 48 already seeded in 0004)
- _tarot_fan.html: 4 new data-* attrs (data-levity-emanation / data-gravity-emanation / data-levity-reversal / data-gravity-reversal); upright + reversal slots render full polarity-split title in name slot when set, qualifier slots blank
- StageCard.fromDataset: parse the 4 new attrs; populateCard: emanationOverride / reversalOverride per polarity bypasses the standard name+qualifier rendering
- model: emanation_for / reversal_for fall back to name_title (group prefix stripped) instead of full self.name; reversal_for uses self.reversal_qualifier (was leftover self.reversal post-rename)
- sea-stage-content: --sig-card-w lifted from inline style to SCSS w. portrait ≤480px / landscape ≤500h breakpoints both stepping to 130px (mirrors fan modal triggers); default 180px
- _tarot_fan.html: rewrite multi-line {# #} that rendered as page text into {% comment %}{% endcomment %}

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:51:23 -04:00
Disco DeDisco
2f039559e6 Game Kit fan stage + FLIP/SPIN; sig/sea/fan refactor — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- fan modal: stage block w. idle-reveal/careen-out; carousel shifts left so focused card sits left-of-center; SPIN rotates whole card via Element.animate(); FLIP toggles polarity (Levity ↔ Gravity) via perspective rotateY w. mid-flip repaint; SPIN state retained across FLIP; FLIP btn hover-revealed only when focused card or btn is hovered (:has)
- mobile breakpoints: --fan-card-w / --fan-card-h / --fan-stage-shift / --fan-carousel-step lifted to CSS vars on .tarot-fan-wrap; portrait ≤ 480px @ 150×230, landscape ≤ 500h @ 150×235; corners + face text/padding scale w. card width
- shared StageCard JS module (apps/epic/stage-card.js): fromDataset, populateCard, populateKeywords, buildInfoData, renderFyi — sig/sea/fan all delegate; ~150 lines de-duplicated
- shared @mixin stat-block-shared (SCSS) lifts duplicated stat-face / stat-keywords / sig-info rules; @mixin stage-card-polarity unifies sea-stage--levity/--gravity + fan[data-polarity] coloring
- model rename: TarotCard.reversal → reversal_qualifier (migration 0014); render-time fallback to current polarity's qualifier when blank
- class unification: .sig-info-open / .sea-info-open / .fyi-open → .fyi-open (on stat block); .sig-flip-btn / .sea-spin-btn / .fan-spin-btn → .spin-btn; same for .fyi-btn / .fyi-prev / .fyi-next
- custom combobox (apps/epic/combobox.js) replaces native <select> for PICK SEA spread picker — keyboard nav, click-outside-close, aria roles; Firefox/Chrome OS-rendered <option> ignored CSS
- Jasmine: FanStageSpec.js w. idle-reveal / population / SPIN / FYI / FLIP specs; sig + sea fixtures + IT view assertions updated for renamed classes
- 748 ITs + Jasmine green

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:01:52 -04:00
Disco DeDisco
61162e36da fan-card-corner: icon outer-edge aligned; padding-left + fan-only padding-top
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- i { align-self: flex-start } — icon sits at outer card edge regardless of
  rank width; --br rotate(180deg) maps flex-start to visual right/outer edge
- padding-left: 0.5rem on .fan-card-corner — breathing room for rank + icon
- .fan-card .fan-card-corner { padding-top: 0.25rem } — top breathing room
  scoped to game kit fan only; stage cards (sea, sig) unaffected

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 15:00:37 -04:00
Disco DeDisco
26a3af21fa PICK SEA crucifix grid: rename CSS position classes + remove dead code
Stage 1 — free 'cross':
- sea-cross-cell → sea-crucifix-cell (template, Jasmine fixture, SCSS)

Stage 2 — semantic position names:
- sea-pos-past → sea-pos-leave; grid-area: past → leave
- sea-pos-center → sea-pos-core; grid-area: center → core
- sea-pos-future → sea-pos-loom; grid-area: future → loom
- sea-pos-root → sea-pos-lay; grid-area: root → lay
- grid-template-areas updated to match
- sea-pos-crossing removed (dead code — no element ever carried it)

Jasmine + 35 ITs green.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 14:19:30 -04:00
Disco DeDisco
d728900c24 fix Nomad icon fa-hat-cowboy → fa-hat-cowboy-side; setup_sea_session command
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- migration 0011 ICONS dict corrected for fresh installs
- migration 0013 data fix for existing DBs (filters on old value to be safe)
- TarotCardSuitIconTest updated to assert fa-hat-cowboy-side
- setup_sea_session: single-gamer PICK SEA dev session w. pre-auth URL

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 12:07:41 -04:00
Disco DeDisco
2dae861f30 tweaked box-shadow attr on active card in PICK SEA stage 2026-04-29 11:49:26 -04:00
Disco DeDisco
98354fd27b PICK SEA slot interaction: cover/cross appear animation; focused glow; card bg fully opaque
- cover/cross card slots animate 0→1→resting opacity (2s) on deposit
- cover rests at 0.3; cross rests at 0.15; hover reveals to 1
- first-tap focus adds diffuse --ninUser + tight black box-shadow glow
- second tap removes --focused before _showStage() — glow dismisses as modal opens
- levity/gravity card backgrounds bumped to rgba alpha 1 (were 0.85)
- box-shadow 0.15s ease added to --visible transition for smooth glow out

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 11:47:50 -04:00
Disco DeDisco
7712cf1d56 PICK SEA FTs: add @tag("channels") to ChannelsFunctionalTest subclasses — CI fix
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- PickSeaAsyncTransitionTest + PickSeaDealTest extend ChannelsFunctionalTest
  (ChannelsLiveServerTestCase), which spawns a child process; daemonic parallel
  workers cannot have children → setUpClass AssertionError in CI test-FTs stage
- @tag("channels") routes them to the non-parallel test-two-browser-FTs stage

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 11:27:51 -04:00
Disco DeDisco
e084bcc2d5 PICK SEA slot interaction: polarity card bg/border, cross-slot opacity fix, two-step tap; _hideOk ReferenceError removed from sea.js; Jasmine spec updated for two-step; migration 0012 PENTACLES cleanup — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 02:30:59 -04:00
Disco DeDisco
08aa4dc819 PICK SEA Sprint C: sea stage card viewer — FLIP in, SPIN/FYI, deposit/re-expand — TDD
- sea.js: SeaDeal module — openStage() shows big card viewer w. flip-in animation;
  SPIN toggles stage-card--reversed; FYI shows energies/operations (Energy/Operation
  titles, PRV/NXT nav); backdrop click deposits card to slot; click deposited slot
  re-opens stage; resetHand() clears hand on DEL
- sea_deck view: adds name_group/name_title/reversal/keywords_upright/keywords_reversed/
  energies/operations to each card dict (full sig-select stage data set)
- _sea_overlay.html: data-sea-user-polarity attr; sea stage HTML (sig-stage-card shell
  + fan-card-face-upright/reversal structure + sea-stat-block w. SPIN/FYI/PRV/NXT);
  FLIP click calls SeaDeal.openStage(); _fillPos removed (sea.js handles slot fill);
  _reset calls SeaDeal.resetHand()
- room.html: sea.js included alongside sig-select.js
- _card-deck.scss: sea-stage layout (fixed overlay, backdrop, content row); sea-stage-card
  w. @keyframes sea-flip-in (3D rotateY perspective); sea-stat-block scoped styles
  incl. SPIN/FYI btns, stat faces, sig-info FYI panel
- SeaDealSpec.js: 20 Jasmine specs — openStage, SPIN, FYI, backdrop dismiss, slot re-expand

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 01:12:06 -04:00
Disco DeDisco
2af59b3a7f tarot card icons + ranks; sig fallback for pre-sync Characters; DECKS label sizing — TDD
- migration 0011: The Nomad (0) → fa-hat-cowboy; The Schizo (1) → fa-hat-wizard
- corner_rank: non-MAJOR pip card 1 → 'A' (Ace); court unchanged (M/J/Q/K); TDD
- 17 unit model tests for corner_rank + suit_icon
- _role_select_context: my_tray_sig falls back to seat.significator when
  confirmed_char.significator is None (Characters created before natus_save sync)
- _card-deck.scss: DECKS label bigger (1rem, 0.32em letter-spacing) to fill
  stack height; sea-stack-name: opacity 0.6, scaleY(1.5), margin-top -0.4rem
  partially under face; sea-stack-face z-index:1

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 00:20:55 -04:00
Disco DeDisco
6d75b9541f PICK SEA styling: deck backs, card rank+icon display, fa-hand-dots Major Arcana — TDD
- migration 0010: icon='fa-hand-dots' for all Earthman Major Arcana number >= 2
  (Nomad/Schizo kept empty for distinct icons later)
- sea_deck view: switch from .values() to model instances; serializes corner_rank +
  suit_icon computed properties alongside DB fields
- sea overlay JS: _fillPos() renders <span class=fan-corner-rank> + <i fa-solid> HTML;
  tracks levity/gravity source via sea-card-slot--levity/gravity class; _reset() strips
  polarity classes; _showOk/_hideOk toggle sea-deck-stack--active
- template: gravity deck before levity; OK btn inside .sea-stack-face (absolute center);
  DECKS label (vertical-rl CCW) on stacks left; Gravity/Levity names under each pile
- _card-deck.scss: .sea-stacks-label (vertical-rl); .sea-stack-ok (absolute center on face);
  .sea-stack-name w. --quaUser/--terUser; glow on hover+:active+--active class —
  --ninUser for levity, --quaUser for gravity; sea-sig-card compact rank+icon display
- sea_partial view: ctx['room'] fix carried in from Sprint B

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:30:07 -04:00
Disco DeDisco
132e60864e PICK SEA Sprint B: deck stacks, OK btn, card draw, LOCK HAND/DEL — TDD
- _sea_overlay.html: DEAL btn replaced by two .sea-deck-stack--levity/gravity
  piles; .sea-pos-cover + .sea-pos-cross overlaid on center sig slot; LOCK HAND
  (disabled) + DEL (.btn-danger) in .sea-form-actions; data-sea-deck-url attr
- sea overlay inline JS: _fetchDeck() loads shuffled piles from sea_deck endpoint;
  stack click → _showOk(); click elsewhere → _hideOk(); OK click → _fillPos()
  in next spread-order position; DEL → _reset(); LOCK HAND enables at 6 fills
- SPREAD_ORDER constants for waite-smith + escape-velocity spread types
- sea_deck view: shuffles full equipped deck minus all seated Significators,
  splits into levity (first half) + gravity (second half) JSON arrays
- epic:sea_deck URL registered
- sea_partial view: ctx['room'] = room added (fixes NoReverseMatch for sea_deck URL)
- _card-deck.scss: .sea-card-slot--filled; .sea-pos-cover/cross absolute overlay;
  .sea-deck-stack + .sea-stack-face; .sea-form-actions layout; removed old DEAL rule
- 9 Sprint B FTs green; 3 Sprint A FTs green; 730 ITs green

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:02:49 -04:00
Disco DeDisco
ff3e4d295c PICK SEA Sprint B FTs: deck stacks, OK btn, card draw, LOCK HAND/DEL — red — TDD
9 tests in PickSeaDealTest: DEAL btn absent; LOCK HAND present+disabled;
DEL present; two deck stacks (.sea-deck-stack--levity/gravity); stack click
shows .sea-stack-ok; elsewhere hides it; OK click fills .sea-pos-cover;
6 draws enables LOCK HAND; DEL clears .sea-card-slot--filled positions.
All 9 fail red — no implementation yet.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 22:50:39 -04:00
Disco DeDisco
39e12d6a3d PICK SEA Sprint A: async sky→sea transition via WS room:sky_confirmed — TDD
- natus_save: group_send room:sky_confirmed after confirm (carries seat_role)
- consumer: sky_confirmed handler rebroadcasts to room group
- _notify_sky_confirmed() helper mirrors _notify_pick_sky_available
- sea_partial view: renders _sea_overlay.html partial for in-page injection (403 if not sky_confirmed)
- epic:sea_partial URL registered
- _natus_overlay.html: data-user-seat-role attr; _onSkyConfirmed() fetches sea partial,
  removes natus overlay + backdrop, injects sea HTML, toggles sea-open on html root;
  room:sky_confirmed WS listener calls _onSkyConfirmed only for matching seat role
- user_seat_role added to SKY_SELECT context
- FT: PickSeaAsyncTransitionTest (3 tests, ChannelsFunctionalTest) — sea overlay,
  natus gone, sea-open class — all green

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 22:16:38 -04:00
Disco DeDisco
379e0ab80c character: significator field populated from seat on natus_save; my_tray_sig from Character when confirmed — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
Character.significator was already in the model but never set. natus_save now copies
seat.significator onto the Character on every save (draft + confirm). _role_select_context
overrides my_tray_sig from Character.significator when sky_confirmed, making Character
the authoritative source for the sig card displayed in the sea overlay.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 21:46:21 -04:00
Disco DeDisco
b5fbc3d354 SIG SELECT: fix major arcana reversed face slot order — title first, qualifier second after spin — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
DOM-second flex child appears first after card rotates 180°; swap qualifier/name slot
assignments for major arcana so reversed face reads "The Schizo, / Enlightened" not
"Enlightened / The Schizo"; spec updated to document the slot-swap invariant

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:48:47 -04:00
Disco DeDisco
4852113fbd SIG SELECT FYI: .card-ref spans in Schizo effects; Energy/Operation singular titles; .sig-info CSS fix — TDD
- migration 0009: re-seeds The Schizo energies/operations w. .card-ref spans on all card titles
  (1. The Priest, 2. The Occultist, 2. Pestilence, 1. The Pervert, etc.)
- migration 0008: updated data constants to match (fresh-install canonical source)
- sig-select.js: title reads "Energy" / "Operation" (singular)
- _card-deck.scss: .sig-info-tooltip → .sig-info (fixes invisible panel + broken dismiss);
  Energy + Operation titles both use --quaUser (gold --terUser reserved for .card-ref spans)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:44:50 -04:00
Disco DeDisco
239da7e5b1 fix ITs: update SigSelectRenderingTest for sig-info-* rename + energies/operations data attrs — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:25:47 -04:00
Disco DeDisco
ed55e4e529 SIG SELECT FYI: mechanisms→energies, articulations→operations; .sig-caution→.sig-info; .btn-caution→.btn-info — TDD
- TarotCard.mechanisms renamed to energies, articulations to operations (migration 0008);
  energies_json + operations_json properties replace old names
- migration 0008 also seeds The Schizo (card 1) w. 4 Energies (LIBIDO/NUMEN/VOLUPTAS×2)
  + 4 Operations (COVER/CROWN/BEHIND/BEFORE)
- FYI info panel renamed throughout: .sig-caution-* → .sig-info-*; data-mechanisms →
  data-energies; data-articulations → data-operations
- _renderCaution() now sets dynamic title (Energies/Operations) + .sig-info-title--energies/
  --operations colour modifier; type element shows entry.type (LIBIDO, COVER etc.)
- .btn-caution → .btn-info across note.js, role-select.js, specs, FT + _button-pad.scss rule
- Major arcana reversed face: card title always shown (reversal concept moves to FYI)
- SigSelectSpec.js rewritten: 242 specs; FYI describe block updated for energies/operations

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:22:19 -04:00
Disco DeDisco
2757ae855f SIG SELECT: non-major reversal display; face wrapper divs; middle arcana reversal seed — TDD
- sig-select.js: three-way reversal branch — major (qualifier + concept name),
  non-major w. reversal (suit qualifier word on own line + card title),
  non-major fallback (polarity qualifier only)
- template: .fan-card-face-upright + .fan-card-face-reversal wrapper divs for
  compact centred text groups; arcana label sits between them
- _card-deck.scss: wrapper divs display:flex; padding-top on reversal group
  equalises gap to MIDDLE ARCANA label on both sides; removes margin:auto overcorrect
- migration 0007: populates reversal qualifier word per suit on Earthman Middle
  Arcana court cards (Seething/Gloomy/Nervous/Vacant); clears The Schizo's
  incorrectly inherited Territoriality reversal

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:09:23 -04:00
Disco DeDisco
505744312b SIG SELECT sprint 1+2: SPIN animation; Emanation/Reversal; Ally Interaction FYI — TDD
Sprint 1 (template + SCSS):
- Stage card gains .fan-card-reversal-name + .fan-card-reversal-qualifier elements
  (pre-rotated 180° so they read forward after card spins); sig cards gain data-reversal attr
- _card-deck.scss: Z-axis rotate(180deg) spin on .stage-card--reversed; reversed elements
  dim @ opacity 0.25 normally, flip to 1 when card is spun; upright content dims in return
- Stat face labels: Upright→Emanation, Reversed→Reversal
- Fixture updated: Emanation/Reversal labels; reversal elements + data-reversal attr

Sprint 2 (FYI from mechanisms + articulations):
- sig-select.js: _openCaution() now parses data-mechanisms + data-articulations (concat)
  instead of data-cautions; _renderCaution() sets .sig-caution-title from entry.category,
  .sig-caution-effect.innerHTML from entry.effect; empty fallback: "No ally interactions"
- TarotCard model: mechanisms_json + articulations_json @property (parallel to cautions_json)
- Template: data-cautions→data-mechanisms+data-articulations; "Caution!"→"" title (set by JS);
  "Rival Interaction"→"Ally Interaction"; shoptalk <p> removed
- SigSelectSpec.js: all old caution tests migrated to {category,effect} dict format +
  data-mechanisms; 7-spec "FYI from mechanisms + articulations" describe block; 242 specs green

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 17:18:16 -04:00
Disco DeDisco
0522b5c126 SIG SELECT: FLIP→SPIN rename; stage-card reversal JS — TDD
- Template: FLIP btn label → SPIN; .btn-reverse class + .sig-flip-btn kept
- _button-pad.scss: .btn-reverse restyled w. cyan (--priCy/--terCy); .btn-tip
  removed; button section comments added (BIG, BYE, FYI, OK, SPIN etc.)
- sig-select.js: SPIN/FLIP handler also toggles .stage-card--reversed on
  stageCard; updateStage() populates .fan-card-reversal-name (data-reversal)
  + .fan-card-reversal-qualifier (polarity qualifier); resets .stage-card--reversed
  on each new hover — TDD
- SigSelectSpec.js: SPIN card animation describe block (7 specs); spec
  descriptions updated FLIP→SPIN; fixture gains reversal elements +
  data-reversal attr; 235 Jasmine specs green

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 16:51:53 -04:00
Disco DeDisco
759ce8d3e4 fix CI FT regressions: deck contribution, ROLE SELECT no-deck guard, sig qualifiers, Carte Blanche multi-slot
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- test_deck_contribution: get_or_create _equip_earthman + unlocked_decks.add; slot_number=2 on
  _setup_in_use_deck seat; navigate to /gameboard/ (not gate — game-kit panel absent there);
  drop #id_kit_card_deck click ({% empty %} placeholder; deck renders in loop when present);
  use textContent for CSS-hidden tooltip; drop stale .deck-micro-status assertion (now mini-portal)
- ROLE SELECT FTs (RoleSelectTest + RoleSelectTrayTest): equip Earthman deck for active-slot
  user in each test that opens the fan — fixes no-deck JS guard blocking #id_role_select
- test_room_sig_select: seed The Nomad/Schizo w. correct Earthman slugs/names + Enlightened/
  Engraven qualifiers; grant super-nomad + super-schizo Notes to all gamers so Major Arcana
  appear in overlay; seed Middle Arcana w. Elevated/Graven qualifiers; rename test methods
- test_game_kit: drop stale assertIn("active", text) — availability moved to In-Use mini-portal
- Carte Blanche: CB stays equipped after multi-slot deposit (revert drop_token unequip);
  select_role existing-seat query gains order_by("slot_number") for deterministic primary seat;
  multi-slot FT: kit bag shows placeholder after first deposit (CB unequipped); cold-feet
  verifies DON via hover→portal; re-equip via portal DON before re-deposit; new
  test_carte_in_use_game_kit_shows_room_attribution checks Game Kit tooltip after deposit

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 16:29:51 -04:00
Disco DeDisco
9eb1c1523e natus wheel: element tooltips — Space/Air-Capacitor subtitle; score badge under square
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
- Add .tt-element-type italic subtitle below element title (classical name + "-Capacitor")
- Wrap element square in .tt-el-badge-col (float right, flex col) w. score beneath
- Remove inline .tt-el-body-line score from all three element branches (classic/Time/Space)
- .tt-element-type shares .tt-sign-type / .tt-house-type italic dim style

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:01:36 -04:00
Disco DeDisco
c399afa26d role select channels FTs: get_or_create DeckVariant in _equip_earthman_deck — fixes CI flush wipe
TransactionTestCase.flush() wipes all rows (incl. migration-seeded DeckVariants)
after every test. Landscape runs first (alphabetically), consumes the seeded deck,
then flush removes it. Subsequent tests called filter().first() → None → silently
skipped → data-equipped-deck="" → JS no-deck warning instead of opening fan.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 11:46:47 -04:00
Disco DeDisco
4b8e02b698 role select channels FTs: equip Earthman deck in setUp — fixes no-deck guard block
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
The role-select.js no-deck guard (added with e512e94) shows a warning instead of
opening #id_role_select when data-equipped-deck is empty. Three RoleSelectChannelsTest
setups didn't equip a deck for the founder, so the card-stack click never opened the
modal and all three failed with NoSuchElementException on #id_role_select.
Only the founder needs equipping — other gamers' roles are ORM-assigned, no browser click.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 02:33:15 -04:00
Disco DeDisco
478e845ecf test_game_invite: guard BillPost import with skipUnless — fixes CI loader error
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
Top-level import of apps.billboard.models.BillPost blew up the test module at collection time because the model doesn't exist yet. Wrap in try/except + @skipUnless so the tests skip cleanly rather than erroring the whole pipeline.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 02:18:06 -04:00
Disco DeDisco
d79380faa5 fix stale test assertions after note-page interaction changes — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
- NoteEquipTitleViewTest.test_doff_returns_200: assert greeting + title fields now returned by doff_title view
- NoteEquipTitleTest FT: click note item to lock before reading DON/DOFF text — opacity:0 by default makes .text return empty

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 02:00:22 -04:00
Disco DeDisco
e78bbb873b Billnotes note-page interaction: hover glow, click-lock, DON/DOFF; note titles + card-ref styling — TDD
- note-page.js: click-lock (_lockedItem + notes-locked body class); DON auto-DOFFs prev donned; _setGreeting updates navbar; _donnedItem exposed for test API
- NotePageSpec.js: 18 Jasmine specs covering lock/unlock, DON/DOFF state, auto-DOFF, greeting update, initial load; flushPromises helper for chained fetch .then()
- _note.scss: DON/DOFF opacity:0 by default; hover + locked + donned states show them; body:not(.notes-locked) hover suppression
- views.py: Super-Schizo/Super-Nomad card titles; recognition_title field (display_title) separate from card title; mark_safe descriptions w. card-ref spans
- my_notes.html: |safe on description; recognition_title for Recognitions block
- _navbar.html: id_greeting_prefix/id_greeting_name spans for JS greeting update
- _base.scss: global .card-ref rule (--terUser, font-weight 600, !important)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:54:57 -04:00
Disco DeDisco
763d555f0c backfill super-schizo + super-nomad Notes to existing superusers
Migration 0003: grants both Notes to all existing is_superuser=True users.
Covers accounts created before the post_save signal was wired.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:31:36 -04:00
Disco DeDisco
6ad736413b super-schizo + super-nomad Notes: auto-grant to superusers; sig unlock; navbar titles — TDD
- drama/models.py: _NOTE_DISPLAY dict; Note.display_title / .display_greeting
  properties; super-schizo → "21st Century" + "Schizoid Man";
  super-nomad → "Howdy," + "Stranger"
- billboard/views.py: _NOTE_META super-schizo/nomad entries with mark_safe
  HTML descriptions ("card-ref"-styled card names), swatch_label "I"/"0",
  no palette_options; swatch_label added to note_items context
- lyric/models.py post_save: new superusers get super-schizo + super-nomad
  Notes automatically; setup_sig_session grants them explicitly too
- epic/models.py _filter_major_unlocks: accepts super-nomad / super-schizo
  as valid unlocks alongside their plain counterparts
- _navbar.html: display_greeting|safe + display_title replace slug|capfirst
- my_notes.html: note-item__image-box--label branch for swatch_label
- _note.scss: .note-item__image-box--label modifier (bold italic, solid border)
- _base.scss: .ord global ordinal superscript class (21st etc.)
- ITs: SuperuserNoteGrantTest (3); SigSelectRenderingTest +2 (super- variants)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:30:02 -04:00
Disco DeDisco
1c2b8f96ab SIG SELECT: Nomad/Schizo locked by default; Note-unlock gate — TDD
- _filter_major_unlocks(cards, user): strips Major 0 (Nomad) and Major 1
  (Schizo) unless user has matching 'nomad'/'schizo' Note; unauthenticated
  users see 0 majors
- levity_sig_cards(room, user) / gravity_sig_cards(room, user): accept user
  param; default 16 court cards, up to 18 with both Note unlocks
- View wires user into both calls; _sig_unique_cards / sig_deck_cards unchanged
  (game-table deck still includes all 18 unique)
- _full_sig_setUp: seats now carry deck_variant=earthman
- SigCardHelperTest: 4 new ITs (default 16, nomad +1, schizo +1); empty-deck
  test updated to clear seats + owner
- SigSelectRenderingTest: 18-card test updated to 16-default + 3 Note-unlock ITs

Pending: superusers auto-granted nomad + schizo Notes on creation (ask user)

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