My Sea applet shell — Sprint 3 of the My Sea roadmap
User roadmap step (Sprint 3 of cluster): scaffold the My Sea applet on the gameboard + the standalone /gameboard/my-sea/ page where later sprints will host the gatekeeper / sig-select / sea-select reskin for solo-user draws. Shell-only — no draw flow yet; latest-draw rendering, mid-progress save, daily quota land in Sprints 4-9 ; **migration**: `applets/migrations/0008_seed_my_sea_applet.py` — RunPython that `update_or_create`s Applet(`slug='my-sea'`, name='My Sea', context='gameboard', default_visible=True, grid_cols=12, grid_rows=4). 12×4 wide horizontal banner so the Celtic Cross spread's 10 cards can render left-to-right in the applet aperture, scrollable like My Palette (per user spec). Reverse migration (`unseed`) deletes the row so the migration is reversible for staging rollbacks ; **applet partial**: `templates/apps/gameboard/_partials/_applet-my-sea.html` — same `{% applet_context %}` auto-discovery shape every other applet uses (`<section id="id_applet_my_sea" style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};">`). Header is a `<h2><a href="{% url 'my_sea' %}">My Sea</a></h2>` link (gold via global `body a` rule); body is a `.my-sea-scroll` container that either renders `.my-sea-card` cells from a `latest_draw_cards` context (TBD in Sprint 4-7) or a `.my-sea-empty` placeholder line "No draws yet." for fresh users ; **standalone page**: new `gameboard/views.py:my_sea` view + url at `/gameboard/my-sea/` (URL name `my_sea`) rendering `apps/gameboard/my_sea.html` — `{% extends "core/base.html" %}` shell w. letter-spread `<span>My</span><span>Sea</span>` h2 wordmark + `.my-sea-page__empty` placeholder paragraph "Your sea is calm. Draws will appear here." `page_class` doubled to `page-gameboard page-my-sea` so the body inherits the gameboard's landscape aperture treatment AND any future my-sea-specific styles can target a single class. Login-required like the rest of gameboard ; **tests (+6 ITs)**: GameboardViewTest gains 3 — `test_gameboard_shows_my_sea_applet` (cssselect pins #id_applet_my_sea), `test_my_sea_applet_renders_empty_state_for_new_user` (asserts ".my-sea-empty" text + no ".my-sea-card" rows), `test_my_sea_applet_header_links_to_my_sea_page` (h2 a href == reverse('my_sea')); new MySeaViewTest class — `test_my_sea_requires_login` (redirect to /?next=...), `test_my_sea_renders_200`, `test_my_sea_uses_gameboard_page_class` (page-gameboard + page-my-sea both in body class). Existing GameboardViewTest setUp already does `get_or_create` per-applet so no fixture change needed for the migration-driven my-sea row ; 1005 IT/UT green (+6 from 999) in 45s; visual verified in Claudezilla at iPhone-14 portrait — applet renders w. rotated "MY SEA" vertical label + "No draws yet." body; /gameboard/my-sea/ standalone page renders w. letter-spread wordmark + placeholder ; **next**: Sprint 4 — My Sea sig-select phase (single-significator pick for solo user, w. the parameterized hex CSS from Sprint 1 hosting the chair-less or single-chair variant)
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 19:45:57 -04:00
|
|
|
{% extends "core/base.html" %}
|
|
|
|
|
{% load static %}
|
|
|
|
|
|
Game Sign picker @ /billboard/my-sign/ + billboard applet — Sprint 4a of My Sea roadmap — TDD
User scope (per design conv this session): split the room's sig-select responsibility off into a standalone billboard-context "My Significator" applet — branded "Game Sign" on the surface. Same 18-card pile as room sig-select (16 middle arcana + Major 0 & 1 filtered by Note unlocks); polarity collapses to a single FLIP choice (the FLIP btn in the picker carousel toggles User.significator_reversed). Selection persists globally on the User model + propagates to the billboard's Game Sign applet ; **naming convention locked**: "significator" stays at storage (User.significator FK + User.significator_reversed) + room sig-select context (DRY w. existing template/JS); "Sign" / "Game Sign" is the billboard-surface branding (file my_sign.html, URL /billboard/my-sign/, URL names my_sign + save_sign, applet name "Game Sign", page wordmark "Game Sign", btn label SAVE SIGN). Action URLs don't carry a trailing slash per project convention (/billboard/my-sign/save vs the page's /billboard/my-sign/) ; **schema**: User gains 2 fields — `significator: FK → epic.TarotCard (nullable, on_delete=SET_NULL)` + `significator_reversed: BooleanField(default=False)`. Migration lyric/0006_user_significator_user_significator_reversed.py auto-generated; reversible. Applet seed in applets/0009_seed_my_sig_applet.py adds the row (slug='my-sign', name='Game Sign', context='billboard', default_visible=True, grid_cols=4, grid_rows=6), idempotent update_or_create, reversible unseed() ; **picker page** (my_sign.html): solo lift of `_sig_select_overlay.html` — sig-stage-card scaffold + sig-stat-block + 18-card grid + SAVE SIGN form. Stripped: countdown / WebSocket / polarity / multi-user / reservations. Empty-state branch covers no-equipped-deck (link back to Game Kit; full Brief-redirect + Earthman-Backup fallback deferred to a follow-up sub-sprint). Minimal inline JS: click .sig-card → mark .sig-focused + set hidden card_id + enable SAVE SIGN; FLIP btn toggles .is-reversed + the hidden reversed input. Stage-card preview (name/qualifier population + keyword swap on FLIP) deferred — Sprint 4a follow-up will lift stage-card.js's populator into a non-room context ; **applet partial** (_applet-my-sign.html): renders user.significator's corner-rank + suit-icon + name_title if set; `.my-sign-applet-empty` "No sign chosen yet." otherwise. Header `<h2><a href="{% url 'billboard:my_sign' %}">Game Sign</a></h2>` links to the picker ; **helper refactor** (epic/models.py): extracted `_sig_unique_cards_for_deck(deck_variant)` from `_sig_unique_cards(room)`. New public `personal_sig_cards(user)` parallels `levity_sig_cards / gravity_sig_cards` but pulls from `user.equipped_deck` instead of `room.deck_variant`. Same Note-unlock filtering. No behavior change to existing room callers (3-line wrapper preserves the room signature) ; **TDD trail** — user called out mid-sprint that I'd skipped FTs; pivoted to FT-first. test_bill_my_sign.py (new, 3 FTs): T1 picker renders w. wordmark + target card present in grid; T2 click card → SAVE SIGN enables → POST persists → applet shows the card; T3 fresh user → applet renders empty-state. Initial reds — (a) setUp's `personal_sig_cards(user)` returned [] because StaticLiveServerTestCase → TransactionTestCase flushes migration-seeded DeckVariant + TarotCard between tests; fixed w. `serialized_rollback = True` on the test class (per [[feedback_transactiontestcase_flush]]); (b) h2 wordmark assertion against `MYSIGNIFICATOR` failed against the renamed "Game Sign" + the letter-splitter spreading chars across <span> children — switched to whitespace-stripped substring check `GAMESIGN`; (c) `.fan-corner-rank` text is CSS-hidden so Selenium returns "" — replaced corner-rank assertions w. data-card-id selectors (already-proven reliable from the parent .sig-card lookup) ; ITs (+12, in apps.billboard.tests.integrated.test_views): MySignViewTest (6 — login redirect, 200 + template, 16-card pile, save persists, invalid card_id → 403, GET save redirects); BillboardAppletMySignTest (3 — applet rendered, empty-state w/o sig, card+reversed class w. sig). PersonalSigCardsTest in apps.epic.tests.integrated.test_models (3 — happy path 16 cards, no-equipped-deck → [], schizo Note unlocks Major 1) ; pre-existing change picked up by the commit: my_sea.html branding "Game Sea" (user-modified mid-session; was "My Sea" in Sprint 3 — divergence captured in MEMORY.md follow-up) ; 1020 IT/UT green (+12) in 46s; 3 FTs green in 24s. Sprint 4a unblocks Sprint 4b (My Sea gating w. --terUser link to /billboard/my-sign/) + Sprint 4c (FT helper for mocking the sig choice across other FTs)
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:23:24 -04:00
|
|
|
{% block title_text %}Game Sea{% endblock title_text %}
|
|
|
|
|
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}
|
My Sea applet shell — Sprint 3 of the My Sea roadmap
User roadmap step (Sprint 3 of cluster): scaffold the My Sea applet on the gameboard + the standalone /gameboard/my-sea/ page where later sprints will host the gatekeeper / sig-select / sea-select reskin for solo-user draws. Shell-only — no draw flow yet; latest-draw rendering, mid-progress save, daily quota land in Sprints 4-9 ; **migration**: `applets/migrations/0008_seed_my_sea_applet.py` — RunPython that `update_or_create`s Applet(`slug='my-sea'`, name='My Sea', context='gameboard', default_visible=True, grid_cols=12, grid_rows=4). 12×4 wide horizontal banner so the Celtic Cross spread's 10 cards can render left-to-right in the applet aperture, scrollable like My Palette (per user spec). Reverse migration (`unseed`) deletes the row so the migration is reversible for staging rollbacks ; **applet partial**: `templates/apps/gameboard/_partials/_applet-my-sea.html` — same `{% applet_context %}` auto-discovery shape every other applet uses (`<section id="id_applet_my_sea" style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};">`). Header is a `<h2><a href="{% url 'my_sea' %}">My Sea</a></h2>` link (gold via global `body a` rule); body is a `.my-sea-scroll` container that either renders `.my-sea-card` cells from a `latest_draw_cards` context (TBD in Sprint 4-7) or a `.my-sea-empty` placeholder line "No draws yet." for fresh users ; **standalone page**: new `gameboard/views.py:my_sea` view + url at `/gameboard/my-sea/` (URL name `my_sea`) rendering `apps/gameboard/my_sea.html` — `{% extends "core/base.html" %}` shell w. letter-spread `<span>My</span><span>Sea</span>` h2 wordmark + `.my-sea-page__empty` placeholder paragraph "Your sea is calm. Draws will appear here." `page_class` doubled to `page-gameboard page-my-sea` so the body inherits the gameboard's landscape aperture treatment AND any future my-sea-specific styles can target a single class. Login-required like the rest of gameboard ; **tests (+6 ITs)**: GameboardViewTest gains 3 — `test_gameboard_shows_my_sea_applet` (cssselect pins #id_applet_my_sea), `test_my_sea_applet_renders_empty_state_for_new_user` (asserts ".my-sea-empty" text + no ".my-sea-card" rows), `test_my_sea_applet_header_links_to_my_sea_page` (h2 a href == reverse('my_sea')); new MySeaViewTest class — `test_my_sea_requires_login` (redirect to /?next=...), `test_my_sea_renders_200`, `test_my_sea_uses_gameboard_page_class` (page-gameboard + page-my-sea both in body class). Existing GameboardViewTest setUp already does `get_or_create` per-applet so no fixture change needed for the migration-driven my-sea row ; 1005 IT/UT green (+6 from 999) in 45s; visual verified in Claudezilla at iPhone-14 portrait — applet renders w. rotated "MY SEA" vertical label + "No draws yet." body; /gameboard/my-sea/ standalone page renders w. letter-spread wordmark + placeholder ; **next**: Sprint 4 — My Sea sig-select phase (single-significator pick for solo user, w. the parameterized hex CSS from Sprint 1 hosting the chair-less or single-chair variant)
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 19:45:57 -04:00
|
|
|
|
|
|
|
|
{% block content %}
|
2026-05-19 15:15:37 -04:00
|
|
|
<div class="my-sea-page" data-phase="landing">
|
2026-05-19 01:38:55 -04:00
|
|
|
{% 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. #}
|
2026-05-19 15:15:37 -04:00
|
|
|
{# FYI (→ /billboard/my-sign/) + NVM (→ /gameboard/) until the #}
|
2026-05-19 01:38:55 -04:00
|
|
|
{# 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">
|
|
|
|
|
<p class="my-sea-sign-gate__line">
|
|
|
|
|
Look!—pick your sign before drawing the Sea.
|
|
|
|
|
</p>
|
|
|
|
|
<div class="my-sea-sign-gate__actions">
|
|
|
|
|
<a class="btn btn-cancel my-sea-sign-gate__back"
|
2026-05-19 15:15:37 -04:00
|
|
|
href="{% url 'gameboard' %}">NVM</a>
|
2026-05-19 01:38:55 -04:00
|
|
|
<a class="btn btn-info my-sea-sign-gate__fyi"
|
|
|
|
|
href="{% url 'billboard:my_sign' %}">FYI</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% else %}
|
2026-05-19 15:15:37 -04:00
|
|
|
{# 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">
|
2026-05-19 15:48:07 -04:00
|
|
|
{# Sprint 5 iter 1 — FREE DRAW = the 1/24hr free-quota draw. #}
|
|
|
|
|
{# Future sprint will conditionally swap this for a DRAW SEA #}
|
|
|
|
|
{# .btn-primary that calls the gatekeeper partial once the #}
|
|
|
|
|
{# free daily has been used; until then the btn renders FREE #}
|
|
|
|
|
{# DRAW. ID retained as `id_draw_sea_btn` (intent: the draw #}
|
|
|
|
|
{# entry point) so the swap is label-only when iter 6+ lands. #}
|
|
|
|
|
<button id="id_draw_sea_btn" type="button" class="btn btn-primary">FREE<br>DRAW</button>
|
2026-05-19 15:15:37 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% for n in "123456" %}
|
2026-05-19 15:48:07 -04:00
|
|
|
{# Chair-position labels (1C-6C). No roles in #}
|
|
|
|
|
{# my-sea (this is the solo draw flow); using #}
|
|
|
|
|
{# `.seat-position-label` instead of the room's #}
|
|
|
|
|
{# `.seat-role-label` to keep the no-role #}
|
|
|
|
|
{# semantics clean. `.position-status-icon` + #}
|
|
|
|
|
{# `.fa-ban` are unchanged — already role- #}
|
|
|
|
|
{# agnostic in _room.scss. #}
|
2026-05-19 15:15:37 -04:00
|
|
|
<div class="table-seat" data-slot="{{ n }}">
|
|
|
|
|
<i class="fa-solid fa-chair"></i>
|
2026-05-19 15:48:07 -04:00
|
|
|
<span class="seat-position-label">{{ n }}C</span>
|
|
|
|
|
<i class="position-status-icon fa-solid fa-ban"></i>
|
2026-05-19 15:15:37 -04:00
|
|
|
</div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-05-19 16:06:14 -04:00
|
|
|
{# Picker phase — three-card cross w. sig pinned in core + #}
|
|
|
|
|
{# cover (overlaid on sig) + leave (left) + loom (right). #}
|
|
|
|
|
{# Crown / lay / cross from the gameroom's 6-position spread #}
|
|
|
|
|
{# are forsaken in the solo flow per user-locked spec. Hidden #}
|
|
|
|
|
{# until FREE DRAW click swaps data-phase to `picker` (see #}
|
|
|
|
|
{# inline JS below); form col (spread dropdown / decks / #}
|
|
|
|
|
{# LOCK HAND / DEL) lands in iter 3. #}
|
2026-05-19 15:15:37 -04:00
|
|
|
<div class="my-sea-picker" style="display:none">
|
2026-05-19 16:06:14 -04:00
|
|
|
<div class="sea-cards-col">
|
|
|
|
|
<div class="sea-cross my-sea-cross">
|
|
|
|
|
<div class="sea-crucifix-cell sea-pos-leave">
|
|
|
|
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sea-crucifix-cell sea-pos-core">
|
|
|
|
|
<div class="sig-stage-card sea-sig-card"
|
|
|
|
|
data-card-id="{{ significator.id }}">
|
|
|
|
|
<span class="fan-corner-rank">{{ significator.corner_rank }}</span>
|
|
|
|
|
{% if significator.suit_icon %}<i class="fa-solid {{ significator.suit_icon }}"></i>{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sea-pos-cover">
|
|
|
|
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sea-crucifix-cell sea-pos-loom">
|
|
|
|
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-05-19 15:15:37 -04:00
|
|
|
</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');
|
2026-05-19 15:48:07 -04:00
|
|
|
// FREE DRAW click flow:
|
|
|
|
|
// 1) seat 1C transitions to .seated (chair --terUser +
|
|
|
|
|
// drop-shadow glow + .fa-ban → .fa-circle-check —
|
|
|
|
|
// _room.scss line 596 makes the colour change a
|
|
|
|
|
// 0.6s ease transition);
|
|
|
|
|
// 2) after a brief delay (so the user sees the seat
|
|
|
|
|
// animation), data-phase swaps to 'picker' + the
|
|
|
|
|
// landing hides. Picker content lands in iter 2.
|
|
|
|
|
// The seat-take logic is solo-coded for now: 1C is the
|
|
|
|
|
// lowest-numeral chair, and my-sea is 1-user-per-page
|
|
|
|
|
// until the friend-invite feature (per [[project-my-
|
|
|
|
|
// sea-roadmap]]) — so 1C is always the user's seat.
|
|
|
|
|
var SEAT_ANIM_MS = 800;
|
2026-05-19 15:15:37 -04:00
|
|
|
if (drawBtn) {
|
|
|
|
|
drawBtn.addEventListener('click', function () {
|
2026-05-19 15:48:07 -04:00
|
|
|
var seat1 = page.querySelector('.table-seat[data-slot="1"]');
|
|
|
|
|
if (seat1) {
|
|
|
|
|
seat1.classList.add('seated');
|
|
|
|
|
var statusIcon = seat1.querySelector('.position-status-icon');
|
|
|
|
|
if (statusIcon) {
|
|
|
|
|
statusIcon.classList.remove('fa-ban');
|
|
|
|
|
statusIcon.classList.add('fa-circle-check');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
page.setAttribute('data-phase', 'picker');
|
|
|
|
|
if (landing) landing.style.display = 'none';
|
|
|
|
|
if (picker) picker.style.display = '';
|
|
|
|
|
}, SEAT_ANIM_MS);
|
2026-05-19 15:15:37 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// 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 %}
|
2026-05-19 01:38:55 -04:00
|
|
|
{% endif %}
|
My Sea applet shell — Sprint 3 of the My Sea roadmap
User roadmap step (Sprint 3 of cluster): scaffold the My Sea applet on the gameboard + the standalone /gameboard/my-sea/ page where later sprints will host the gatekeeper / sig-select / sea-select reskin for solo-user draws. Shell-only — no draw flow yet; latest-draw rendering, mid-progress save, daily quota land in Sprints 4-9 ; **migration**: `applets/migrations/0008_seed_my_sea_applet.py` — RunPython that `update_or_create`s Applet(`slug='my-sea'`, name='My Sea', context='gameboard', default_visible=True, grid_cols=12, grid_rows=4). 12×4 wide horizontal banner so the Celtic Cross spread's 10 cards can render left-to-right in the applet aperture, scrollable like My Palette (per user spec). Reverse migration (`unseed`) deletes the row so the migration is reversible for staging rollbacks ; **applet partial**: `templates/apps/gameboard/_partials/_applet-my-sea.html` — same `{% applet_context %}` auto-discovery shape every other applet uses (`<section id="id_applet_my_sea" style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};">`). Header is a `<h2><a href="{% url 'my_sea' %}">My Sea</a></h2>` link (gold via global `body a` rule); body is a `.my-sea-scroll` container that either renders `.my-sea-card` cells from a `latest_draw_cards` context (TBD in Sprint 4-7) or a `.my-sea-empty` placeholder line "No draws yet." for fresh users ; **standalone page**: new `gameboard/views.py:my_sea` view + url at `/gameboard/my-sea/` (URL name `my_sea`) rendering `apps/gameboard/my_sea.html` — `{% extends "core/base.html" %}` shell w. letter-spread `<span>My</span><span>Sea</span>` h2 wordmark + `.my-sea-page__empty` placeholder paragraph "Your sea is calm. Draws will appear here." `page_class` doubled to `page-gameboard page-my-sea` so the body inherits the gameboard's landscape aperture treatment AND any future my-sea-specific styles can target a single class. Login-required like the rest of gameboard ; **tests (+6 ITs)**: GameboardViewTest gains 3 — `test_gameboard_shows_my_sea_applet` (cssselect pins #id_applet_my_sea), `test_my_sea_applet_renders_empty_state_for_new_user` (asserts ".my-sea-empty" text + no ".my-sea-card" rows), `test_my_sea_applet_header_links_to_my_sea_page` (h2 a href == reverse('my_sea')); new MySeaViewTest class — `test_my_sea_requires_login` (redirect to /?next=...), `test_my_sea_renders_200`, `test_my_sea_uses_gameboard_page_class` (page-gameboard + page-my-sea both in body class). Existing GameboardViewTest setUp already does `get_or_create` per-applet so no fixture change needed for the migration-driven my-sea row ; 1005 IT/UT green (+6 from 999) in 45s; visual verified in Claudezilla at iPhone-14 portrait — applet renders w. rotated "MY SEA" vertical label + "No draws yet." body; /gameboard/my-sea/ standalone page renders w. letter-spread wordmark + placeholder ; **next**: Sprint 4 — My Sea sig-select phase (single-significator pick for solo user, w. the parameterized hex CSS from Sprint 1 hosting the chair-less or single-chair variant)
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 19:45:57 -04:00
|
|
|
</div>
|
|
|
|
|
{% endblock content %}
|