- 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>
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>
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>
- _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>
- _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>
- 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>
- 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>
- 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>
- 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>
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>
Sprint 2 UI (game kit applet):
- _applet-game-kit.html: in-use deck → two disabled × buttons, .tt-deck-game-name;
in-use Carte Blanche → two disabled × buttons, data-current-room-name,
.tt-token-room-name; tooltip content mirrors kit bag panel (Default, card count,
description, Stock version)
- gameboard.js buildMiniContent: 'In-Use' for tokens w. data-current-room-name set
- _kit_bag_panel.html: Deck section always renders (placeholder when unequipped)
View safeguards:
- select_role: look up existing deck from prior seat in same room before
equipped_deck (Carte Blanche multi-seat); only unequip when using equipped_deck
- drop_token Carte: reject 409 if token.current_room is a different room;
unequip from equipped_trinket on drop
ITs: SelectRoleMultiSeatTest (2), DropTokenViewTest +3 (carte drop, unequip, lock)
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
- _sig_select_overlay.html: add data-levity-qualifier + data-gravity-qualifier
to sig card elements so JS can read per-card values
- sig-select.js: derive qualifier from cardEl.dataset instead of hardcoded string
- _sea_overlay.html: use my_tray_sig.levity_qualifier / gravity_qualifier;
collapse MIDDLE/MAJOR/else branches → MAJOR vs rest (all non-major show
qualifier above name; empty qualifier renders empty <p>)
- views.py: SIG READY event display uses card qualifier fields directly;
removes separate MIDDLE / MAJOR / else branches
Earthman courts now show Elevated/Graven; pips show Relieving/Grieving;
Nomad and Popes show Enlightened/Engraven.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
- sea overlay SCSS moved from _natus.scss to end of _card-deck.scss (correct file for card/overlay primitives)
- Significator center slot: sig-stage-card w. .sea-cross context rule (background, border, aspect-ratio, overflow); fan-card-face name + sig-qualifier-above/below at 0.5rem w. word-wrap
- _sea_overlay.html: qualifier rendered from user_polarity + arcana (MIDDLE → Leavened/Graven above; MAJOR → below); crossing slot removed from HTML + grid-template-areas (deferred re-add later)
- SCSS grid trimmed to 3 rows (crown/past-center-future/root); .sea-pos-crossing class kept for later reuse
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
- _scripts.html: add note.js alongside dashboard.js — Note global now available on dashboard
page, enabling Note.handleSaveResponse from _applet-my-sky.html save handler
- test_dashboard.py: remove stale new-note applet seed (renamed to new-post/billboard)
- test_room_tray.py: remove stale new-note applet seed
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- _computeConjunctions(planets, threshold=8) detects conjunct pairs
- Tick lines (nw-planet-tick) radiate from each planet circle outward
past the zodiac ring; animated via attrTween; styled with --pri* colours
- planetEl.raise() on mouseover puts hovered planet on top in SVG z-order
- Dual tooltip: hovering a conjunct planet shows #id_natus_tooltip_2 beside
the primary, populated with the hidden partner's sign/degree/retrograde data
- #id_natus_tooltip_2 added to home.html, sky.html, room.html
- _natus.scss: tick line rules + both tooltip IDs share all selectors;
#id_natus_confirm gets position:relative/z-index:1 to fix click intercept
- NatusWheelSpec.js: T7 (tick extends past zodiac), T8 (raise to front),
T9j (conjunction dual tooltip) in new conjunction describe block
- FT T3 trimmed to element-ring hover only; planet/conjunction hover
delegated to Jasmine (ActionChains planet-circle hover unreliable in Firefox)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move #id_natus_tooltip out of #id_applets_container (container-type:
inline-size breaks position:fixed) → add to home.html alongside
#id_tooltip_portal
- Move #id_natus_tooltip out of .natus-modal-wrap (transform breaks
position:fixed) → place as sibling of .natus-overlay in room.html
- Add _positionTooltip() helper in natus-wheel.js: flips tooltip to
left of cursor when it would overflow right edge; clamps both axes
- Replace hardcoded 280px in dashboard.js palette tooltip with measured
offsetWidth; add left-edge floor (Math.max margin)
- Planet tooltip format: @14.0° Capricorn (<svg-icon>) using preloaded
_signPaths; falls back to unicode symbol if not yet loaded
- Add .tt-sign-icon SCSS: fill:currentColor, vertical-align:middle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking a swatch instantly swaps the body palette class for a live
preview; OK commits silently (POST, no reload); click-elsewhere or
10 s auto-dismiss reverts. Tooltip portal shows label, shoptalk,
lock state. Locked swatches show × (disabled). 20 FTs green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sky_view passes page_class="page-sky" so the footer pins correctly
- _natus.scss: page-sky aperture block (mirrors page-wallet pattern);
sky-page stacks wheel above form via flex order + page-level scroll;
wheel col uses aspect-ratio:1/1 so it takes natural square size without
compressing to fit the form
- natus-wheel.js: _layout() sets viewBox + preserveAspectRatio="xMidYMid meet"
so the wheel is always centred inside the SVG element regardless of its
aspect ratio (fixes left-alignment in the dashboard applet)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- User model: sky_birth_dt/lat/lon/place/house_system/chart_data fields (lyric migration 0018)
- Applet seed: my-sky (6×6, dashboard context) via applets migration 0009
- dashboard/sky/ — full monoapplet page: natus form + D3 wheel, LS key scoped to dashboard:sky; saves to User model
- dashboard/sky/preview/ — PySwiss proxy (same logic as epic:natus_preview, no seat check)
- dashboard/sky/save/ — persists 6 sky fields to User via update_fields
- _applet-my-sky.html: tile with h2 link to /dashboard/sky/
- 2 FTs (applet→link→form, localStorage persistence) + 12 ITs — all green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- natus-wheel.js: per-planet <g> group with data-planet/sign/degree/retrograde
attrs; mouseover/mouseout on group (pointer-events:none on child text/℞ so
the whole apparatus triggers hover); tooltip uses .tt-title/.tt-description;
in-sign degree via _inSignDeg() (ecliptic % 30); D3 switched from CDN to
local d3.min.js
- _natus.scss: .nw-planet--hover glow; #id_natus_tooltip position:fixed z-200
- _natus_overlay.html: tooltip div uses .tt; local d3.min.js script tag
- T3/T4/T5 converted from Selenium execute_script to Jasmine unit tests
(NatusWheelSpec.js) — NatusWheel was never defined in headless GeckoDriver;
SpecRunner.html updated to load D3 + natus-wheel.js
- test_pick_sky.py: NatusWheelTooltipTest removed (replaced by Jasmine)
- test_component_cards_tarot / test_trinket_carte_blanche: equip assertions
updated from legacy .equip-deck-btn/.equip-trinket-btn mini-tooltip pattern
to current DON|DOFF (.btn-equip in main portal); mini-portal text assertions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>