The Voronoi felt gains a stripped sky-wheel rim drawn from the ROOM's own sky
(Room.sky_chart) — identical for every gamer, updating toward the shared map —
with the tessellation sized into the wheel's freed hub. Roadmap step 21, Step
2's coordinate frame.
- Room.convened_at + Room.sky_chart (migration 0019); pick_roles stamps
convened_at at gate-close (stamp only — no HTTP on the transition)
- epic.table_sky: lazy planets-only chart via PySwiss at the null location
(geocentric longitudes need only the convened TIME; houses/ASC/MC need a
birth LOCATION a virtual table lacks → omitted), cached on Room.sky_chart;
legacy rooms key off created_at; seated-gamer gated, 502 on PySwiss down
- SkyWheel.drawRim(svg, data): pure static renderer — canonical asc=0 frame,
signs ring + planet glyphs only, NO element ring / centre disc / houses /
axes / aspects / tooltips; never writes the interactive wheel's singleton
state; returns {size, cx, cy, r, hubR} so the felt sizes the map into the hub
- _seed_map_overlay.html: rim draws on open; map svg shrinks to 2×hubR +
.voronoi-map--rimmed clip; lazy table-sky fetch on open; preload-then-repaint
so a cold-cache open doesn't strand the zodiac glyphs; ResizeObserver on the
col (not the self-sized map svg)
- _sky.scss: stacked centred svgs in .seed-map-col; .seed-wheel pointer-events
none; circle clip on the rimmed map
- room_sky_json ctx in _role_select_context; rootvars: --sixUser/--octUser
nudged within the Trs ramp (parallel palette tune)
- drawRim Jasmine suite (R1–R6: signs+planets, strip, hub geometry, static
placement, singleton untouched, signs-only fallback) in both spec copies;
epic TableSkyViewTest + convened_at stamp + seed-overlay rim ITs; FT rim
assertions (12 signs, 3 planets, no stripped/located layers, hub sizing)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New inline --duoUser felt (sibling of CAST SKY / DRAW SEA) that opens on
#id_seed_map_btn once the 6-card sea hand completes (hand_complete); paints a
Voronoi cell layer (territory) + a Delaunay edge layer (adjacency) from
PLACEHOLDER seeds. Card-driven seeding (the 6 Celtic-Cross cards) is Step 2.
- voronoi-map.js: window.SeedMap.draw/drawPlaceholder/clear over the bundled
d3.min.js (d3-delaunay ships in the v7.9.0 UMD bundle — no new dep); a
ResizeObserver re-tessellates to fill the felt on resize; data-seed reads d3's
ring.index (survives skipped/degenerate cells in Step 2)
- _seed_map_overlay.html felt + room.html include + has-seed-stage (gated on hand_complete)
- three-way felt close (T3): openSeed closes sky+sea; openSky/openSea reciprocally close seed
- .room-menu-seed gear NVM pane + room-views.js seed-open branch
- _sky.scss felt block (T1: no aperture-fill light; T2: chained selector; fills the pane) + _room.scss aperture pin
- VoronoiMapSpec Jasmine + game_room_seed_map FT + 5 felt ITs; CarteTray NVM count 2->3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Aspected/Unaspected micro-tooltip (the DON|DOFF state read-out, mirroring the
game-kit Equipped/Unequipped chip) showed on the standalone sky.html but NOT on
the home-page SkyDrive applet or the gameroom Sky Select felt. Root cause: the
sky-wheel grabbed #id_mini_tooltip_portal — the SAME element the wallet + game-kit
token tooltips use (wallet.js / wallet-shop.js / gameboard.js). On the home page,
where the My Wallet AND SkyDrive applets coexist, the wallet left an inline
display:none on the shared element that the sky-wheel's class-based `.active`
{display:block} could never override (inline > stylesheet). sky.html only worked
because it has no wallet. Live trace: the element even carried the wallet's
"In-Use: <room>" text while the sky tried to show "Unaspected".
Fix — separate elements, no shared state:
- sky-wheel.js reads its own #id_aspect_mini_portal (not #id_mini_tooltip_portal).
- _gameboard.scss: the italic/right-aligned aspect-portal rule + `.active`
{display:block} rescoped to #id_aspect_mini_portal. The wallet/kit keep
#id_mini_tooltip_portal (_wallet-tokens.scss) untouched.
- home.html now ships BOTH portals (wallet #id_mini_tooltip_portal +
#id_aspect_mini_portal); sky.html renames its portal; room.html adds
#id_aspect_mini_portal in the SKY_SELECT block (covers initial CAST SKY + the
saved-wheel revisit — same #id_sky_tooltip block).
Verified live (Claudezilla): home-page applet + sky.html now show the aspect
micro-tooltip (display:block, "Unaspected" ⇄ "Aspected" on DON|DOFF) with the
wallet element untouched. TDD: SkyWheelSpec +1 — a planet activation touches only
#id_aspect_mini_portal, leaving a sibling #id_mini_tooltip_portal (w. its inline
display:none + content) untouched. 506+1 Jasmine specs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three threads:
1. Reversal-aware SEA_DRAWN prose. The affinity clause now reads "the {orientation}
of the {card}" — "emanation" upright, "reversal" when the drawn card is reversed
— and a reversed card's abbrev gains a ℞ (U+211E, the sky wheel's retrograde
glyph) <sub> subscript inside the existing no-wrap span, e.g. "(Q ♥℞)".
_sea_affinity_for now carries the per-card `reversed` flag (it was in the hand
entry but discarded) → event.data → to_prose via d.get("reversed", False).
_card_abbrev gains reversed_flag=False so the shared SIG_READY caller is
untouched (significators are picked, never drawn reversed). TDD: drama ITs +2
(reversal orientation + ℞; upright emanation w.o ℞), epic IT +1 (reversed flows
end-to-end), existing SEA_DRAWN prose tests updated to "emanation of the".
2. Reelhouse sea/sky reel-up swipe machine. The burger Sea/Sky reopen btns are
.active from anywhere in the reelhouse, but opening a felt while parked on the
reelhouse pane pinned the aperture there (html.*-open freezes the scroll) with
the felt stranded off-screen in the hex pane (scroll-locked; NVM was the only
escape). New RoomViews.scrollToHex(cb): from the reelhouse it reels the aperture
UP to the ROOM hex, then fires the open cb on settle (the banner h2 reel follows
via the existing IO); already at the hex → synchronous (in-hex CAST SKY / DRAW
SEA unchanged). afterDescent (scrollend + 700ms ceiling) hoisted out of the Text
sub-btn machine + shared. Both sub-btn handlers (burger-btn.js sky, _sea_overlay
sea) route through it. Pinned in Jasmine (RoomViewsSpec +2) per the headless-
drops-delayed-scroll trap — the delayed-vs-synchronous DECISION, not real scroll.
3. SCROLL gear OK now closes the menu. room-scroll.js's filter-submit handler
referenced an UNDECLARED `roomMenu` → ReferenceError thrown AFTER applyFilter
ran, so the filter applied but the menu never closed. Look it up via
getElementById (mirrors the ATLAS OK / applets close). FT +1.
506 Jasmine specs + 8 room-scroll FTs + 61 drama + 649 epic/billboard ITs green.
[[project-sea-select-scroll-provenance]] [[feedback-headless-delayed-scroll-dropped]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SIG_UNREADY ("disembodies POSS Significator") + SEA_RELINQUISHED ("relinquishes
POSS affinity with the X") are the character un-doing its Significator / Sea —
the un-do counterparts of the fable SIG_READY / SEA_DRAWN. They were stranded in
FABLE_VERBS' blind spot, so the scroll tagged them data-base="frame" with a bare
@handle instead of "@handle's character", and the Fable filter never caught them
(a disembody log showed under Frame, split from its embody pair).
Add both to FABLE_VERBS so they render data-base="fable" + the "@handle's
character" stub and ride the Fable filter with their pair. The billboard recent-
room EMBED still drops SIG_UNREADY noise (a verb-based exclude, untouched); the
redact-pair machinery is verb-keyed too, so is_fable has no functional effect
beyond display/filter.
TDD: drama ITs +2 — sig-unready + sea-relinquished render fable + character
stub; is_fable test moves both verbs to the fable group (only table/gate events
— deposits, chair assignment — stay frame). 59 drama + 241 billboard + 407 epic
ITs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SCROLL (+ Billscroll) Frame/Fable/Redact filter treated redact as a 3rd
mutually-exclusive label, so a struck row showed whenever Redact was checked
regardless of its underlying tag. Per user-spec, Redact now layers on the base:
a struck row shows only when BOTH its base (Frame/Fable) AND Redact are checked.
So Fable+Redact (Frame off) hides every Frame log incl. redacted Carte Blanche
lines; Frame+Redact (Fable off) hides @disco's-character Fable logs even though
Redact is on.
- _scroll.html: each row now carries data-base (frame|fable, ALWAYS — the
underlying tag) alongside data-label (collapses to redact when struck).
- room-scroll.js + billboard scroll.html applyFilter (mirrored): visible =
base-checked AND (not struck OR redact-checked).
TDD: drama ITs +1 — data-base asserted on fable/frame/struck-fable/struck-frame
renders (struck keeps its base). Room scroll FT +2 — fable+redact/frame-off
hides all frame incl. struck; frame+redact/fable-off hides the struck fable
despite Redact on. Existing redact-hides-struck + billboard gear-flow FTs still
green (their struck rows are fable-based + never toggle Fable). 57 drama ITs + 7
room-scroll FTs + 3 billboard gear FTs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gear's CAST SKY + DRAW SEA NVM panes linked to a bare epic:room with no ?seat, so on cancel a CARTE multi-seat gamer fell back to owned[0] (pos 1): revisiting Sky/Sea Select at pos 2 + clicking NVM landed him on pos 1's view (e.g. pos 1's SEED MAP) instead of pos 2's own DRAW SEA. Append ?seat={{ current_slot }} to both felt NVMs (guarded on current_slot) — the same fix shape room_gate already applies to the CONT GAME btn + gate NVM (table_url). Single-seat gamers get their sole slot; spectators (current_slot None) fall back to the bare URL.
TDD: CarteTrayFollowsSelectedSeatTest +2 — test_sky_sea_nvm_carry_acting_seat (?seat=4 → both panes' NVM carry seat 4) + test_sky_sea_nvm_default_targets_lowest_owned (no ?seat → seat 1 = lowest owned). 17 ITs green.
[[project-sig-select-seat-switch-open-problems]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RoomVoiceActivationTest gains test_voice_toggles_off_when_depositors_recede_to_one: asserts active at high-water (6 distinct cost-current depositors), then collapses all filled slots to one gamer + asserts off — proving the >1 gate has no memory of the prior count (recomputed each render). Pairs with the existing flips-on test (1->2) for the full up/down round-trip. 14 ITs green.
[[project-my-sea-invite-voice-blueprint]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Was 'assumes Nth Chair; SUBJ will start the game as the Role.' Now joins with ', where' and appends the role code in a no-wrap bracket (the Player [PC], the Builder [BC]…), matching the SkyDrive/Sea abbrev treatment. Chair stays capitalised (user-confirmed). 56 drama ITs green; the billscroll FT's 'assumes 1st Chair' assertion still holds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A batch of character-creation provenance polish (user-spec 2026-06-09):
- SKY_SAVED prose: 'beholds the skyscape of POSS birth, which yields OBJ a unique X capacity' -> 'fires up the SkyDrive to observe that POSS cosmic origins yield OBJ a unique X [Xc] capacity' — each capacitor now carries its short code (Ossum [Om], etc.) from a new epic.utils.CAPACITOR_ABBREV (mirrors ELEMENT_INFO.abbr in sky-wheel.js). SEA_DRAWN 'Sea of cards' -> 'Sea of Cards'; the AC clause 'sinister' -> 'offhand connections'.
- Fable tag: a new GameEvent.is_fable (SIG_READY / SKY_SAVED / SEA_DRAWN) makes the shared _scroll.html render data-label='fable' (not 'frame'; struck still wins -> 'redact') + prefix '@handle's character' — a stub for the character's name once set. Both scroll gear filters (room + billscroll) gain a Fable checkbox so fable rows stay filterable.
- Usernames: the scroll @handle <strong> is now bold --quaUser (breaks up the --secUser monotony, mirroring .atlas-row-who); the fable "'s character" stub sits OUTSIDE the strong so it stays --secUser. Mirrored into the ATLAS via .atlas-row-body strong (provenance rows embed the whole SCROLL body).
- No-wrap: card parentheticals (Q [icon]) + element brackets [Om] ride white-space:nowrap spans so an abbreviation never splits across a line wrap. Shared _card_abbrev helper for SIG_READY + SEA_DRAWN.
TDD: drama prose tests reworked (SkyDrive + bracket codes, no-wrap spans, Sea of Cards, offhand); is_fable + scroll-render ITs (fable->fable label + character stub; non-fable->frame; struck fable->redact). 70 drama + 918 epic+billboard ITs green. Existing billscroll/room-scroll filter FTs unaffected (their frame events are ROLE_SELECTED/deposits, their redact are struck SIG_READY).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two user follow-ups to the personalized affinity prose: (1) the corner-rank + suit-icon abbrev rides the card name again (e.g. 'the Queen of Pentacles (Q <i class=fa-crown></i>)'), mirroring SIG_READY — majors render just the numeral, e.g. '(I)'. (2) The NC clause conjugates the subject verb: they + yo are treated as PLURAL -> 'they leave' / 'yo leave behind'; the singular he/she/it keep the 3rd-person 'leaves'.
TDD: per-role-clauses test updated to 'they leave'; new yo-pluralizes + he-keeps-leaves cases; a with-abbrev PC case asserting the full rank+icon parenthetical. 51 drama+epic ITs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the generic 'finding affinity with the X in the Crown' SEA_DRAWN prose with a role-specific clause per user-spec 2026-06-09 — the @handle is template-prepended, pronouns (subj/poss) resolve via the actor:
PC: draws POSS Sea of cards, where the CARD crowns all POSS loftiest illusions. NC: ...the CARD traces all the narratives SUBJ leaves behind. EC: ...always looms before POSS calling. SC: ...covers all POSS righteous conduct. AC: ...always crosses POSS sinister connections. BC: ...lays all POSS foundational work.
to_prose branches on data['role']; 'The ' still stripped so a qualifier butts the proper name; an unknown role falls back to a generic 'marks POSS affinity' clause. The stored position_label/corner_rank/suit_icon are now unused by the prose but left in the event data (harmless). TDD: drama prose tests reworked (per-role clauses + bawlmorese pronoun substitution) + the epic affinity-prose IT updated to the PC crown clause. 49 drama+epic ITs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both SigSelectChannelsTest cursor/reservation FTs hardcoded the OLD --priYl = rgb(255,207,52); the palette tuning moved --priYl to 255,227,82, so the rendered NC role colour no longer matched and both failed in the test-two-browser-FTs stage (build 377). Now each reads --priYl off document.documentElement at run time and compares whitespace-stripped (so 255,227,82 matches rgb(255,227,82) and the rgb()/rgba() box-shadow forms alike) — future palette tweaks to --priYl won't re-break them. Docstrings de-hardcoded too. Couldn't run the channels/two-browser stage locally (needs Redis + dual Firefox); verified --priYl is a :root base var + py_compile clean; the assertion now tracks whatever --priYl renders.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Sea Select Scroll-log sprint (d28046f) added two verbs to GameEvent.VERB_CHOICES but not the AlterField migration Django generates for a choices change, so the pipeline's makemigrations --check failed early. 0005_alter_gameevent_verb regenerates it; applies clean. No data change (choices is validation-only).
- bundled (parallel work): rootvars.scss ongoing palette tuning.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pairs with the dubbodeck assembly: a mixed pile can now hold cards from up to three decks per polarity, so the single server-rendered back (the viewer's seat deck) was wrong for any cross-deck card. Now per-card: each .sig-card thumbnail carries data-back-image-url (its OWN deck back, only for non-polarized image decks; empty otherwise); stage-card.js fromDataset reads back_image_url + _setImageMode repoints the stage .sig-stage-card-back-img at the FOCUSED card's back and shows/hides the FLIP affordance per card (a backless card in the pile hides FLIP + drops any flipped state).
- _sig_select_overlay.html: the stage back-img + FLIP now render on sig_pile_has_backs (ANY pile card is a non-polarized image deck) instead of the viewer's-seat-deck gate, so an RWS grails/blades card in a PC-earthman viewer's pile can FLIP. Initial src dropped (JS sets it on first focus). epic/views.py computes the flag where sig_cards is set; _court_cards/_major_cards gained select_related(deck_variant) to keep the per-card deck access N+1-free.
TDD: 3 ITs in SigSelectUnifiedStageTest (non-polarized image deck -> per-card /static back url + sig_pile_has_backs + stage back-img/FLIP rendered; polarized deck -> empty back + no FLIP element; text-only deck -> empty back). 562 epic view+model ITs green. The JS repoint/hide is the stage-Jasmine debt (deferred) — manually verified.
- bundled (parallel work): rootvars.scss ongoing palette tuning.
[[project-deck-segment-model]] [[project-image-based-deck-face-rendering]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refines the room voice activation per user-spec: voice mirrors my_sea's window but over the seat's 7d INITIAL cost period (GateSlot.cost_current — within [filled_at, filled_at+7d), NOT the renewal grace). voice_active now needs the gate CLOSED (table_status set = ROLE_SELECT onset) + the viewer seated + MORE THAN ONE distinct gamer holding a FILLED, cost-current seat. A sole depositor — including a CARTE owner occupying all 6 seats (one gamer) — keeps voice OFF (no one to talk to); a 2nd qualifying gamer flips it on; it toggles back off once the cost period lapses into grace. Replaces the earlier phase-set gate (ROLE/SIG/SKY only) — voice now spans the whole 7d window incl. IN_GAME (the phase ceiling at SEA_SELECT made the 7d expiry unreachable).
TDD: RoomVoiceActivationTest reworked (13 ITs) — active w. 6 depositors across ROLE/SIG/SKY + persists in IN_GAME; inactive for a single depositor (CARTE-all-6), flips on once a 2nd gamer qualifies, inactive when filled 8d ago (grace), inactive before the gate closes, inactive for a non-seated viewer; room-id / muted-at passthrough + active-btn markup unchanged. 736 epic+drama ITs green.
[[project-my-sea-invite-voice-blueprint]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each polarity's 18-card sig pile now assembles from THREE seats by segment, each FROM THAT SEAT'S OWN deck: CROWNS/BRANDS courts (8) from the cb role's deck, GRAILS/BLADES courts (8) from the gb role's, majors 0/1 (2) from the tr role's — levity cb/gb/tr = PC/SC/NC, gravity = BC/AC/EC. So an RWS King of Grails (SC seat) can sit beside a Minchiate Queen of Wands (PC seat) in one levity pile, each carrying its own deck_variant -> per-card face/back art, NO schema change (cards already carry deck_variant).
- models.py: new _POLARITY_SEGMENT_ROLES + _seat_deck_for_role + _court_cards + _major_cards + _polarity_sig_cards. A missing seat/deck falls back to _room_deck_variant, so a single-deck (or CARTE-solo) room assembles the IDENTICAL 18-card pile it always did (16 courts + 2 majors). sig_deck_cards is now the UNION of both polarity piles (note-unfiltered) -> select_sig's pick validation (views.py:1664) accepts a card from EITHER deck/polarity with no view change. Dropped the now-dead _sig_unique_cards; _sig_unique_cards_for_deck stays for personal_sig_cards (my_sign, single equipped deck).
- TDD: DubbodeckAssemblyTest (4 ITs) — levity grails/blades come from the SC seat's distinct deck while crowns/brands stay earthman; the gravity pile is unaffected by a levity-seat deck; the validation set spans both decks; CARTE-solo one-deck feeds BOTH polarity piles sharing pks (no cross-polarity dedup). Existing SigDeckCompositionTest (36/16/16/4) + SigCardHelperTest (single-deck counts, note unlocks, share-pks, empty fallback) green — single-deck behavior preserved. 736 epic+drama ITs green.
- bundled (parallel work): rootvars.scss ongoing palette tuning.
[[project-deck-segment-model]] [[project-image-based-deck-face-rendering]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared relative_ts gained a <60min -> 'N min' bucket (a02f347), so a just-now auto_now_add event renders 'N min', not the g:i a clock time BillscrollEntryLayoutTest.test_recent_event_shows_time_format asserts -> red locally + in CI from that commit on. Backdate the 'recent' event 3h (still recent vs. the >1yr old event, but >60min) so it renders clock time, mirroring the existing old-event backdate. Data-only change; the layout/columns test is age-agnostic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the reelhouse recolor: the POST thread keeps its earlier per-line grid (author / text / time columns, 0.1rem --secUser border at the .form-control radius, margin/padding) but drops the background-color + box-shadow per user-spec, so the lines read as outlined rows on the plain wash. The filled-pill chrome remains salvaged in YARN.
- bundled (parallel work): rootvars.scss ongoing palette tuning (--terPer + neighbouring slots).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gameroom DRAW SEA phase now writes drama provenance, mirroring sig/sky. When a gamer's 6-card Celtic Cross COMPLETES, a SEA_DRAWN Scroll log publishes their affinity with the card sitting in their Role-correlated position.
- epic/views.py: ROLE_POSITION_MAP — the user's sixfold index (PC->crown, NC->leave, EC->loom, SC->cover, AC->cross, BC->lay; roles rotate each round, so a seat's CURRENT role drives it) + SEA_POSITION_LABELS (each spread's display label for a position KEY; Waite-Smith's Behind/Before/Beneath + Escape-Velocity's Leave/Loom/Lay both key to the same index). sea_save publishes SEA_DRAWN on the <6->6 completing transition only (a reload that re-POSTs the full hand can't double-publish); a re-draw first redacts the standing relinquishment, then publishes anew. sea_delete redacts the published affinity (the strikethrough) + records SEA_RELINQUISHED in its wake (the redact-pair). _sea_affinity_for mirrors SIG_READY's polarity-qualified name_title + corner abbrev; _redact_standing_sea_event tests 'not retracted' in PYTHON (the SQLite JSONField exclude-NULL trap).
- drama/models.py: SEA_DRAWN + SEA_RELINQUISHED verbs + to_prose ('draws {poss} Celtic Cross, finding affinity with the {card}{abbrev} in the {Position}.' / 'relinquishes {poss} affinity with the {card}.'); 'The ' stripped so a levity/gravity qualifier butts the proper name. The generic struck/retracted property renders the strikethrough + data-label=redact in _scroll.html unchanged.
TDD: 4 drama prose ITs (affinity statement, spread-label passthrough, relinquishment, struck-when-retracted) + 7 epic ITs (publish-on-complete, position=crown for PC, none-before-complete, no-double-publish-on-reload, DEL redacts+relinquishes, re-draw redacts-the-relinquishment+republishes, DEL-noop-when-nothing-published). 459 drama + epic-view ITs green.
- bundled (parallel work): rootvars.scss --sixUser/--sepUser/--octUser slot reassignments across the forest/khaki/blade palettes (tuning the new reelhouse h2 bgs) + a new --terMrb.
[[project-sea-select-scroll-provenance]] [[feedback-jsonfield-exclude-sqlite-null]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Voice (Phase C, room path) — the my_sea voice mesh now extends to the epic game room. room_view sets voice_active for a SEATED gamer (a TableSeat with their gamer) while table_status is in {ROLE_SELECT, SIG_SELECT, SKY_SELECT} — from ROLE_SELECT onset (all tokens committed, seats pre-created by pick_roles) continuously through SKY_SELECT, which hosts the in-page DRAW SEA felt; dark at IN_GAME. voice_room_id = the room UUID (the WebRTC mesh key), voice_muted_at = the persisted mute so an in-arc nav/refresh rejoins muted (mirrors my_sea). The burger fan's shared #id_voice_btn lights .active + carries data-room-id; room.html now includes voice-glow.js (the glow/pulse machine, coexisting w. the sea-btn glow handoff).
No consumer/JS change needed: RoomVoiceConsumer._can_join already gates an epic room on TableSeat membership, the ws/voice/<str:room_id> route serves both mysea-… + bare-UUID keys, and burger-btn.js/voice-mesh.js read data-room-id generically. TDD: RoomVoiceActivationTest (9 ITs — active across the arc, dark at IN_GAME, dark for a non-seated viewer, room-id = UUID, muted-at passthrough) + RoomVoiceConsumerEpicGateTest (2 channels ITs — seated gamer admitted + receives welcome; non-seated refused). 390 epic-view + 11 voice channels ITs green.
- _room.scss: POST composer kept as a flex row — the OK btn sits inline beside the 'Enter a post line' input (the felt/pill chrome stays salvaged in YARN, but the input<->OK row layout is retained; follow-up to 577ef30).
- bundled (parallel work): _room.scss reelhouse h2 font --priUser -> --secUser (SCROLL/POST/PULSE); rootvars.scss chroma-hue primaries brightened (yellow/lime/cyan/indigo/violet/fuschia/magenta).
[[project-my-sea-invite-voice-blueprint]] [[project-character-creation-spec]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The token + position-circle expiry tooltips hardcoded 'expires' and never
flipped to 'expired' once the time passed — a seat whose `cost_current_until`
(the 7d cost clock) lapsed still read 'expires 11:30 p.m.' (staging 2026-06-08).
- New `expiry_phrase(dt)` filter (lyric_extras): 'expires <when>' for a FUTURE
datetime, 'expired <when>' for a PAST one — the verb carries the tense so the
underlying `relative_ts` stays direction-agnostic. Wired into
`Token.tooltip_expiry` + the position-circle `data-tt-expiry` (position-tooltip.js
copies it verbatim, so no JS change).
- `relative_ts` gains a <60min → 'N min' bucket (buckets: 60min / 24h / 7d /
12mo / >12mo). Per user-spec it stays SHARED, so scroll.html's provenance feed
(+ post.html / my-games row-ts) now reads 'N min' for very recent events too.
TDD: relative_ts <60min past+future + the 1h boundary; expiry_phrase
none/future/past/wraps-relative_ts; billboard post-line test updated (3h→clock-
time, + new just-posted→'N min'). 727 lyric+billboard+gameboard ITs green.
Bundled (parallel work): rootvars.scss chroma-hue primaries brightened
(--priRd/Or/Gn/Tk/Bl/Id + --terGn).
[[project-position-circle-tooltips]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A CARTE owner (all 6 seats, both polarities) entering SIG_SELECT with no ?seat
saw the sig overlay + reserve URL locked to the canonical PC seat (one polarity)
while the TRAY followed `current_slot` = owned[0] (the lowest owned slot, often
the OTHER polarity). A sig reserved from that view filed against the WRONG seat
— the seat the owner thought he covered stayed empty — so that polarity never
reached 3-ready, its 12s countdown ran to 0 but the server-side `_fire` bailed at
`len(ready) < 3` and never advanced; the other polarity proceeded. Switching
pos-circles via GATE VIEW (sets ?seat) re-aligned every surface and unstuck it.
A 3-agent trace confirmed the mechanism + corrected my first guess: this is NOT a
WS problem (the cursor group only drives the cosmetic flashing numeral; the
SIG→SKY advance is a threading.Timer broadcasting to the room_<id> group every
socket joins). The stall is the misfiled reservation → 3-ready COMPLETENESS
failure, rooted in two seat resolvers disagreeing when seatless: the overlay /
sig_confirm use `_canonical_user_seat` (PC-first) while the tray / reserve use
`_viewer_current_slot` owned[0].
Fix: `room_view` redirects a seatless multi-seat owner (gate_slots.filter(gamer)
.count() > 1) in SIG_SELECT to ?seat=<current_slot>, so EVERY surface (tray,
overlay, reserve URL, WS cursor group) resolves to one seat via the already-correct
?seat path — the same realignment a GATE-VIEW switch does. SIG_SELECT-only
(SKY_SELECT already keys off selected_seat); single-seat gamers / non-owners / anon
fail the guard. Unaffected: the multi-gamer sig FTs (one seat each) + the WS-direct
CarteCursorGroupTest.
TDD: 6 ITs in CarteTrayFollowsSelectedSeatTest (redirect / ?seat-present no-redirect
/ overlay+tray agree post-redirect / single-seat / non-owner / SKY_SELECT
no-redirect); the red `'PC' != 'BC'` was the divergence itself. 381 epic-view ITs
green.
[[project-sig-select-seat-switch-open-problems]] [[feedback-ws-cursor-group-must-match-acting-seat]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A deposited trinket binds `Token.current_room` (the 'In-Use: <room>' Game Kit
label), set on deposit + cleared only on MANUAL return. Once a seat's token cost
lapsed (7d → GATE VIEW returns to prompt re-deposit) nothing freed the binding —
`cost_current` is a render-time prop, so no 7d mutation — leaving e.g. a CARTE
used a week prior stuck 'In-Use' (staging, 2026-06-08).
Fix — a uniform, type-agnostic in-use clock:
- New `Token.in_use_since`, stamped when `current_room` is set (`debit_token`
COIN, `drop_token` CARTE). `release_lapsed_trinkets(tokens)` frees
`current_room` + `in_use_since` + `slots_claimed` once held >= the room's
`renewal_period` (7d). The SEAT is untouched — renewal grace (to 2xspan) +
auto-BYE stay `_expire_lapsed_seats`'s job (a separate, later threshold).
- PASS/BAND never bind (reusable keys) -> no-op; COIN + CARTE covered by the one
rule. Fires on the gameboard / `_game_kit_context` render (the gamer sees it
freed immediately) + the cron backstop (`expire_lapsed_room_seats`).
- Migration 0018 backfills `in_use_since` from the earliest backing-slot
`filled_at` so existing staging bindings release on accurate timing (old free
on next sweep, recent keep grace) instead of every legacy NULL releasing early.
Retired COIN's bespoke ROOM cooldown: `debit_token` no longer sets
`next_ready_at` + `return_token` no longer clears it. `next_ready_at` is now
My-Sea-exclusive (the 24h DRAW cooldown, no room context) — the two clocks no
longer share a meaning + can't clash (user-spec 2026-06-08).
TDD: ReleaseLapsedTrinketsTest (6) + cron release + 2 gameboard-immediacy tests +
updated return/tooltip/bind assertions; 1124 lyric+epic+gameboard ITs green.
[[feedback-equip-slot-gates-trinket-use]] [[feedback-my-sea-cooldown-design]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure rename, no behavior change — the deck-stack reveal btn kept the name `.sea-stack-ok` from when it said OK; it has rendered FLIP for a while. Renamed across all 8 references: _card-deck.scss (reveal + pointer-events rules) + a _gameboard.scss comment; the 3 templates that emit or query it (_sea_overlay.html, _my_sea_deck_stack.html, my_sea.html inline JS); and the 3 tests that select it (test_game_room_select_sea FT, test_game_my_sea FT, test_sea_visit IT rendered-HTML asserts). Verified 47 green across all three surfaces (visit IT + gameroom PickSeaDeal stack FT + my_sea CardDraw FT).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_clicking_stack_shows_ok_btn is the only PickSeaDealTest that pairs _choose_spread w. an is_displayed() assert (the six-draws test checks the class instead). Once a spread is OK'd the felt scroll-snaps onto two pages + the deck stacks sit on the page the post-OK rAF scroll-to-cross targets — but headless Firefox DROPS that delayed scroll, leaving the clicked stack's revealed FLIP btn off the visible page → is_displayed False. scrollIntoView the btn first so the assert reflects the `--active` reveal, not the scroll position. Surfaced in CI build 374's channels stage (a flake that 373 survived on retry; my bd9155c preview edits only shifted the timing).
[[feedback-headless-delayed-scroll-dropped]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- the `.sea-stage--gravity/levity .sea-stage-card.is-flipped-to-back::after` 0.3 fill differentiates the two halves of a monodeck CLONED into a two-toned dubbodeck — only meaningful in Sea Select. my_sea / my_sea_visit draw a single monodeck that is never cloned, so the tint was just noise on their FLIP'd card-back
- the shared `_sea_stage.html` now takes a `sea_stage_dubbo` flag; only `_sea_overlay.html` (the gameroom Sea Select felt) passes it → `.sea-stage--dubbo` on #id_sea_stage. The two tint rules gate on `.sea-stage--dubbo.sea-stage--gravity/levity`; non-dubbo stages fall through to the empty, fill-less base `::after` (no visible overlay)
[[project-deck-segment-model]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- rename the spread-preview's `.sea-pos-*` cells to `.sea-prev-pos-*` (template + aliased Celtic-cross geometry under `.sea-cross--preview` in _gameboard.scss + the SeaDealSpec fixture) so a bare `.sea-pos-*` matches ONLY the live `.my-sea-cross`; fixes test_picker_renders_sao_default_position_subset (was 2≠1 — the preview duplicated every cross cell)
- spread dropdown: cap `.sea-select-list` w. max-height + overflow-y:auto, & JS-click the option in the `_pick` FT helper, so the last option (escape-velocity) can't land below the un-scrollable picker-modal fold in landscape (ElementNotInteractable, seen in CI build 373)
- _retry_failed.sh: anchor the FAIL/ERROR label sed to the FIRST paren group so parameterized subTest failures retry the real method label, not a bogus `position='…'` module (ModuleNotFoundError)
[[project-my-sea-roadmap]] [[feedback-collectstatic-before-ft]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 06-07 modal→felt scroll-snap refactor dropped LOCK HAND (→ AUTO DRAW +
auto-completion on the 6th draw) and moved the deck stacks into the cross-col
(`.sea-page--room:not(.sea-spread-chosen) .sea-cross-col{display:none}`), but the
channels `PickSeaDealTest` was never updated → 3 reds in the two-browser/channels
CI stage:
• test_lock_hand_btn_present_and_disabled + test_lock_hand_enables_after_six_draws
— looked up the removed `id_sea_lock_hand` (NoSuchElement).
• test_clicking_stack_shows_ok_btn — the stack OK is hidden until a spread is OK'd.
Fix: deleted the disabled-LOCK-HAND test; rewrote the six-draws test to assert the
new synchronous completion (the deck-stack FLIP btns gain `.btn-disabled` the
instant the 6th card lands, before the 3s felt cascade — SEED MAP is IT-covered);
added a `_choose_spread()` helper (clicks `id_sea_confirm_spread`) so the
stack-interaction tests run against the revealed cross page. Pre-existing 06-07
staleness — NOT tonight's glow/swap/applet commits (verified: `_load_sea_overlay`
opens the sea felt with no sky felt open, so the new swap guard is a no-op).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applet titles + gear-menu toggle labels (Applet.name, via data migration 0014)
both retitled — a deliberate departure from the "My X" applet convention for
these two surfaces. Slugs (my-sky / my-sea) stay put.
Recolor (applet shells only — the standalone pages are tomorrow's todo):
• SkyDrive (uranium ramp): --sixU light-green title font, --priU deep-green
shell bg; hover keeps the palette --ninUser highlight, glow takes the --sixU
tint. Reverses to --priU font / --sixU bg on *-light palettes.
• Sea of Cards (neptunium ramp): --sixNp light-teal font, --priNp deep-teal shell;
the conjunction "of" is a lowercase-italic span against the h2 uppercase
transform. Reverses to --priNp / --sixNp on *-light palettes.
Mechanism: the shared applet title-link rule now reads --applet-title-fg (with a
--terUser fallback so every other applet is untouched); each applet sets that +
--applet-shell-bg, and a body[class*="-light"] override swaps the pair. No
specificity war.
Tests: data migration 0014 (reversible); FT/IT applet seeds + the My Sky heading
assertion updated to the new names; 531 dashboard/gameboard/applets ITs green.
[[feedback-applet-vs-page-naming-convention]] [[feedback-scss-id-context-specificity-trap]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Symmetry (user-spec): Sky Select leaves the burger sea btn lit while the sky
felt is up; Sea Select now mirrors it — openSea disables ONLY id_text_btn, no
longer id_sky_btn, so a revisited Sea Select keeps the completed sky one click
away. An adversarial pass flagged that the two felts are equal-z (z-index:5)
siblings and neither open path drops the other's open-class, so leaving the sky
btn lit and clicking it from inside the sea felt would DOUBLE-OPEN (sea paints
over sky → confusing no-op) — the same latent stack already reachable in the
sky->sea direction on a both-complete reload. Fixed in BOTH directions: openSea
now closes the sky felt first + openSky closes the sea felt first (each exposes
its close on window: closeSeaFelt / closeSkyFelt), so clicking the other phase's
lit btn performs a clean SWAP. The text-btn disable/restore chain stays correct
across the swap (closeSky restores text, openSea re-disables + recaches it).
Glow: the glow-handoff pulse ease-OUT now runs ~2.8s (was ~1.4s) — moved the
cycle to 3.2s with the bright peak at 12.5%, keeping the ease-IN swell at ~0.4s
(user asked for a longer linger).
[[feedback-felt-aperture-fill-covers-felt]] [[feedback-inline-partial-script-defer-for-later-partial]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
room.html includes _sea_overlay.html (~L77) BEFORE _burger.html (~L167, which
holds #id_burger_btn + #id_sea_btn), so the overlay's two inline scripts
captured those btns at parse time → null → both bindings silently no-op'd:
the sea btn (active post-completion) did nothing on click + the burger stayed
stuck --priId because its glow-handoff transfer listener never bound. Defer both
the sea-btn REOPEN binding (_bindSeaReopen) & the burger→sea_btn→.sea-select
GLOW chain (_bindGlowHandoff) to DOMContentLoaded so the burger fan exists first.
Also make the glow-handoff halo PULSE (quick ease-in swell, slow ease-out decay
via per-segment timing fns + a lopsided 22/78 keystop split) instead of a flat
glow — the burger, then the sea btn after the handoff click, keep cueing
"click me to reopen your sea".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The option chunks' width tracked the select label length (jumped between the
two spreads). Fix the `.sea-form-col` to 19rem (a touch wider) so all three
chunks share a standard width.
- Split the spread label onto two lines around the comma + add the "Rider-"
prefix now there's room: "Celtic Cross," / "Rider-Waite-Smith" and "Celtic
Cross," / "Escape Velocity". combobox.js now writes the current label via
`innerHTML` (not textContent) so the `<br>` survives a selection — plain-text
options (sky / my_sea) are unaffected (their innerHTML == textContent).
- ITs updated for the new gameroom labels (my_sea's single-line names untouched).
953 epic+gameboard ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- A disabled OK/DEL × inside a --priUser option chunk blended into it (the global
`.btn-disabled` bg is also --priUser → no visible circle). Drop the disabled
btns in `.sea-options-col` to the felt --duoUser so they read as a distinct
disabled circle, like the deck-stack FLIP ×.
- AUTO DRAW now eases the felt back UP to the cross even when the user already
OK'd the spread + scrolled DOWN to the options page — so he watches the cards
land one-by-one. `_chooseSpread(slideIn)`: the OK reveal pins to the options
(slide-in from above); AUTO DRAW (already chosen) skips the pin + just eases up
to the cross. `_scrollToCross` now eases from the current scroll position.
- 12 PickSeaUnifiedFeltTest render ITs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restyle the spread-options page (post the scroll-snap refactor):
- OK `.btn-confirm` moves UP beside the `.sea-select` combobox (a new
`.sea-select-row`), off the AUTO DRAW / DEL action row.
- OK gains `.btn-disabled` + × the moment the first card is drawn — inverse to
DEL (which loses them then), simultaneous with the combobox locking. So
`_chooseSpread` (OK) no longer locks; the lock + both btn states flip together
at the first draw via `_setHasDrawn` + `_lockSpread`. Server-renders OK
disabled/× when `saved_by_position`.
- The three chunks (spread/select/OK, the mini preview, AUTO DRAW/DEL) each get
the same --priUser rounded rectangle as the GAME POST lines / composer
(`_base.scss` `.form-control`): --priUser fill + half-alpha --secUser border +
rounded + padding. The `.sea-form-col`/`-main` go transparent flex columns so
the felt shows between the chunks.
- IT: OK enabled / DEL disabled when fresh; flips once a card is drawn.
953 epic+gameboard ITs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Gaussian spread modal couldn't hang off the burger #id_sea_btn anymore (that
button now also opens the felt). Mirror Sky Select's form→wheel scroll-snap
instead: the felt starts with the spread OPTIONS on the --duoUser felt; clicking
OK confirms the spread → the options shunt DOWN and the spread CROSS takes page 1
(scroll down to find the options again). No modal, no corner NVM.
- `_sea_overlay.html` restructured into `.sea-options-col` (the .sea-select
combobox + mini preview + OK .btn-confirm + AUTO DRAW + DEL — NO deck stacks)
and `.sea-cross-col` (the real .my-sea-cross + the Gravity/Levity deck stacks +
the portaled stage). `#id_sea_overlay` is a `display:contents` passthrough so
the two cols are the scroll-snap sections.
- OK (`#id_sea_confirm_spread`) → `_chooseSpread()`: adds `sea-spread-chosen` to
the felt → SCSS engages `scroll-snap-type:y mandatory`, the cross-col gets
`order:-1` (page 1), options shunt to page 2; locks the combobox; eases the
scroller to the cross. AUTO DRAW also confirms first. A reload of an in-progress
sea renders `sea-spread-chosen` (cross revealed) server-side.
- SCSS (`_sky.scss`): the sea felt is now a column scroller; `.sea-cross-col`
`display:none` pre-confirm; the `sea-spread-chosen` scroll-snap block mirrors
`body.sky-saved`. The options `.sea-form-col` goes transparent/content-sized
(blends onto the felt, not the modal's --priUser card).
- Sea sub-btn: no longer activated by openSea; it's the POST-COMPLETION reopen
affordance (cascade activates it + `sea_btn_active = hand_complete` ctx flag),
an active click → `window.openSeaFelt()` (review the saved spread), like the
sky btn. Removed the sea_btn open-modal IIFE + the corner NVM.
- IT: options-on-felt (combobox + OK + AUTO DRAW + DEL + preview) w. NO modal /
NVM. 952 epic+gameboard ITs + Jasmine + PickSeaAsyncTransitionTest(3) green.
my_sea.html keeps its modal (untouched) — the gameroom intentionally diverges.
[[project-character-creation-spec]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror CAST SKY's post-save cascade for the sea phase. When the 6-card spread
completes (live FLIP of the 6th card / AUTO DRAW finishing): linger ~3s on the
felt → the felt eases OUT (`.sea-page--cascade-out`, revealing the table-hex) →
DRAW SEA gives way to SEED MAP + the sea glow fires on the burger (handoff →
sea_btn) → +3s → SEED MAP eases IN. Same shape as CAST SKY → sky-btn glow →
DRAW SEA.
- `_room_hex_center.html`: SEED MAP joins the hex-phase-stack; DRAW SEA goes
--out once `hand_complete`, SEED MAP --out until then (a reload of a complete
sea lands on SEED MAP server-side = the cascade's end-state). SEED MAP → the
Voronoi map (roadmap step 21) is a stub — it only needs to APPEAR here
- `_sea_overlay.html`: `_setComplete(on, live)` runs `_startSeaCascade()` on the
LIVE completion (FLIP / AUTO DRAW pass `live=true`; init does not, so a reload
doesn't re-animate). The completion-glow IIFE no longer self-starts on the
data-state transition — the cascade adds `glow-handoff` to the burger; the IIFE
keeps only the burger → sea_btn → .sea-select handoff
- `.sea-page--cascade-out` SCSS (mirrors `.sky-page--cascade-out`)
- ITs: SEED MAP --out pre-completion (DRAW SEA in); SEED MAP in + DRAW SEA --out
when hand_complete. 952 epic+gameboard ITs + PickSeaAsyncTransitionTest(3) green
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The felt cross's CENTER significator was hardcoded to the corner-rank + suit-icon
text thumbnail, so an image deck (RWS / Minchiate) showed a bare "0"+icon card
instead of its face. Mirror my_sea: render the `.sig-stage-card-img` (image mode)
when the sig's deck has card images, else fall through to the text thumbnail. The
tray sig stays the simple thumbnail (user-spec). The sig's `deck_variant` is the
card's OWN deck — the Sig Select pick is drawn from the Role Select contributed
deck (`_room_deck_variant`), so this is the correct image source (no equipped_deck
bug, unlike the earlier FLIP back-img).
- IT: an image-deck significator renders the center `sea-sig-card sig-stage-card--image`
- 949+ epic ITs green
; FLIP tint tweak (parallel edit): flipped-back overlay alpha 0.6 → 0.3
[[project-deck-segment-model]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Carte Blanche gamer owns all 6 seats but could only cast sky / draw sea for ONE:
the sky/sea state IS per-seat (Character.seat), but the code resolved to the fixed
canonical (PC-first) seat, ignoring the ?seat switched to via the GATE VIEW pos-
circles — so the tray switched but the sky wheel / sea spread stuck on the
canonical seat + saves wrote back to it. Mirrors Sig Select's existing ?seat path.
- generalize `_acting_sig_seat` → `_acting_seat` (logic is sig-agnostic; 3 callers)
- `_role_select_context` SKY_SELECT branch keys off `selected_seat` (the ?seat-aware
seat, already computed) instead of `_canonical_seat`: user_polarity,
confirmed_char, user_seat_role, my_tray_sig, saved_by_position, saved_sea_spread,
sea_default_spread, hand_complete, sea_back_image_url
- sky_save / sky_delete / sea_save / sea_delete / sea_deck resolve the acting seat
via `_acting_seat(…, request.GET.get("seat"))`; sea_partial threads seat_param
- the sky + sea felts carry `?seat={{ current_slot }}` on their save/delete/deck/
sea_partial action URLs so the POSTs target the switched-to seat
- single-seat flow unchanged (no ?seat → canonical fallback)
- ITs: CARTE owner — ?seat switches the displayed spread; sea_save/sky_save target
the switched seat leaving the canonical seat's Character intact; felt URLs carry
?seat. 949 epic+gameboard ITs green.
; FLIP tint tweak (parallel edit): drop the polarity border, bump the flipped-back
overlay --quiUser/--terUser to 0.6 alpha
[[project-sig-select-seat-switch-open-problems]] [[project-deck-segment-model]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sea-stage FLIP no-op'd in the gameroom because `_sea_stage.html` rendered
the back-img from `request.user.equipped_deck.back_image_url` — but
`select_role` NULLS OUT the user's equipped_deck once it's contributed to the
room's seats (views.py:1148), so in the Sea Select phase the deck was None →
no back-img element → sea.js's FLIP handler short-circuits on the missing
`.sig-stage-card-back-img` sibling. (my_sea worked: solo, no contribution.)
- `_sea_stage.html` now renders the back-img from a `sea_back_image_url` ctx var
instead of `request.user.equipped_deck`
- gameroom: `_role_select_context` sets it from the SEAT's contributed deck
(`_canonical_seat.deck_variant`, when it has card images)
- my_sea: the my_sea view sets it from the user's own equipped deck
- ITs: image seat-deck renders `.sig-stage-card-back-img`; text seat-deck omits it
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- fix: drawn spread slots silently no-op'd on click AFTER a refresh — SeaDeal's
in-memory `_seaHand` is only populated by openStage/register during the live
session, so a reload left it empty + the overlay click handler short-circuited
(`if (!_seaHand[pos]) return`). `_sea_overlay.html` now re-seeds `_seaHand`
from the server-rendered saved slots once the deck fetch resolves (cards
looked up by `data-card-id`; reversed/polarity DOM-sourced) — the same fix
my_sea already carries
- FLIP card-back: the sea stage now renders the deck back-img for ANY image-
equipped deck w. a back image (dropped the `not is_polarized` gate — it
omitted the back for the room's polarized Gravity/Levity draw, so FLIP
no-op'd). The back-art is identical across polarities, so `_card-deck.scss`
tints the FLIPped card by the drawn polarity: gravity --quiUser fill @ 0.3 +
--quaUser border; levity --terUser fill @ 0.3 + --ninUser border (scoped to
`.sea-stage--*`, so my_sign / applet stages are untouched)
- IT: an image-deck sea stage renders `.sig-stage-card-back-img`
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hollow out the gameroom DRAW SEA dark modal into the two surfaces my_sea
uses: a --duoUser felt (.sea-page--room) filling the hex pane where the
Celtic Cross deals, + a Gaussian spread modal (#id_sea_spread_modal: the
.sea-select combobox w. the two Celtic Cross 6-card opts ONLY, AUTO DRAW/DEL,
a mini preview, + a corner #id_sea_cancel NVM). Opened by DRAW SEA
(html.sea-open); the room gear's NVM (room-menu-sea) returns to the hex;
#id_text_btn + #id_sky_btn go inert while the felt is open.
- persist: epic:sea_save / sea_delete upsert the seat's Character.celtic_cross
(none of my_sea's MySeaDraw quota/Brief machinery); room ctx adds
saved_by_position + saved_sea_spread + sea_default_spread + hand_complete so
a reload re-renders the filled cross. celtic_cross field already existed (no
migration)
- mini spread preview (_sea_spread_preview.html) in BOTH the gameroom + my_sea
modals — shape only, NEVER dealt to: SeaDeal scopes its slot queries to
.sea-cross:not(.sea-cross--preview)
- always TWO deck stacks (Gravity + Levity) in the room Sea Select — the gamer
draws from either populated half (sea_deck split), even a CARTE monodeck;
unlike my_sea / Sig Select's polarization collapse
- glow coordination: the sky-saved glow is muted in the sky/sea phases
(html.sky-open / sea-open / sea-entered); sea glow color --priYl -> --priId
(distinct from sky's --priTk); the sea glow-machine fires at hand-COMPLETION
(mirrors Sky Select), not during drawing
- guard copy "Auto deal cards?" -> "Auto Draw cards?" (match the AUTO DRAW btn)
- fix: drop the stale `html.sea-open #id_aperture_fill { opacity:1 }` — it
painted the opaque z-90 fill over the z-5 felt so the spread flashed then
vanished (same trap as the CAST SKY felt); removed the dead .sea-backdrop /
.sea-overlay / .sea-modal-* SCSS
- tests: epic PickSeaPersistTest (7) + PickSeaUnifiedFeltTest (6) ITs; SeaDeal
preview-scoping + BurgerSpec sky-glow-mute Jasmine specs; my_sea sig-card
ITs scoped to .my-sea-cross (the preview adds a 2nd .sea-sig-card)
[[feedback-felt-aperture-fill-covers-felt]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The .room-table-scene → .table-hex-border → .table-hex → .table-center + seats
ring was byte-identical across room.html, my_sea.html, my_sea_visit.html &
billboard/my_sign.html. Lift the skeleton into one cross-app partial so every
surface — and the upcoming Sea Select felt rebuild — shares a single source.
- core/_partials/_table_hex.html: the 4-div skeleton. The varying center +
seats are passed as partial NAMES (`hex_center` / `hex_seats`) since Django
{% include %} has no slot; `{% include hex_center %}` resolves the string var
+ inherits the full page context (no `only`), so each fragment sees its page's
vars unchanged → identical render.
- Per-surface center fragments (verbatim moves): _room_hex_center,
_my_sea_hex_center, _my_sea_visit_hex_center (gameboard/_partials),
_my_sign_hex_center (billboard/_partials).
- Seats fragments: _room_hex_seats (gate_positions); _table_seats — SHARED by
my_sea + my_sea_visit (`seats` loop; the `--self` modifier is inert on my_sea);
_my_sign_hex_seats (single chair).
- The page-specific outer wrappers stay put (room's .room-shell + ROLE_SELECT
SCAN SIGS form; .my-sea-landing / .my-sign-landing; my_sign's
{% if not current_significator %} gate).
The "felt" is deliberately NOT extracted — it's a --duoUser bg toggled by
phase/stage classes (CSS), already DRY; each phase's felt content is bespoke.
Markup-only, no behaviour change. Verified: 1170 epic+gameboard+billboard render
ITs green (the table-hex / table-seat / center-btn assertions are the gate) +
MySeaDrawSeaLandingTest FTs green (live hex render + FREE DRAW seats
.table-seat[data-slot="1"] through the shared seats partial).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running the corequisite channels FTs surfaced a real gap the cascade introduced:
a sky confirm that did NOT come through the felt's own save (a direct POST, or
another browser of the same seat) left _lastChartData null, so _onSkyConfirmed's
_activateSavedState returned early → no transition at all (the old code reloaded).
- _sky_overlay.html: _onSkyConfirmed now reloads when _lastChartData is absent
(the pre-cascade behaviour, preserved for non-felt-save confirm paths); the
felt-save path still eases to DRAW SEA via the cascade (no reload).
- test_game_room_select_sea.py: the async-transition assertion updated for the
phase-stack — CAST SKY is now present-but-`--out` (hidden in the shared grid
cell), not removed from the DOM, so assert the `hex-phase-btn--out` class on
CAST SKY + its ABSENCE on DRAW SEA rather than `find_elements(...) == []`.
Corequisite FTs run green: select_sea async-transition (3) + deal (9) channels;
dash my_sky async-save + aperture-snap (3) — the shared body.sky-saved apparatus
is untouched by the room-scoped felt SCSS. select_sky FTs already green. The sig
SRG7 reveal FT is unaffected (it waits for the SIG_SELECT hidden CAST SKY, which
that branch still renders; it never clicks, so the _reload change isn't reached).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-save choreography + the polish asks from this session.
- Post-save cascade (_sky_overlay.html): after SAVE the gamer lingers on the
wheel ~3s, then the felt eases OUT (fade, .sky-page--cascade-out) to reveal the
table-hex; the burger fires its --priTk glow + #id_sky_btn goes active (both
ride the cascade now, not the save instant); 3s later the DRAW SEA btn eases IN
and the sea overlay is injected so it's live with NO reload.
- Hex phase-stack (room.html + _room.scss): CAST SKY + DRAW SEA share one grid
cell (.hex-phase-stack) so they cross-fade in place (.hex-phase-btn--out); the
server seeds --out on the inactive one, the cascade swaps them. A confirmed
reload lands DRAW SEA visible / CAST SKY out, same as the cascade end.
- Seamless sea injection: _injectSeaOverlay fetches sea_partial (the URL already
existed, unused) + re-creates its <script>s so the overlay's own init (openSea
+ SeaDeal.reinit) runs — DRAW SEA opens with no reload. (Bridge until Sea Select
is itself hollowed into a felt.)
- Burger → Sky-btn handoff (burger-btn.js + _burger.scss): _pulseGlow generalized;
once saved, the next burger-OPEN pulses #id_sky_btn --priTk ("now click me to
reopen"). Jasmine specs added (BurgerSpec).
- #id_text_btn disabled while the felt is up (openSky/closeSky) — its swipe
machine would otherwise half-load the scroll-locked reelhouse.
- Reload-into-open (sig-select.js + _sky_overlay.html): the SIG_SELECT→SKY_SELECT
CAST SKY click crosses a server-render boundary (felt/phase-stack/sea-inject
only exist in SKY_SELECT), so it still reloads — but drops a sessionStorage
flag first so the felt OPENS on arrival. Kills the old click→reload→click-again
double-take (the "first click reloads" report). DRAW SEA can inject in-place
(stays within SKY_SELECT); CAST SKY can't, so this is the seamless equivalent.
Tests: BurgerSpec handoff specs + full Jasmine green; 32 render ITs + 930
epic+gameboard green. Cascade timing live-verified by the user.
[[feedback-scss-import-order-specificity]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>