From bb44aa326af732651977c5898884ff7af9730296 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Wed, 20 May 2026 14:53:05 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20AUTO-DRAWn=20my-sea=20cards=20are=20now?= =?UTF-8?q?=20clickable=20to=20re-open=20the=20stage=20modal=20=E2=80=94?= =?UTF-8?q?=20`SeaDeal.register(card,=20posSelector,=20isLevity)`=20public?= =?UTF-8?q?=20method=20populates=20`=5FseaHand`=20+=20delegates=20to=20Sea?= =?UTF-8?q?Deal's=20internal=20`=5FfillSlot`=20so=20the=20overlay=20click?= =?UTF-8?q?=20handler=20can=20resolve=20`=5FseaHand[pos]`=20for=20auto-dra?= =?UTF-8?q?wn=20slots=20(previously=20short-circuited=20=E2=86=92=20silent?= =?UTF-8?q?=20no-op).=20AUTO=20DRAW=20in=20my=5Fsea.html=20now=20calls=20r?= =?UTF-8?q?egister=20instead=20of=20the=20inline=20`=5FfillSlot`=20shim=20?= =?UTF-8?q?=E2=80=94=20also=20fixes=20a=20`dataset.posKey`=20inconsistency?= =?UTF-8?q?=20(inline=20stored=20raw=20"cover",=20SeaDeal=20stores=20".sea?= =?UTF-8?q?-pos-cover";=20click=20handler=20reads=20SeaDeal's=20form).=20U?= =?UTF-8?q?ser-reported=202026-05-21.=20TDD=20=E2=80=94=20new=20FT=20`test?= =?UTF-8?q?=5Fauto=5Fdrawn=5Fslots=5Fcan=5Freopen=5Fstage=5Fmodal=5Fon=5Fc?= =?UTF-8?q?lick`=20pins=20the=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- src/apps/epic/static/apps/epic/sea.js | 14 ++++++ src/functional_tests/test_game_my_sea.py | 62 ++++++++++++++++++++++++ src/templates/apps/gameboard/my_sea.html | 15 +++++- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/apps/epic/static/apps/epic/sea.js b/src/apps/epic/static/apps/epic/sea.js index 4921375..732d47a 100644 --- a/src/apps/epic/static/apps/epic/sea.js +++ b/src/apps/epic/static/apps/epic/sea.js @@ -112,6 +112,19 @@ var SeaDeal = (function () { _showStage(isLevity); } + // Like `openStage` but DOESN'T show the stage modal — used by AUTO + // DRAW (my_sea.html) to place cards quietly while keeping them + // clickable later. The overlay click handler reads `_seaHand[pos]`; + // without an entry here, click silently no-ops (the user-reported + // bug fixed 2026-05-21). Routing slot fill thru SeaDeal's internal + // `_fillSlot` (instead of the inline shim) also ensures + // `dataset.posKey` stays consistent w. the manual-FLIP path + // (selector form like ".sea-pos-cover", not raw "cover"). + function register(card, posSelector, isLevity) { + _seaHand[posSelector] = { card: card, isLevity: isLevity }; + _fillSlot(posSelector, card, isLevity); + } + // ── Init ────────────────────────────────────────────────────────────────── function init() { @@ -239,6 +252,7 @@ var SeaDeal = (function () { return { openStage: openStage, + register: register, resetHand: resetHand, reinit: init, // call after overlay is injected into the DOM _testInit: function () { diff --git a/src/functional_tests/test_game_my_sea.py b/src/functional_tests/test_game_my_sea.py index fbca23b..beebc3f 100644 --- a/src/functional_tests/test_game_my_sea.py +++ b/src/functional_tests/test_game_my_sea.py @@ -15,6 +15,14 @@ from apps.epic.models import personal_sig_cards from apps.lyric.models import User +def _count_filled_slots(picker): + """Count `.sea-card-slot--filled` elements inside a picker container. + Module-level so multiple test classes can use it without inheriting.""" + return len( + picker.find_elements(By.CSS_SELECTOR, ".sea-card-slot--filled") + ) + + def _seed_gameboard_applets(): """My Sea + the rest of the gameboard applets so /gameboard/ renders without missing-applet errors during the applet-side assertions. @@ -840,6 +848,60 @@ class MySeaCardDrawTest(FunctionalTest): ) self.assertIn("GATE", action_btn.text.upper()) + # ── Test 5b — AUTO DRAW slot click reopens preview modal ──────────────── + + def test_auto_drawn_slots_can_reopen_stage_modal_on_click(self): + """Iter-6c bug-fix (user-reported 2026-05-21): cards placed by + AUTO DRAW were silently un-clickable — the inline `_fillSlot` + bypassed `SeaDeal.openStage` so SeaDeal's `_seaHand` dict was + never populated for those slots, and the overlay click handler + short-circuits on `if (!_seaHand[pos]) return;` → no modal. + + Reproduce: draw one card manually (sets `_seaHand[lay]`), AUTO + DRAW the rest (sets `_seaHand[lay]` still, but NOT cover/crown + if the bug exists), then click an auto-drawn slot — should + re-open the stage modal w. that card's preview. + + Fix landed: `SeaDeal.register(card, posSelector, isLevity)` + public method populates `_seaHand` + delegates to SeaDeal's + internal `_fillSlot` (so `dataset.posKey` stays consistent w. + the manual-FLIP path). AUTO DRAW now calls register, not the + inline shim.""" + picker = self._enter_picker_phase() + # Manual draw: lay (first position in SAO order). + self._draw_one(picker, "levity") + self.assertEqual(_count_filled_slots(picker), 1) + + # AUTO DRAW the remaining 2 (cover + crown). + action_btn = picker.find_element(By.CSS_SELECTOR, "#id_sea_action_btn") + action_btn.click() + confirm = self.wait_for( + lambda: self.browser.find_element( + By.CSS_SELECTOR, "#id_guard_portal.active .guard-yes" + ) + ) + confirm.click() + # Wait for hand-complete transition (action btn → GATE VIEW). + self.wait_for( + lambda: self.assertEqual(action_btn.get_attribute("data-state"), "gate-view") + ) + # All 3 slots filled. + self.wait_for(lambda: self.assertEqual(_count_filled_slots(picker), 3)) + + # Click an AUTO-drawn slot (cover — second in SAO draw order, so + # placed by AUTO DRAW). Two-tap pattern: first click focuses, + # second click opens the modal (per SeaDeal's overlay handler). + cover_slot = picker.find_element( + By.CSS_SELECTOR, ".sea-pos-cover .sea-card-slot--filled" + ) + cover_slot.click() + cover_slot.click() + # Stage modal must open w. the card preview. + stage = self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_sea_stage") + ) + self.wait_for(lambda: self.assertTrue(stage.is_displayed())) + # ── Test 6 ─────────────────────────────────────────────────────────────── def test_del_btn_is_disabled_until_hand_complete(self): diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index 8cf4876..340651a 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -591,7 +591,20 @@ ); if (stack) _showOk(stack); setTimeout(function () { - _fillSlot(e.posName, e.card, e.isLevity); + // Route thru SeaDeal.register (not the inline + // `_fillSlot`) so SeaDeal's `_seaHand` dict + // gets the entry — the overlay click handler + // reads from there, so without registration + // the auto-drawn slots are silently un- + // clickable (user-reported bug, 2026-05-21). + // Fallback to the inline shim only if sea.js + // hasn't loaded yet (defensive — script load + // order makes this unlikely). + if (window.SeaDeal && window.SeaDeal.register) { + SeaDeal.register(e.card, '.sea-pos-' + e.posName, e.isLevity); + } else { + _fillSlot(e.posName, e.card, e.isLevity); + } cross.querySelector('.sea-pos-' + e.posName + ' .sea-card-slot') .classList.add('sea-card-slot--visible'); _filled++;