My Sea iter 4b: MySeaDraw persistence + LOCK HAND POST + DEL guard + Brief banner; rewrite obsolete spread-switch FT; fix bud-panel CI race on gatekeeper FT — Sprint 5 iter 4b of My Sea roadmap — TDD

Iter 4b lands server persistence of the iter-4a client-side hand. New MySeaDraw model (FK user, spread, hand JSONField in draw order, sig snapshot, created_at) w. 1/24h quota window; new endpoints /gameboard/my-sea/lock (POST, 409 on quota-active, 400 on partial hand) + /gameboard/my-sea/delete (POST, idempotent). LOCK HAND now collects the in-progress hand from DOM, POSTs, and on success un-hides a Brief banner inline (no page reload — preserves iter-4a FT picker refs). DEL post-LOCK opens #id_my_sea_del_portal w. uniform 'Are you sure?' copy; CONFIRM POSTs delete + reloads to landing. Brief banner carries the next-free-draw timestamp + a NVM dismiss. Saved-draw render bypasses the sign-gate via _resolve_sig (sig snapshot on the draw is used even if user.significator was cleared later) + bypasses the landing phase (the saved hand IS what the user came to see). Per-position slot rendering extracted to _my_sea_slot.html. DRY follow-up: card_dict() extracted to apps.epic.utils — gameroom sea_deck + my-sea _my_sea_deck_data now share one source of truth (prevents drift like the iter-4a-follow-up Major Arcana fix from recurring).

Pipeline #316 fixes bundled: (a) functional_tests.test_game_my_sea.MySeaCardDrawTest.test_switching_spread_resets_in_progress_hand was obsoleted by the iter-4a follow-up's spread-lock-after-first-draw — the test premise (mid-draw spread switching resets hand) no longer matches behavior (switching is blocked outright). Rewrote as test_first_draw_locks_spread_combobox, which pins .sea-select--locked after first draw + verifies DEL releases it. (b) functional_tests.test_game_room_gatekeeper.GatekeeperTest.test_second_gamer_drops_token_into_open_slot failed in CI on ElementNotInteractableException when clicking #id_bud_panel .btn.btn-confirm — the bud panel's scaleX(0)→scaleX(1) 0.2s CSS transition wasn't settled by click-time, so Selenium read scroll-into-view against a near-zero-width target. Added a wait_for on getBoundingClientRect().width > 100 so the click waits for the animation to finish. Local passes consistently; CI was 1+ frame slower than the implicit 'find element' wait.

Tests: 1085 IT/UT green in 55s; 35 my_sea FTs green in 5m; new ITs in MySeaDrawModelTest (8), MySeaLockHandViewTest (7), MySeaDeleteDrawViewTest (5), MySeaViewWithSavedDrawTest (9); new FTs in MySeaLockHandTest (5).

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-19 23:54:00 -04:00
parent 31ed2bda0e
commit b76d3c5dff
13 changed files with 1147 additions and 145 deletions

View File

@@ -24,6 +24,48 @@ def stack_reversal_probability(user=None, room=None):
return STACK_REVERSAL_PROBABILITY
def card_dict(card, reversal_prob=STACK_REVERSAL_PROBABILITY):
"""Canonical serialization of a TarotCard → JSON payload.
Single source of truth for the gameroom `sea_deck` endpoint AND the
`_my_sea_deck_data` helper on /gameboard/my-sea/. Iter 4b of the My
Sea roadmap extracted this from two near-identical copies that had
drifted on the Major Arcana polarity-split keys (cards 19-21 + 48-49)
— keeping the contract in one place prevents the same drift recurring.
The `reversed` flag is rolled fresh each call via `random.random()`
against `reversal_prob`. Callers that need a deterministic flag (e.g.
re-rendering a previously-saved hand) should NOT use this helper —
look up the card and serialize manually with the persisted flag.
"""
import random as _random
return {
'id': card.id,
'name': card.name,
'arcana': card.arcana,
'suit': card.suit,
'number': card.number,
'corner_rank': card.corner_rank,
'suit_icon': card.suit_icon,
'name_group': card.name_group,
'name_title': card.name_title,
'levity_qualifier': card.levity_qualifier,
'gravity_qualifier': card.gravity_qualifier,
'reversal_qualifier': card.reversal_qualifier,
# Polarity-split full-title overrides (cards 48-49 + trumps 19-21)
'levity_emanation': card.levity_emanation,
'gravity_emanation': card.gravity_emanation,
'levity_reversal': card.levity_reversal,
'gravity_reversal': card.gravity_reversal,
'italic_word': card.italic_word,
'keywords_upright': card.keywords_upright,
'keywords_reversed': card.keywords_reversed,
'energies': card.energies,
'operations': card.operations,
'reversed': _random.random() < reversal_prob,
}
# Element key → in-game capacitor name (mirrors ELEMENT_INFO in sky-wheel.js).
# Used by the SKY_SAVED provenance event to render prose like