My Sea iter 6a: gatekeeper page + INSERT/REFUND/PAID DRAW endpoints + MySeaDraw deposit fields + `_select_my_sea_token` / `debit_my_sea_token` helpers (CARTE blocked, COIN 24h cooldown not 7-day) + Sprint 6 FT skeleton — Sprint 5 iter 6a of My Sea roadmap — TDD
First of three Sprint 6 commits per [[sprint-my-sea-iter-6-plan]]. Replaces the iter-4c 404 stub at `/gameboard/my-sea/gate/` w. a real token-deposit-to-redraw UI. Iter 6b will wire the navbar GATE VIEW swap + landing PAID DRAW state + seat-1 persistence; iter 6c will land the bud-btn stub.
## Server
`MySeaDraw` gains two fields: `deposit_token_id` (int, nullable) + `deposit_reserved_at` (datetime, nullable). Migration 0002. The row plays triple duty now: hand storage + 24h quota tracker + deposit reservation slot.
`_select_my_sea_token(user)` mirrors `apps.epic.models.select_token` priority (PASS > COIN > FREE > TITHE) w. two adaptations:
- CARTE excluded outright (door-spell trinket, not valid for my-sea draws).
- COIN cooldown-respecting: filters out COINs w. `next_ready_at > now`. Standard `select_token` doesn't apply this filter — room logic unchanged.
`debit_my_sea_token(user, token)` is the my-sea variant of `apps.epic.models.debit_token`:
- CARTE → ValueError (defensive; caller validates upstream).
- COIN: `next_ready_at = now + 24h` (not 7-day room cycle) + unequip from kit if equipped.
- PASS: no consumption (auto-admit, unlimited redraws).
- FREE / TITHE: deleted.
`my_sea_gate` view replaces the 404 stub. Renders the gatekeeper template w. branching on `deposit_reserved` (token reserved on row vs not).
`my_sea_insert_token` POST: picks a token via `_select_my_sea_token` + sets `deposit_token_id + deposit_reserved_at`. Creates the row if missing (so a fresh user can deposit without first using their free draw). Idempotent w.r.t. an already-reserved deposit.
`my_sea_refund_token` POST: clears deposit fields. Token isn't consumed at INSERT (refund-aware design), so this is purely a row update — no inventory side effects.
`my_sea_paid_draw` POST: commits via `debit_my_sea_token` + resets row (hand=[], created_at=now, deposit fields cleared). Redirects to `/gameboard/my-sea/` for a fresh quota cycle.
## Template + UX
`apps/gameboard/my_sea_gate.html` (new) — per user spec 2026-05-20, the gatekeeper is a darkened-modal-over-`--duoUser` bg matching the room gatekeeper's chrome (`.gate-backdrop` + `.gate-overlay` + `.gate-modal`). No hex / chair-seats — those live on the my-sea picker page itself; the gatekeeper is a transient in-flight UI for token deposit.
Coin-slot rails (mirrors room's `.token-slot`):
- Pre-deposit: form-wrapped `.token-rails` button → POSTs to `my_sea_insert_token`. Coin-panel labels read INSERT TOKEN TO PLAY.
- Post-deposit: rails inert (no form); `.token-return-btn` form → POSTs to `my_sea_refund_token`. Coin-panel labels swap to PUSH TO RETURN.
- Post-deposit: PAID DRAW btn (`#id_my_sea_paid_draw_btn`, `.btn-primary`) → POSTs to `my_sea_paid_draw`. Mirrors the room's PICK ROLES btn shape.
SCSS minimal — page bg `rgba(--duoUser, 1)` on `.my-sea-page[data-phase="gate"]`; everything else reuses the room gatekeeper's existing rules.
## FT skeleton
Per user TDD directive (2026-05-20: "Also via TDD so if we run out we're adhering to FT-described behavior"), wrote the FULL Sprint 6 FT skeleton up front (covers iter 6a + 6b + 6c). Five new FT classes in `test_game_my_sea.py`:
- `MySeaGatekeeperPageTest` (5 tests) — iter 6a; pre-deposit / INSERT / REFUND / PAID DRAW paths.
- `MySeaLandingPaidDrawTest` (1 test) — iter 6b; landing renders PAID DRAW btn when deposit reserved (red until iter 6b lands).
- `MySeaNavbarGateViewTest` (1 test) — iter 6b; navbar GATE VIEW swap (red until iter 6b).
- `MySeaSeatOnePersistenceTest` (2 tests) — iter 6b; seat 1 banned for fresh user + empty-hand active draw (red until iter 6b).
- `MySeaBudBtnStubTest` (2 tests) — iter 6c; panel opens + OK shows coming-soon Brief (red until iter 6c).
## ITs (iter 6a — 22 new + 153 total green)
- `MySeaGateViewTest` (4) — view branching pre/post deposit.
- `MySeaInsertTokenViewTest` (4) — row creation, existing row, idempotency, GET=405.
- `MySeaRefundTokenViewTest` (3) — clears fields, no token consumption, idempotent.
- `MySeaPaidDrawViewTest` (6) — FREE consumed, COIN cooldown + unequip, PASS no-op, hand reset, created_at reset, redirect.
- `SelectMySeaTokenTest` (3) — CARTE excluded, COIN cooldown excluded, PASS priority for staff.
- `DebitMySeaTokenTest` (4) — CARTE ValueError, FREE/TITHE consumed, PASS preserved.
## Trap caught
Existing User `post_save` signal auto-creates COIN + FREE tokens (`apps.lyric.models:309`). Sprint 6 ITs that assert "user has only the token I seeded" must `self.user.tokens.all().delete()` after User.create. Without it, `_select_my_sea_token` returns the auto-COIN instead of None for the CARTE-excluded test. Worth a future feedback memory if it bites again.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:29:08 -04:00
|
|
|
{% extends "core/base.html" %}
|
|
|
|
|
{% load static %}
|
2026-05-20 02:31:51 -04:00
|
|
|
{% load lyric_extras %}
|
My Sea iter 6a: gatekeeper page + INSERT/REFUND/PAID DRAW endpoints + MySeaDraw deposit fields + `_select_my_sea_token` / `debit_my_sea_token` helpers (CARTE blocked, COIN 24h cooldown not 7-day) + Sprint 6 FT skeleton — Sprint 5 iter 6a of My Sea roadmap — TDD
First of three Sprint 6 commits per [[sprint-my-sea-iter-6-plan]]. Replaces the iter-4c 404 stub at `/gameboard/my-sea/gate/` w. a real token-deposit-to-redraw UI. Iter 6b will wire the navbar GATE VIEW swap + landing PAID DRAW state + seat-1 persistence; iter 6c will land the bud-btn stub.
## Server
`MySeaDraw` gains two fields: `deposit_token_id` (int, nullable) + `deposit_reserved_at` (datetime, nullable). Migration 0002. The row plays triple duty now: hand storage + 24h quota tracker + deposit reservation slot.
`_select_my_sea_token(user)` mirrors `apps.epic.models.select_token` priority (PASS > COIN > FREE > TITHE) w. two adaptations:
- CARTE excluded outright (door-spell trinket, not valid for my-sea draws).
- COIN cooldown-respecting: filters out COINs w. `next_ready_at > now`. Standard `select_token` doesn't apply this filter — room logic unchanged.
`debit_my_sea_token(user, token)` is the my-sea variant of `apps.epic.models.debit_token`:
- CARTE → ValueError (defensive; caller validates upstream).
- COIN: `next_ready_at = now + 24h` (not 7-day room cycle) + unequip from kit if equipped.
- PASS: no consumption (auto-admit, unlimited redraws).
- FREE / TITHE: deleted.
`my_sea_gate` view replaces the 404 stub. Renders the gatekeeper template w. branching on `deposit_reserved` (token reserved on row vs not).
`my_sea_insert_token` POST: picks a token via `_select_my_sea_token` + sets `deposit_token_id + deposit_reserved_at`. Creates the row if missing (so a fresh user can deposit without first using their free draw). Idempotent w.r.t. an already-reserved deposit.
`my_sea_refund_token` POST: clears deposit fields. Token isn't consumed at INSERT (refund-aware design), so this is purely a row update — no inventory side effects.
`my_sea_paid_draw` POST: commits via `debit_my_sea_token` + resets row (hand=[], created_at=now, deposit fields cleared). Redirects to `/gameboard/my-sea/` for a fresh quota cycle.
## Template + UX
`apps/gameboard/my_sea_gate.html` (new) — per user spec 2026-05-20, the gatekeeper is a darkened-modal-over-`--duoUser` bg matching the room gatekeeper's chrome (`.gate-backdrop` + `.gate-overlay` + `.gate-modal`). No hex / chair-seats — those live on the my-sea picker page itself; the gatekeeper is a transient in-flight UI for token deposit.
Coin-slot rails (mirrors room's `.token-slot`):
- Pre-deposit: form-wrapped `.token-rails` button → POSTs to `my_sea_insert_token`. Coin-panel labels read INSERT TOKEN TO PLAY.
- Post-deposit: rails inert (no form); `.token-return-btn` form → POSTs to `my_sea_refund_token`. Coin-panel labels swap to PUSH TO RETURN.
- Post-deposit: PAID DRAW btn (`#id_my_sea_paid_draw_btn`, `.btn-primary`) → POSTs to `my_sea_paid_draw`. Mirrors the room's PICK ROLES btn shape.
SCSS minimal — page bg `rgba(--duoUser, 1)` on `.my-sea-page[data-phase="gate"]`; everything else reuses the room gatekeeper's existing rules.
## FT skeleton
Per user TDD directive (2026-05-20: "Also via TDD so if we run out we're adhering to FT-described behavior"), wrote the FULL Sprint 6 FT skeleton up front (covers iter 6a + 6b + 6c). Five new FT classes in `test_game_my_sea.py`:
- `MySeaGatekeeperPageTest` (5 tests) — iter 6a; pre-deposit / INSERT / REFUND / PAID DRAW paths.
- `MySeaLandingPaidDrawTest` (1 test) — iter 6b; landing renders PAID DRAW btn when deposit reserved (red until iter 6b lands).
- `MySeaNavbarGateViewTest` (1 test) — iter 6b; navbar GATE VIEW swap (red until iter 6b).
- `MySeaSeatOnePersistenceTest` (2 tests) — iter 6b; seat 1 banned for fresh user + empty-hand active draw (red until iter 6b).
- `MySeaBudBtnStubTest` (2 tests) — iter 6c; panel opens + OK shows coming-soon Brief (red until iter 6c).
## ITs (iter 6a — 22 new + 153 total green)
- `MySeaGateViewTest` (4) — view branching pre/post deposit.
- `MySeaInsertTokenViewTest` (4) — row creation, existing row, idempotency, GET=405.
- `MySeaRefundTokenViewTest` (3) — clears fields, no token consumption, idempotent.
- `MySeaPaidDrawViewTest` (6) — FREE consumed, COIN cooldown + unequip, PASS no-op, hand reset, created_at reset, redirect.
- `SelectMySeaTokenTest` (3) — CARTE excluded, COIN cooldown excluded, PASS priority for staff.
- `DebitMySeaTokenTest` (4) — CARTE ValueError, FREE/TITHE consumed, PASS preserved.
## Trap caught
Existing User `post_save` signal auto-creates COIN + FREE tokens (`apps.lyric.models:309`). Sprint 6 ITs that assert "user has only the token I seeded" must `self.user.tokens.all().delete()` after User.create. Without it, `_select_my_sea_token` returns the auto-COIN instead of None for the CARTE-excluded test. Worth a future feedback memory if it bites again.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:29:08 -04:00
|
|
|
|
|
|
|
|
{% block title_text %}Game Sea Gate{% endblock title_text %}
|
|
|
|
|
{% block header_text %}<span>Game</span><span>Gate</span>{% endblock header_text %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
{# Sprint 6 iter 6a — solo my-sea gatekeeper. Token-deposit-to-redraw #}
|
|
|
|
|
{# within the 24h window after the user's daily free draw is spent. #}
|
|
|
|
|
{# Layout: `--duoUser` page bg + darkened Gaussian-glass modal centered #}
|
|
|
|
|
{# over it (mirrors the room gatekeeper's `.gate-overlay` + `.gate- #}
|
|
|
|
|
{# modal` chrome). No hex / chair-seats — the gatekeeper is a transient #}
|
|
|
|
|
{# in-flight UI; seats live on the my-sea picker page itself. #}
|
|
|
|
|
<div class="my-sea-page my-sea-gate-page"
|
|
|
|
|
data-phase="gate"
|
|
|
|
|
data-polarity="{% if significator_reversed %}gravity{% else %}levity{% endif %}">
|
|
|
|
|
|
|
|
|
|
<div id="id_gate_wrapper" class="my-sea-gate-wrapper">
|
|
|
|
|
<div class="gate-backdrop"></div>
|
|
|
|
|
<div class="gate-overlay my-sea-gate-overlay">
|
|
|
|
|
<div class="gate-modal my-sea-gate-modal" role="dialog" aria-label="My Sea Gatekeeper">
|
|
|
|
|
|
2026-05-20 02:31:51 -04:00
|
|
|
{# Title panel — mirrors room's `<h1>{{ room.name }}</h1>` #}
|
|
|
|
|
{# inside `.gate-title-panel`. For solo my-sea the page #}
|
|
|
|
|
{# title reads "@<handle>'s Sea" (rendered via the #}
|
|
|
|
|
{# `at_handle` filter so handle-less users fall back to #}
|
|
|
|
|
{# their email prefix). #}
|
|
|
|
|
<div class="gate-title-panel">
|
|
|
|
|
<header class="gate-header">
|
|
|
|
|
<h1>{{ user|at_handle }}'s Sea</h1>
|
|
|
|
|
</header>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# Top row — token slot (main panel) + PAID DRAW square #}
|
|
|
|
|
{# (roles panel). Both share `.gate-main-panel` / #}
|
|
|
|
|
{# `.gate-roles-panel` `--priUser` bg styling from #}
|
|
|
|
|
{# `_room.scss`. Layout mirrors the room gatekeeper. #}
|
|
|
|
|
<div class="gate-top-row">
|
|
|
|
|
<div class="gate-main-panel">
|
|
|
|
|
<div class="token-slot{% if not deposit_reserved %} active{% else %} pending{% endif %}">
|
|
|
|
|
{% if not deposit_reserved %}
|
|
|
|
|
<form method="POST" action="{% url 'my_sea_insert_token' %}" style="display:contents">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="token-rails" aria-label="Insert token to play">
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="token-rails">
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
<span class="rail"></span>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<div class="token-panel">
|
|
|
|
|
<div class="token-denomination">1</div>
|
|
|
|
|
<span class="token-insert-label">INSERT TOKEN TO PLAY</span>
|
|
|
|
|
<span class="token-return-label">PUSH TO RETURN</span>
|
|
|
|
|
</div>
|
|
|
|
|
{% if deposit_reserved %}
|
|
|
|
|
<form method="POST" action="{% url 'my_sea_refund_token' %}" style="display:contents">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit" class="token-return-btn" aria-label="Push to return"></button>
|
|
|
|
|
</form>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
My Sea iter 6a: gatekeeper page + INSERT/REFUND/PAID DRAW endpoints + MySeaDraw deposit fields + `_select_my_sea_token` / `debit_my_sea_token` helpers (CARTE blocked, COIN 24h cooldown not 7-day) + Sprint 6 FT skeleton — Sprint 5 iter 6a of My Sea roadmap — TDD
First of three Sprint 6 commits per [[sprint-my-sea-iter-6-plan]]. Replaces the iter-4c 404 stub at `/gameboard/my-sea/gate/` w. a real token-deposit-to-redraw UI. Iter 6b will wire the navbar GATE VIEW swap + landing PAID DRAW state + seat-1 persistence; iter 6c will land the bud-btn stub.
## Server
`MySeaDraw` gains two fields: `deposit_token_id` (int, nullable) + `deposit_reserved_at` (datetime, nullable). Migration 0002. The row plays triple duty now: hand storage + 24h quota tracker + deposit reservation slot.
`_select_my_sea_token(user)` mirrors `apps.epic.models.select_token` priority (PASS > COIN > FREE > TITHE) w. two adaptations:
- CARTE excluded outright (door-spell trinket, not valid for my-sea draws).
- COIN cooldown-respecting: filters out COINs w. `next_ready_at > now`. Standard `select_token` doesn't apply this filter — room logic unchanged.
`debit_my_sea_token(user, token)` is the my-sea variant of `apps.epic.models.debit_token`:
- CARTE → ValueError (defensive; caller validates upstream).
- COIN: `next_ready_at = now + 24h` (not 7-day room cycle) + unequip from kit if equipped.
- PASS: no consumption (auto-admit, unlimited redraws).
- FREE / TITHE: deleted.
`my_sea_gate` view replaces the 404 stub. Renders the gatekeeper template w. branching on `deposit_reserved` (token reserved on row vs not).
`my_sea_insert_token` POST: picks a token via `_select_my_sea_token` + sets `deposit_token_id + deposit_reserved_at`. Creates the row if missing (so a fresh user can deposit without first using their free draw). Idempotent w.r.t. an already-reserved deposit.
`my_sea_refund_token` POST: clears deposit fields. Token isn't consumed at INSERT (refund-aware design), so this is purely a row update — no inventory side effects.
`my_sea_paid_draw` POST: commits via `debit_my_sea_token` + resets row (hand=[], created_at=now, deposit fields cleared). Redirects to `/gameboard/my-sea/` for a fresh quota cycle.
## Template + UX
`apps/gameboard/my_sea_gate.html` (new) — per user spec 2026-05-20, the gatekeeper is a darkened-modal-over-`--duoUser` bg matching the room gatekeeper's chrome (`.gate-backdrop` + `.gate-overlay` + `.gate-modal`). No hex / chair-seats — those live on the my-sea picker page itself; the gatekeeper is a transient in-flight UI for token deposit.
Coin-slot rails (mirrors room's `.token-slot`):
- Pre-deposit: form-wrapped `.token-rails` button → POSTs to `my_sea_insert_token`. Coin-panel labels read INSERT TOKEN TO PLAY.
- Post-deposit: rails inert (no form); `.token-return-btn` form → POSTs to `my_sea_refund_token`. Coin-panel labels swap to PUSH TO RETURN.
- Post-deposit: PAID DRAW btn (`#id_my_sea_paid_draw_btn`, `.btn-primary`) → POSTs to `my_sea_paid_draw`. Mirrors the room's PICK ROLES btn shape.
SCSS minimal — page bg `rgba(--duoUser, 1)` on `.my-sea-page[data-phase="gate"]`; everything else reuses the room gatekeeper's existing rules.
## FT skeleton
Per user TDD directive (2026-05-20: "Also via TDD so if we run out we're adhering to FT-described behavior"), wrote the FULL Sprint 6 FT skeleton up front (covers iter 6a + 6b + 6c). Five new FT classes in `test_game_my_sea.py`:
- `MySeaGatekeeperPageTest` (5 tests) — iter 6a; pre-deposit / INSERT / REFUND / PAID DRAW paths.
- `MySeaLandingPaidDrawTest` (1 test) — iter 6b; landing renders PAID DRAW btn when deposit reserved (red until iter 6b lands).
- `MySeaNavbarGateViewTest` (1 test) — iter 6b; navbar GATE VIEW swap (red until iter 6b).
- `MySeaSeatOnePersistenceTest` (2 tests) — iter 6b; seat 1 banned for fresh user + empty-hand active draw (red until iter 6b).
- `MySeaBudBtnStubTest` (2 tests) — iter 6c; panel opens + OK shows coming-soon Brief (red until iter 6c).
## ITs (iter 6a — 22 new + 153 total green)
- `MySeaGateViewTest` (4) — view branching pre/post deposit.
- `MySeaInsertTokenViewTest` (4) — row creation, existing row, idempotency, GET=405.
- `MySeaRefundTokenViewTest` (3) — clears fields, no token consumption, idempotent.
- `MySeaPaidDrawViewTest` (6) — FREE consumed, COIN cooldown + unequip, PASS no-op, hand reset, created_at reset, redirect.
- `SelectMySeaTokenTest` (3) — CARTE excluded, COIN cooldown excluded, PASS priority for staff.
- `DebitMySeaTokenTest` (4) — CARTE ValueError, FREE/TITHE consumed, PASS preserved.
## Trap caught
Existing User `post_save` signal auto-creates COIN + FREE tokens (`apps.lyric.models:309`). Sprint 6 ITs that assert "user has only the token I seeded" must `self.user.tokens.all().delete()` after User.create. Without it, `_select_my_sea_token` returns the auto-COIN instead of None for the CARTE-excluded test. Worth a future feedback memory if it bites again.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:29:08 -04:00
|
|
|
</div>
|
2026-05-20 02:31:51 -04:00
|
|
|
|
|
|
|
|
<div class="gate-roles-panel">
|
|
|
|
|
{% if deposit_reserved %}
|
|
|
|
|
<form method="POST" action="{% url 'my_sea_paid_draw' %}" style="display:contents">
|
|
|
|
|
{% csrf_token %}
|
|
|
|
|
<button type="submit"
|
|
|
|
|
id="id_my_sea_paid_draw_btn"
|
|
|
|
|
class="launch-game-btn btn btn-primary">PAID<br>DRAW</button>
|
|
|
|
|
</form>
|
|
|
|
|
{% endif %}
|
My Sea iter 6a: gatekeeper page + INSERT/REFUND/PAID DRAW endpoints + MySeaDraw deposit fields + `_select_my_sea_token` / `debit_my_sea_token` helpers (CARTE blocked, COIN 24h cooldown not 7-day) + Sprint 6 FT skeleton — Sprint 5 iter 6a of My Sea roadmap — TDD
First of three Sprint 6 commits per [[sprint-my-sea-iter-6-plan]]. Replaces the iter-4c 404 stub at `/gameboard/my-sea/gate/` w. a real token-deposit-to-redraw UI. Iter 6b will wire the navbar GATE VIEW swap + landing PAID DRAW state + seat-1 persistence; iter 6c will land the bud-btn stub.
## Server
`MySeaDraw` gains two fields: `deposit_token_id` (int, nullable) + `deposit_reserved_at` (datetime, nullable). Migration 0002. The row plays triple duty now: hand storage + 24h quota tracker + deposit reservation slot.
`_select_my_sea_token(user)` mirrors `apps.epic.models.select_token` priority (PASS > COIN > FREE > TITHE) w. two adaptations:
- CARTE excluded outright (door-spell trinket, not valid for my-sea draws).
- COIN cooldown-respecting: filters out COINs w. `next_ready_at > now`. Standard `select_token` doesn't apply this filter — room logic unchanged.
`debit_my_sea_token(user, token)` is the my-sea variant of `apps.epic.models.debit_token`:
- CARTE → ValueError (defensive; caller validates upstream).
- COIN: `next_ready_at = now + 24h` (not 7-day room cycle) + unequip from kit if equipped.
- PASS: no consumption (auto-admit, unlimited redraws).
- FREE / TITHE: deleted.
`my_sea_gate` view replaces the 404 stub. Renders the gatekeeper template w. branching on `deposit_reserved` (token reserved on row vs not).
`my_sea_insert_token` POST: picks a token via `_select_my_sea_token` + sets `deposit_token_id + deposit_reserved_at`. Creates the row if missing (so a fresh user can deposit without first using their free draw). Idempotent w.r.t. an already-reserved deposit.
`my_sea_refund_token` POST: clears deposit fields. Token isn't consumed at INSERT (refund-aware design), so this is purely a row update — no inventory side effects.
`my_sea_paid_draw` POST: commits via `debit_my_sea_token` + resets row (hand=[], created_at=now, deposit fields cleared). Redirects to `/gameboard/my-sea/` for a fresh quota cycle.
## Template + UX
`apps/gameboard/my_sea_gate.html` (new) — per user spec 2026-05-20, the gatekeeper is a darkened-modal-over-`--duoUser` bg matching the room gatekeeper's chrome (`.gate-backdrop` + `.gate-overlay` + `.gate-modal`). No hex / chair-seats — those live on the my-sea picker page itself; the gatekeeper is a transient in-flight UI for token deposit.
Coin-slot rails (mirrors room's `.token-slot`):
- Pre-deposit: form-wrapped `.token-rails` button → POSTs to `my_sea_insert_token`. Coin-panel labels read INSERT TOKEN TO PLAY.
- Post-deposit: rails inert (no form); `.token-return-btn` form → POSTs to `my_sea_refund_token`. Coin-panel labels swap to PUSH TO RETURN.
- Post-deposit: PAID DRAW btn (`#id_my_sea_paid_draw_btn`, `.btn-primary`) → POSTs to `my_sea_paid_draw`. Mirrors the room's PICK ROLES btn shape.
SCSS minimal — page bg `rgba(--duoUser, 1)` on `.my-sea-page[data-phase="gate"]`; everything else reuses the room gatekeeper's existing rules.
## FT skeleton
Per user TDD directive (2026-05-20: "Also via TDD so if we run out we're adhering to FT-described behavior"), wrote the FULL Sprint 6 FT skeleton up front (covers iter 6a + 6b + 6c). Five new FT classes in `test_game_my_sea.py`:
- `MySeaGatekeeperPageTest` (5 tests) — iter 6a; pre-deposit / INSERT / REFUND / PAID DRAW paths.
- `MySeaLandingPaidDrawTest` (1 test) — iter 6b; landing renders PAID DRAW btn when deposit reserved (red until iter 6b lands).
- `MySeaNavbarGateViewTest` (1 test) — iter 6b; navbar GATE VIEW swap (red until iter 6b).
- `MySeaSeatOnePersistenceTest` (2 tests) — iter 6b; seat 1 banned for fresh user + empty-hand active draw (red until iter 6b).
- `MySeaBudBtnStubTest` (2 tests) — iter 6c; panel opens + OK shows coming-soon Brief (red until iter 6c).
## ITs (iter 6a — 22 new + 153 total green)
- `MySeaGateViewTest` (4) — view branching pre/post deposit.
- `MySeaInsertTokenViewTest` (4) — row creation, existing row, idempotency, GET=405.
- `MySeaRefundTokenViewTest` (3) — clears fields, no token consumption, idempotent.
- `MySeaPaidDrawViewTest` (6) — FREE consumed, COIN cooldown + unequip, PASS no-op, hand reset, created_at reset, redirect.
- `SelectMySeaTokenTest` (3) — CARTE excluded, COIN cooldown excluded, PASS priority for staff.
- `DebitMySeaTokenTest` (4) — CARTE ValueError, FREE/TITHE consumed, PASS preserved.
## Trap caught
Existing User `post_save` signal auto-creates COIN + FREE tokens (`apps.lyric.models:309`). Sprint 6 ITs that assert "user has only the token I seeded" must `self.user.tokens.all().delete()` after User.create. Without it, `_select_my_sea_token` returns the auto-COIN instead of None for the CARTE-excluded test. Worth a future feedback memory if it bites again.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:29:08 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-05-20 09:47:47 -04:00
|
|
|
|
|
|
|
|
{# Sprint 6 iter 6c — bud-btn invite stub (multiplayer-coming-soon) #}
|
|
|
|
|
{# + gear-btn NVM-only menu. Both render outside .gate-modal so the #}
|
|
|
|
|
{# 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" %}
|
|
|
|
|
{% include "apps/gameboard/_partials/_my_sea_gear.html" %}
|
My Sea iter 6a: gatekeeper page + INSERT/REFUND/PAID DRAW endpoints + MySeaDraw deposit fields + `_select_my_sea_token` / `debit_my_sea_token` helpers (CARTE blocked, COIN 24h cooldown not 7-day) + Sprint 6 FT skeleton — Sprint 5 iter 6a of My Sea roadmap — TDD
First of three Sprint 6 commits per [[sprint-my-sea-iter-6-plan]]. Replaces the iter-4c 404 stub at `/gameboard/my-sea/gate/` w. a real token-deposit-to-redraw UI. Iter 6b will wire the navbar GATE VIEW swap + landing PAID DRAW state + seat-1 persistence; iter 6c will land the bud-btn stub.
## Server
`MySeaDraw` gains two fields: `deposit_token_id` (int, nullable) + `deposit_reserved_at` (datetime, nullable). Migration 0002. The row plays triple duty now: hand storage + 24h quota tracker + deposit reservation slot.
`_select_my_sea_token(user)` mirrors `apps.epic.models.select_token` priority (PASS > COIN > FREE > TITHE) w. two adaptations:
- CARTE excluded outright (door-spell trinket, not valid for my-sea draws).
- COIN cooldown-respecting: filters out COINs w. `next_ready_at > now`. Standard `select_token` doesn't apply this filter — room logic unchanged.
`debit_my_sea_token(user, token)` is the my-sea variant of `apps.epic.models.debit_token`:
- CARTE → ValueError (defensive; caller validates upstream).
- COIN: `next_ready_at = now + 24h` (not 7-day room cycle) + unequip from kit if equipped.
- PASS: no consumption (auto-admit, unlimited redraws).
- FREE / TITHE: deleted.
`my_sea_gate` view replaces the 404 stub. Renders the gatekeeper template w. branching on `deposit_reserved` (token reserved on row vs not).
`my_sea_insert_token` POST: picks a token via `_select_my_sea_token` + sets `deposit_token_id + deposit_reserved_at`. Creates the row if missing (so a fresh user can deposit without first using their free draw). Idempotent w.r.t. an already-reserved deposit.
`my_sea_refund_token` POST: clears deposit fields. Token isn't consumed at INSERT (refund-aware design), so this is purely a row update — no inventory side effects.
`my_sea_paid_draw` POST: commits via `debit_my_sea_token` + resets row (hand=[], created_at=now, deposit fields cleared). Redirects to `/gameboard/my-sea/` for a fresh quota cycle.
## Template + UX
`apps/gameboard/my_sea_gate.html` (new) — per user spec 2026-05-20, the gatekeeper is a darkened-modal-over-`--duoUser` bg matching the room gatekeeper's chrome (`.gate-backdrop` + `.gate-overlay` + `.gate-modal`). No hex / chair-seats — those live on the my-sea picker page itself; the gatekeeper is a transient in-flight UI for token deposit.
Coin-slot rails (mirrors room's `.token-slot`):
- Pre-deposit: form-wrapped `.token-rails` button → POSTs to `my_sea_insert_token`. Coin-panel labels read INSERT TOKEN TO PLAY.
- Post-deposit: rails inert (no form); `.token-return-btn` form → POSTs to `my_sea_refund_token`. Coin-panel labels swap to PUSH TO RETURN.
- Post-deposit: PAID DRAW btn (`#id_my_sea_paid_draw_btn`, `.btn-primary`) → POSTs to `my_sea_paid_draw`. Mirrors the room's PICK ROLES btn shape.
SCSS minimal — page bg `rgba(--duoUser, 1)` on `.my-sea-page[data-phase="gate"]`; everything else reuses the room gatekeeper's existing rules.
## FT skeleton
Per user TDD directive (2026-05-20: "Also via TDD so if we run out we're adhering to FT-described behavior"), wrote the FULL Sprint 6 FT skeleton up front (covers iter 6a + 6b + 6c). Five new FT classes in `test_game_my_sea.py`:
- `MySeaGatekeeperPageTest` (5 tests) — iter 6a; pre-deposit / INSERT / REFUND / PAID DRAW paths.
- `MySeaLandingPaidDrawTest` (1 test) — iter 6b; landing renders PAID DRAW btn when deposit reserved (red until iter 6b lands).
- `MySeaNavbarGateViewTest` (1 test) — iter 6b; navbar GATE VIEW swap (red until iter 6b).
- `MySeaSeatOnePersistenceTest` (2 tests) — iter 6b; seat 1 banned for fresh user + empty-hand active draw (red until iter 6b).
- `MySeaBudBtnStubTest` (2 tests) — iter 6c; panel opens + OK shows coming-soon Brief (red until iter 6c).
## ITs (iter 6a — 22 new + 153 total green)
- `MySeaGateViewTest` (4) — view branching pre/post deposit.
- `MySeaInsertTokenViewTest` (4) — row creation, existing row, idempotency, GET=405.
- `MySeaRefundTokenViewTest` (3) — clears fields, no token consumption, idempotent.
- `MySeaPaidDrawViewTest` (6) — FREE consumed, COIN cooldown + unequip, PASS no-op, hand reset, created_at reset, redirect.
- `SelectMySeaTokenTest` (3) — CARTE excluded, COIN cooldown excluded, PASS priority for staff.
- `DebitMySeaTokenTest` (4) — CARTE ValueError, FREE/TITHE consumed, PASS preserved.
## Trap caught
Existing User `post_save` signal auto-creates COIN + FREE tokens (`apps.lyric.models:309`). Sprint 6 ITs that assert "user has only the token I seeded" must `self.user.tokens.all().delete()` after User.create. Without it, `_select_my_sea_token` returns the auto-COIN instead of None for the CARTE-excluded test. Worth a future feedback memory if it bites again.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:29:08 -04:00
|
|
|
</div>
|
|
|
|
|
{% endblock content %}
|
2026-05-20 09:47:47 -04:00
|
|
|
|
|
|
|
|
{% block scripts %}
|
|
|
|
|
{# Brief module — needed by _my_sea_bud_panel's OK handler so the #}
|
|
|
|
|
{# 'Multiplayer my-sea coming soon' banner shows on a successful #}
|
|
|
|
|
{# (stub) invite. #}
|
|
|
|
|
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
|
|
|
|
{% endblock scripts %}
|