My Sea DRAW SEA landing — Sprint 5 iter 1 of My Sea roadmap — TDD

DRAW SEA landing UX on /gameboard/my-sea/ for users past the [[sprint-my-sea-sign-gate-may19]] gate. DRY table hex (reused from the room shell + my-sign Sprint 4a iter 3) w. 6 chair seats labeled 1C-6C (placeholder for friend-invite per the My Sea roadmap "Six chairs retained even in solo" anchor) + central DRAW SEA `.btn-primary` mirroring SCAN SIGN on /billboard/my-sign/. Click swaps `.my-sea-page[data-phase]` from `landing` to `picker`; the picker UX itself (three-card cross w. cover/leave/loom + form col / spread dropdown / decks / LOCK HAND / DEL) lands in iters 2 + 3.

The 'C' suffix on the chair labels = "Chair" (user-locked); no role semantics (this is a solo draw, not a 6-player role assignment). `.table-seat` CSS class + `data-slot` attribute preserved so the room's existing `[data-slot="N"]` positioning rules (`_room.scss` L583-588) carry over for free — no SCSS fork; just a new `.seat-label` span inside each seat.

The 'Default deck warning' Brief banner from /billboard/my-sign/ fires verbatim when `user.equipped_deck` is None (the user is headed for a draw against the Earthman [Shabby Cardstock] backup unless they equip one first). Tagged `.my-sea-intro-banner` so FTs disambiguate from other Briefs. Same FYI (→ /gameboard/) / NVM (dismiss) action grammar.

Bundled: BACK→NVM label swap (user-edited mid-sprint) in the existing sign-gate. CSS class `.my-sea-sign-gate__back` retained — the swap was label-only — so existing FTs targeting the class still pass; docstrings + comments updated for accuracy.

