From e78ba730e3d94536fa08513c20997df9699ac08e Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Sun, 31 May 2026 23:21:58 -0400 Subject: [PATCH] =?UTF-8?q?room=20gate-view:=20reuse=20the=20gatekeeper=20?= =?UTF-8?q?token-slot=20modal=20=E2=80=94=20CONT=20GAME=20=E2=86=92=20hex?= =?UTF-8?q?=20when=20satisfied=20/=20rails-renew=20when=20lapsed=20?= =?UTF-8?q?=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redesign of the room gate-view per user-spec 2026-05-31: drop the custom seat-circle + countdown; render the EXACT gatekeeper modal instead (title panel + animated status-dots + token-slot rails + roles panel). - roles-panel .btn-primary is CONT GAME (→ table hex, same target as the gear NVM) while the viewer's seat cost is current; absent once it lapses, reappears after renewal re-satisfies the cost - .gate-status-text: " Token(s) Deposited" (literal "(s)" + the shared . . . . dots loop) when satisfied; "Please Deposit Token" when not. = the room's deposited (FILLED) slot count - token slot: .claimed (static rails) when current; .active rails that POST to renew_token when lapsed - seat circle + time-remaining removed — the hex's own .fa-chair carries seat status & user/seat tooltips land next sprint - room_gate view trimmed to {room, cost_current, deposited_count, page_class} - tests: RoomGateViewTest reworked (9) — CONT GAME→hex + deposited-count status + no renew-form when current; "Please Deposit Token" + renew rails + no CONT GAME when lapsed; NVM→hex; page-room; no seat/countdown markup. 510 epic tests green Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) --- src/apps/epic/tests/integrated/test_views.py | 47 +++++--- src/apps/epic/views.py | 20 ++-- src/templates/apps/gameboard/room_gate.html | 117 ++++++++++--------- 3 files changed, 102 insertions(+), 82 deletions(-) diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index a799658..96c3ae1 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -2633,26 +2633,46 @@ class RoomGateViewTest(TestCase): self.client.force_login(self.owner) self.url = reverse("epic:room_gate", args=[self.room.id]) + def _lapse(self): + # Backdate the seat into the renewal-grace window (cost lapsed, seat + # still FILLED) so the gate-view renders its renew state. + self.slot.filled_at = timezone.now() - timedelta(days=8) + self.slot.save() + def test_renders_200_even_when_table_status_set(self): # gatekeeper would 302 to the room; the gate-view must render mid-game. response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - def test_shows_viewer_own_seat_circle(self): + def test_cost_current_shows_cont_game_to_hex(self): response = self.client.get(self.url) - self.assertContains(response, 'data-slot="1"') - self.assertContains(response, "PC") # slot 1 role label + self.assertContains(response, "id_room_cont_game_btn") + self.assertContains(response, "CONT") + self.assertContains(response, reverse("epic:room", args=[self.room.id])) - def test_shows_time_remaining_data_attr(self): + def test_cost_current_status_shows_deposited_count(self): + # One filled slot (the owner's) → "1 Token(s) Deposited" (literal "(s)"). response = self.client.get(self.url) - self.assertContains(response, "data-cost-until=") - self.assertContains(response, "id_room_gate_remaining") + self.assertContains(response, "1 Token(s) Deposited") - def test_renew_button_posts_to_renew_endpoint(self): + def test_cost_current_has_no_renew_form(self): + # Rails are static (claimed) while the cost is current — renewing is a + # lapsed-state affordance only. response = self.client.get(self.url) + self.assertNotContains( + response, reverse("epic:renew_token", args=[self.room.id])) + + def test_cost_lapsed_shows_please_deposit_and_renew_rails(self): + self._lapse() + response = self.client.get(self.url) + self.assertContains(response, "Please Deposit Token") self.assertContains( response, reverse("epic:renew_token", args=[self.room.id])) - self.assertContains(response, "RENEW") + + def test_cost_lapsed_hides_cont_game(self): + self._lapse() + response = self.client.get(self.url) + self.assertNotContains(response, "id_room_cont_game_btn") def test_nvm_returns_to_room_hex(self): response = self.client.get(self.url) @@ -2663,12 +2683,13 @@ class RoomGateViewTest(TestCase): response = self.client.get(self.url) self.assertIn("page-room", response.context["page_class"]) - def test_no_seat_viewer_still_renders(self): - stranger = User.objects.create(email="stranger@test.io") - self.client.force_login(stranger) + def test_no_seat_circle_or_countdown_rendered(self): + # Seat circle + countdown removed (user-spec) — the hex .fa-chair + the + # next-sprint user/seat tooltips carry that info. response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, "id_room_renew_btn") + self.assertNotContains(response, "room-gate-seat") + self.assertNotContains(response, "data-cost-until") + self.assertNotContains(response, "id_room_gate_remaining") class RoomRenewTokenTest(TestCase): diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index 9028505..f05a94a 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -464,24 +464,22 @@ def room_view(request, room_id): def room_gate(request, room_id): """Room renewal gate-view — reachable mid-game (unlike `gatekeeper`, which redirects to the table once `table_status` is set). GATE VIEW - (navbar + center supersession) routes here. Shows the viewer's own - seat/position circle, their token time-remaining, and a RENEW - affordance; the gear-menu NVM returns to the table hex, not /gameboard/. - Mirrors the my-sea gate-view (`my_sea_gate`) for the 3rd-person table.""" + (navbar + center supersession) routes here. Reuses the gatekeeper's + token-slot modal: when the viewer's seat cost is current the roles + panel shows CONT GAME (→ table hex, same target as the gear NVM) and + the status reads " Token(s) Deposited"; when the cost has lapsed the + rails go active to RENEW and the status reads "Please Deposit Token" + (no CONT GAME until the cost is satisfied again). The seat circle + + time-remaining live on the table hex / next-sprint user-seat tooltips, + so they're intentionally absent here (user-spec 2026-05-31).""" room = Room.objects.get(id=room_id) user_slot = room.gate_slots.filter( gamer=request.user, status=GateSlot.FILLED ).first() return render(request, "apps/gameboard/room_gate.html", { "room": room, - "user_filled_slot": user_slot, "cost_current": user_slot.cost_current if user_slot else True, - "cost_current_until": user_slot.cost_current_until if user_slot else None, - "grace_expires_at": user_slot.grace_expires_at if user_slot else None, - "in_renewal_grace": user_slot.in_renewal_grace if user_slot else False, - "slot_role_label": ( - SLOT_ROLE_LABELS.get(user_slot.slot_number, "") if user_slot else "" - ), + "deposited_count": room.gate_slots.filter(status=GateSlot.FILLED).count(), "page_class": "page-gameboard page-room page-room-gate", }) diff --git a/src/templates/apps/gameboard/room_gate.html b/src/templates/apps/gameboard/room_gate.html index e5bf5c4..bd82cc8 100644 --- a/src/templates/apps/gameboard/room_gate.html +++ b/src/templates/apps/gameboard/room_gate.html @@ -6,14 +6,18 @@ {% block header_text %}GameGate{% endblock header_text %} {% block content %} -{# Room renewal gate-view (sprint 2026-05-31) — the 3rd-person mirror of #} -{# `my_sea_gate.html`. Reachable mid-game: unlike the gatekeeper (which #} -{# redirects to the table once table_status is set), GATE VIEW routes here #} -{# at any time so a seated gamer can check their token TIME REMAINING or #} -{# RENEW. Reuses the room gatekeeper's `.gate-overlay` / `.gate-modal` #} -{# chrome (hand-rolled, not `{% include _gatekeeper %}` — the inner content #} -{# differs: one seat circle + countdown + RENEW, no rails/PICK ROLES). The #} -{# gear-menu NVM returns to the table hex (passed `nvm_url`), not /gameboard.#} +{# Room renewal gate-view (sprint 2026-05-31) — reuses the EXACT gatekeeper #} +{# token-slot modal (`_gatekeeper.html` chrome: title panel + status-dots + #} +{# token-slot rails + roles panel), reachable mid-game. Unlike the #} +{# gatekeeper (which redirects to the table once table_status is set), GATE #} +{# VIEW routes here at any time so a seated gamer can renew. #} +{# • cost current → roles-panel `.btn-primary` is CONT GAME (→ table hex, #} +{# same target as the gear NVM); status " Token(s) Deposited . . . .".#} +{# • cost lapsed → no CONT GAME; the rails go active to RENEW; status #} +{# "Please Deposit Token . . . .". Renewing re-satisfies the cost and #} +{# the CONT GAME btn reappears. #} +{# No seat circle / countdown here — the table hex's own `.fa-chair` shows #} +{# seat status, and user/seat tooltips land next sprint (user-spec). #}
@@ -24,40 +28,50 @@

