Sea Select FLIP: source the card-back from the seat's deck, not equipped_deck — TDD

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>
This commit is contained in:
Disco DeDisco
2026-06-07 22:03:19 -04:00
parent ab00774a49
commit de59cb7e69
4 changed files with 40 additions and 19 deletions

View File

@@ -4162,19 +4162,24 @@ class PickSeaUnifiedFeltTest(TestCase):
content = self.client.get(self.url).content.decode() content = self.client.get(self.url).content.decode()
self.assertIn("room-menu-sea", content) self.assertIn("room-menu-sea", content)
def test_sea_stage_renders_back_img_for_image_deck(self): def test_sea_stage_renders_back_img_from_seat_deck(self):
"""The sea-stage FLIP reveals the card back. The back-img now renders for """The sea-stage FLIP reveals the card back. The back-img is sourced from
ANY image-equipped deck WITH a back image (polarized or not) — dropping the SEAT's contributed deck (NOT request.user.equipped_deck, which
the old `not is_polarized` gate — so the Gravity/Levity draw can show a select_role nulls out → the back silently never rendered + FLIP no-op'd)
polarity-tinted back (user-spec 2026-06-07).""" and renders for any image-equipped deck w. a back (user-flagged
2026-06-07)."""
minch = DeckVariant.objects.get(slug="minchiate-fiorentine-1860-1890") minch = DeckVariant.objects.get(slug="minchiate-fiorentine-1860-1890")
founder = self.gamers[0] self.pc_seat.deck_variant = minch
founder.unlocked_decks.add(minch) self.pc_seat.save(update_fields=["deck_variant"])
founder.equipped_deck = minch
founder.save(update_fields=["equipped_deck"])
content = self.client.get(self.url).content.decode() content = self.client.get(self.url).content.decode()
self.assertIn("sig-stage-card-back-img", content) self.assertIn("sig-stage-card-back-img", content)
def test_sea_stage_no_back_img_for_text_seat_deck(self):
"""Earthman (text-mode, no card images) → no back-img element (FLIP is a
no-op for text decks, by design)."""
content = self.client.get(self.url).content.decode()
self.assertNotIn("sig-stage-card-back-img", content)
def test_always_two_deck_stacks_gravity_and_levity(self): def test_always_two_deck_stacks_gravity_and_levity(self):
"""Unlike my_sea / Sig Select, the room Sea Select ALWAYS shows BOTH the """Unlike my_sea / Sig Select, the room Sea Select ALWAYS shows BOTH the
Gravity + Levity stacks — the gamer draws from either populated half Gravity + Levity stacks — the gamer draws from either populated half

View File

@@ -680,6 +680,15 @@ def _role_select_context(room, user, seat_param=None):
) )
# 6-card Celtic Cross is complete at 6 placed cards. # 6-card Celtic Cross is complete at 6 placed cards.
ctx["hand_complete"] = len(_sea_hand) >= 6 ctx["hand_complete"] = len(_sea_hand) >= 6
# Sea-stage FLIP card-back — sourced from the SEAT's contributed deck,
# NOT request.user.equipped_deck (which `select_role` nulls out once
# the deck is contributed to the room → the back-img silently never
# rendered in the gameroom + FLIP no-op'd). User-flagged 2026-06-07.
_back_deck = _canonical_seat.deck_variant if _canonical_seat else None
ctx["sea_back_image_url"] = (
_back_deck.back_image_url
if (_back_deck and _back_deck.has_card_images) else ""
)
return ctx return ctx

View File

@@ -372,6 +372,14 @@ def my_sea(request):
"significator_reversed": sig_reversed, "significator_reversed": sig_reversed,
"default_spread": default_spread, "default_spread": default_spread,
"reversals_pct": 25, "reversals_pct": 25,
# Sea-stage FLIP card-back — my_sea is solo (no deck contribution), so the
# user's OWN equipped deck is the right source (the gameroom uses the
# seat's contributed deck instead; see _sea_stage.html).
"sea_back_image_url": (
request.user.equipped_deck.back_image_url
if (request.user.equipped_deck and request.user.equipped_deck.has_card_images)
else ""
),
"sea_deck_data": ( "sea_deck_data": (
_my_sea_deck_data(request.user, exclude_id=sig_card.id if sig_card else None) _my_sea_deck_data(request.user, exclude_id=sig_card.id if sig_card else None)
if user_has_sig else {"levity": [], "gravity": []} if user_has_sig else {"levity": [], "gravity": []}

View File

@@ -43,20 +43,19 @@
</div> </div>
{% comment %} {% comment %}
back-img + FLIP btn. FLIP reveals the card back. Renders for ANY back-img + FLIP btn. FLIP reveals the card back. Renders for ANY
image-equipped deck that has a back image — INCLUDING polarized decks image-equipped deck w. a back image — INCLUDING polarized decks
(user-spec 2026-06-07): the Sea Select phase tints the back per the (user-spec 2026-06-07): the Sea Select phase tints the back per the
drawn card's polarity (`.sea-stage--gravity` / `--levity`, see drawn card's polarity (`.sea-stage--gravity` / `--levity`, see
`_card-deck.scss`), so the same back-art reads distinctly for the two `_card-deck.scss`), so the same back-art reads distinctly for the two
stacks (was previously gated to non-polarized decks, where it never stacks. `sea_back_image_url` is set by each caller from the RIGHT
rendered for the room's Gravity/Levity draw). The FLIP btn renders deck: the gameroom uses the SEAT's contributed deck (NOT
unconditionally; sea.js's handler no-ops when no back-img sibling request.user.equipped_deck, which select_role nulls out → the back
exists (text decks). Multi-user gameroom limitation: the src is the silently never rendered + FLIP no-op'd); my_sea uses the user's
VIEWER's deck-back — correct for the sea (each gamer draws their own), equipped deck. FLIP btn renders unconditionally; sea.js's handler
parked for the general multi-user case. no-ops when no back-img sibling exists (text decks).
{% endcomment %} {% endcomment %}
{% if request.user.is_authenticated and request.user.equipped_deck.has_card_images and request.user.equipped_deck.back_image_url %} {% if sea_back_image_url %}
<img class="sig-stage-card-back-img" alt="" <img class="sig-stage-card-back-img" alt="" src="{{ sea_back_image_url }}">
src="{{ request.user.equipped_deck.back_image_url }}">
{% endif %} {% endif %}
<button class="btn btn-reveal sea-stage-flip-btn" type="button" aria-label="Flip card">FLIP</button> <button class="btn btn-reveal sea-stage-flip-btn" type="button" aria-label="Flip card">FLIP</button>
</div> </div>