Files:
- `apps/gameboard/views.py` — `my_sea` view adds 2 context keys: `no_equipped_deck` (bool) + `show_backup_intro_banner` (= user_has_sig AND no_equipped_deck). The sig-gate path still wins precedence.
- `templates/apps/gameboard/my_sea.html` — `.my-sea-page[data-phase="landing"]`; new `.my-sea-landing` block w. room-shell hex + `#id_draw_sea_btn` + 6 `.table-seat[data-slot="N"]` w. `<span class="seat-label">NC</span>`; new `.my-sea-picker` placeholder (`display:none` til DRAW SEA click); inline `<script>` for the click→data-phase swap + scaleTable re-fire on next tick (mirrors my-sign's iter-3 RAF dispatch); copies the my-sign Brief banner script block verbatim w. `.my-sea-intro-banner` post-render tag.
- `static_src/scss/_gameboard.scss` — new `.my-sea-page` + `.my-sea-landing` + `.my-sea-picker` rule blocks mirroring `.my-sign-page` / `.my-sign-landing` from `_card-deck.scss`. `.seat-label` styled in `--terUser` to match the chair iconography.
- `apps/gameboard/tests/integrated/test_views.py` — `+7 ITs` in new `MySeaDrawSeaLandingViewTest` pinning the context keys + presence/absence of `#id_draw_sea_btn` + 6 `data-slot=N` w. `NC` labels + Sprint 4b gate's precedence over the new landing.
- `functional_tests/test_game_my_sea.py` — `+5 FTs` in new `MySeaDrawSeaLandingTest` for the visible UX (hex + DRAW SEA, 6 seats labeled 1C-6C, click→picker phase swap, Brief banner on no-deck, no banner when deck equipped). Uses new `_assign_sig` helper from [[sprint-sig-page-helper-may19c]] for the user-w-sig precondition.

Tests: 25/25 FTs green across test_bill_my_sign + test_game_my_sea in 219s; 1036/1036 IT/UT green in 50s (+7 from baseline).

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-19 15:15:37 -04:00
parent 4d1c74a2af
commit de48ae226d
5 changed files with 352 additions and 23 deletions

View File

@@ -5,11 +5,11 @@
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}
{% block content %}
<div class="my-sea-page">
<div class="my-sea-page" data-phase="landing">
{% if not user_has_sig %}
{# Sprint 4b sign-gate. The draw UX is gated behind a saved #}
{# significator — render a Look!-formatted Brief-style line w. #}
{# FYI (→ /billboard/my-sign/) + BACK (→ /gameboard/) until the #}
{# FYI (→ /billboard/my-sign/) + NVM (→ /gameboard/) until the #}
{# user picks a sign. Inline (not portaled like .note-banner) #}
{# because the gate IS the page content, not a transient nudge. #}
<div class="my-sea-sign-gate">
@@ -18,15 +18,95 @@
</p>
<div class="my-sea-sign-gate__actions">
<a class="btn btn-cancel my-sea-sign-gate__back"
href="{% url 'gameboard' %}">BACK</a>
href="{% url 'gameboard' %}">NVM</a>
<a class="btn btn-info my-sea-sign-gate__fyi"
href="{% url 'billboard:my_sign' %}">FYI</a>
</div>
</div>
{% else %}
{# Sprint 3 shell — gatekeeper / sig-select / sea-select phases #}
{# will land here in later sprints of the My Sea roadmap. #}
<p class="my-sea-page__empty">No draws yet&mdash;the depths remain unfathomable.</p>
{# Sprint 5 iter 1 — DRAW SEA landing UX. DRY table hex from #}
{# the room shell (.room-shell > .room-table > … > .table-hex) #}
{# w. 6 chair seats labeled 1C-6C as placeholders for the #}
{# friend-invite feature per the My Sea roadmap architectural #}
{# anchor "Six chairs retained even in solo". DRAW SEA btn #}
{# mirrors SCAN SIGN on /billboard/my-sign/. #}
<div class="my-sea-landing">
<div class="room-shell">
<div id="id_game_table" class="room-table">
<div class="room-table-scene">
<div class="table-hex-border">
<div class="table-hex">
<div class="table-center">
<button id="id_draw_sea_btn" type="button" class="btn btn-primary">DRAW<br>SEA</button>
</div>
</div>
</div>
{% for n in "123456" %}
<div class="table-seat" data-slot="{{ n }}">
<i class="fa-solid fa-chair"></i>
<span class="seat-label">{{ n }}C</span>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{# Picker phase placeholder — iter 2 wires up the three-card #}
{# cross layout (sig in center + cover/leave/loom) w. the #}
{# --duoUser bg + form col (spread dropdown / decks / LOCK #}
{# HAND / DEL). For iter 1 it's just a phase-swap target. #}
<div class="my-sea-picker" style="display:none">
<p class="my-sea-picker__placeholder">DRAW SEA picker — wiring lands in Sprint 5 iter 2.</p>
</div>
<script src="{% static 'apps/epic/room.js' %}"></script>
<script>
(function () {
var page = document.querySelector('.my-sea-page');
if (!page) return;
var landing = page.querySelector('.my-sea-landing');
var picker = page.querySelector('.my-sea-picker');
var drawBtn = document.getElementById('id_draw_sea_btn');
if (drawBtn) {
drawBtn.addEventListener('click', function () {
page.setAttribute('data-phase', 'picker');
if (landing) landing.style.display = 'none';
if (picker) picker.style.display = '';
});
}
// Mirror my-sign's scaleTable() init timing fix — the
// .my-sea-page hasn't flushed its flex sizing on
// DOMContentLoaded, so the hex stays unscaled until we
// dispatch a resize once layout settles.
window.requestAnimationFrame(function () {
window.dispatchEvent(new Event('resize'));
});
}());
</script>
{# Brief 'Default deck warning' banner — lifted verbatim from #}
{# /billboard/my-sign/'s no-equipped-deck path. Same copy, #}
{# same FYI (→ /gameboard/) + NVM (dismiss + proceed) actions.#}
{# Tagged w. .my-sea-intro-banner so FTs disambiguate from #}
{# any other Briefs on the page. #}
<script src="{% static 'apps/dashboard/note.js' %}"></script>
{% if show_backup_intro_banner %}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!window.Brief || !Brief.showBanner) return;
Brief.showBanner({
title: 'Default deck warning',
line_text: 'Look!—no deck is equipped. Navigate to the Game Kit to equip one (FYI) or (NVM) proceed with the Earthman [Shabby Cardstock] deck.',
post_url: '{% url "gameboard" %}',
created_at: '',
kind: 'NUDGE',
});
var banner = document.querySelector('.note-banner');
if (banner) banner.classList.add('my-sea-intro-banner');
});
</script>
{% endif %}
{% endif %}
</div>
{% endblock content %}