From 5cade51d03824be1e15779512c3cf3f41c4d7380 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 26 May 2026 22:39:26 -0400 Subject: [PATCH] =?UTF-8?q?my-sea=20gear=20NVM:=20gatekeeper=20+=20picker?= =?UTF-8?q?=20nav-back=20to=20table=20hex=20instead=20of=20ejecting=20to?= =?UTF-8?q?=20gameboard=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-spec'd 2026-05-26: the gear menu's NVM was always nav-backing to /gameboard/ from every my-sea state, which ejects the user one step further than they want when bailing on the gatekeeper or the spread/crucifix picker. Both should nav-back to the my-sea TABLE HEX (the landing) — one step back, not all the way out. ## templates/apps/gameboard/_partials/_my_sea_gear.html Now takes an optional `nvm_url` context variable; falls back to `{% url 'gameboard' %}` when unset. The onclick handler reads `{{ nvm_url }}` so callers can override per-page. ## templates/apps/gameboard/my_sea.html ``` {% if show_picker %}{% url 'my_sea' as nvm_url %}{% endif %} {% include "apps/gameboard/_partials/_my_sea_gear.html" %} ``` Picker phase → nvm_url = my_sea landing (table hex). Sign-gate + landing phases fall through to the default (gameboard) — landing's NVM = "back out of my-sea entirely", which is the existing behaviour. ## templates/apps/gameboard/my_sea_gate.html ``` {% url 'my_sea' as nvm_url %} {% include "apps/gameboard/_partials/_my_sea_gear.html" %} ``` Gatekeeper unconditionally nav-backs to the my-sea landing. ## Tests `apps/gameboard/tests/integrated/test_views.py`: - `MySeaViewTest.test_gear_nvm_navs_to_gameboard_on_landing_phase` — landing NVM still goes to /gameboard/ (regression guard). - `MySeaViewTest.test_gear_nvm_navs_to_my_sea_landing_on_picker_phase` — picker NVM goes to /gameboard/my-sea/ (seeds a non-empty hand to trigger show_picker per views.py:277's `hand_non_empty or (phase_param and show_paid_draw)` logic). - `MySeaGateViewTest.test_gear_nvm_navs_to_my_sea_landing_not_gameboard` — gatekeeper NVM goes to /gameboard/my-sea/ (and explicitly NOT /gameboard/). ## Verification All 1364 IT+UT green. No FTs assert the gear-menu NVM target URL (per pre-change grep) so no test fixes required there. Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Sonnet 4.6 --- .../gameboard/tests/integrated/test_views.py | 34 +++++++++++++++++++ .../gameboard/_partials/_my_sea_gear.html | 18 +++++----- src/templates/apps/gameboard/my_sea.html | 7 ++-- src/templates/apps/gameboard/my_sea_gate.html | 4 +++ 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/apps/gameboard/tests/integrated/test_views.py b/src/apps/gameboard/tests/integrated/test_views.py index d7f94f3..5a47aa0 100644 --- a/src/apps/gameboard/tests/integrated/test_views.py +++ b/src/apps/gameboard/tests/integrated/test_views.py @@ -1037,6 +1037,31 @@ class MySeaViewTest(TestCase): self.assertIn('id="id_bud_btn"', body) self.assertIn('id="id_burger_btn"', body) + def test_gear_nvm_navs_to_gameboard_on_landing_phase(self): + """Landing-phase NVM = "back out of my-sea entirely" → /gameboard/.""" + response = self.client.get(reverse("my_sea")) + self.assertIn("location.href='/gameboard/'", response.content.decode()) + + def test_gear_nvm_navs_to_my_sea_landing_on_picker_phase(self): + """Picker-phase NVM = "back out of the spread to the table hex" → + /gameboard/my-sea/ (the landing). User-spec'd 2026-05-26 so the + spread/crucifix exit doesn't eject all the way to /gameboard/. + Picker phase triggers on a non-empty hand (see views.py:277).""" + from apps.epic.models import personal_sig_cards, TarotCard + from apps.gameboard.models import MySeaDraw + sig = personal_sig_cards(self.user)[0] + self.user.significator = sig + self.user.save(update_fields=["significator"]) + card = TarotCard.objects.exclude(pk=sig.pk).first() + MySeaDraw.objects.create( + user=self.user, spread="situation-action-outcome", + significator_id=sig.id, + hand=[{"position": "lay", "card_id": card.id, + "reversed": False, "polarity": "gravity"}], + ) + response = self.client.get(reverse("my_sea")) + self.assertIn("location.href='/gameboard/my-sea/'", response.content.decode()) + def test_sea_stage_stat_block_renders_rank_suit_chip_per_face(self): """Sprint A.7.5 — `_sea_stage.html` modal scaffold (included from my_sea-picker-phase + the gameroom sea overlay) carries the new @@ -2189,6 +2214,15 @@ class MySeaGateViewTest(TestCase): self.assertContains(response, "id_my_sea_paid_draw_btn") self.assertContains(response, reverse("my_sea_refund_token")) + def test_gear_nvm_navs_to_my_sea_landing_not_gameboard(self): + """Gatekeeper NVM = "back out of the gate to the table hex" → + /gameboard/my-sea/ (the landing), NOT /gameboard/. User-spec'd + 2026-05-26 so the gatekeeper exit lands one step back.""" + response = self.client.get(reverse("my_sea_gate")) + body = response.content.decode() + self.assertIn("location.href='/gameboard/my-sea/'", body) + self.assertNotIn("location.href='/gameboard/'", body) + def test_gate_view_renders_burger_btn_and_fan(self): response = self.client.get(reverse("my_sea_gate")) self.assertContains(response, 'id="id_burger_btn"') diff --git a/src/templates/apps/gameboard/_partials/_my_sea_gear.html b/src/templates/apps/gameboard/_partials/_my_sea_gear.html index 07c46de..5dd59cf 100644 --- a/src/templates/apps/gameboard/_partials/_my_sea_gear.html +++ b/src/templates/apps/gameboard/_partials/_my_sea_gear.html @@ -1,13 +1,15 @@ {# Sprint 6 iter 6c — gear-btn on /gameboard/my-sea/ + the gatekeeper. #} {# NVM-only menu (no DEL, no BYE) — gear is the "back out without #} -{# committing" affordance; NVM nav-backs to /gameboard/ mirroring the #} -{# room's gear-menu convention. Rendered unconditionally (no active- #} -{# draw guard) so fresh users + post-DEL states still see it. #} -{# ` + {% include "apps/applets/_partials/_gear.html" with menu_id="id_my_sea_menu" %} diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index 0329402..fabb680 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -1062,8 +1062,11 @@ {% endif %} {% endif %} {# Sprint 6 iter 6c — gear-btn lives on every my-sea page state #} - {# (sign-gate / landing / picker). NVM-only menu mirrors the #} - {# gatekeeper's gear; "back out to /gameboard/" affordance. #} + {# (sign-gate / landing / picker). NVM-only menu. Picker NVM #} + {# nav-backs to the my-sea TABLE HEX (landing), letting the user #} + {# leave the spread without committing. Sign-gate + landing NVM #} + {# nav-backs to /gameboard/ (the partial's default fallback). #} + {% if show_picker %}{% url 'my_sea' as nvm_url %}{% endif %} {% include "apps/gameboard/_partials/_my_sea_gear.html" %} {# Bud + burger persist across every stage too — same affordance #} {# as on the my-sea gatekeeper, so the user can invite + reach #} diff --git a/src/templates/apps/gameboard/my_sea_gate.html b/src/templates/apps/gameboard/my_sea_gate.html index 920e26d..4074563 100644 --- a/src/templates/apps/gameboard/my_sea_gate.html +++ b/src/templates/apps/gameboard/my_sea_gate.html @@ -88,6 +88,10 @@ {# bud-btn lives at viewport fixed position (per `_bud.scss`) and #} {# the gear-btn sits atop `#id_kit_btn` in the bottom-right corner. #} {% include "apps/gameboard/_partials/_my_sea_bud_panel.html" %} + {# Gatekeeper NVM nav-backs to the my-sea TABLE HEX (landing) so the #} + {# user lands one step back from the gatekeeper rather than ejecting #} + {# all the way to /gameboard/. #} + {% url 'my_sea' as nvm_url %} {% include "apps/gameboard/_partials/_my_sea_gear.html" %} {% include "apps/gameboard/_partials/_burger.html" %}