{{ room.name }}

- {% if not user_filled_slot %}No Seat{% elif cost_current %}Token Current{% elif in_renewal_grace %}Renewal Due{% else %}Grace Expired{% endif %} + {% if cost_current %}{{ deposited_count }} Token(s) Deposited{% else %}Please Deposit Token{% endif %} +
- {% if user_filled_slot %} - {# The viewer's own seat + position circle — held while #} - {# they hold the slot (through the renewal-grace window).#} -
- - {{ slot_role_label }} - + {# Cost current → claimed (static rails, token in the slot). #} + {# Cost lapsed → active rails that POST to renew_token. #} +
+ {% if not cost_current %} +
+ {% csrf_token %} + +
+ {% else %} +
+ + +
+ {% endif %} +
+
1
+ INSERT TOKEN TO PLAY + PUSH TO RETURN +
- {# Live time-remaining — ticks to cost_current_until while #} - {# current, then to grace_expires_at once in renewal grace. #} -

- {% else %} -

You hold no seat in this room.

- {% endif %}
- {% if user_filled_slot %} -
- {% csrf_token %} - -
+ {% if cost_current %} + {# CONT GAME — same destination as the gear NVM (the table #} + {# hex). Non-destructive nav, so no confirm guard. Only #} + {# rendered while the token cost is satisfied. #} + {% endif %}
@@ -77,33 +91,20 @@ {% endblock scripts %}