PICK SEA Sprint A: async sky→sea transition via WS room:sky_confirmed — TDD

- natus_save: group_send room:sky_confirmed after confirm (carries seat_role)
- consumer: sky_confirmed handler rebroadcasts to room group
- _notify_sky_confirmed() helper mirrors _notify_pick_sky_available
- sea_partial view: renders _sea_overlay.html partial for in-page injection (403 if not sky_confirmed)
- epic:sea_partial URL registered
- _natus_overlay.html: data-user-seat-role attr; _onSkyConfirmed() fetches sea partial,
  removes natus overlay + backdrop, injects sea HTML, toggles sea-open on html root;
  room:sky_confirmed WS listener calls _onSkyConfirmed only for matching seat role
- user_seat_role added to SKY_SELECT context
- FT: PickSeaAsyncTransitionTest (3 tests, ChannelsFunctionalTest) — sea overlay,
  natus gone, sea-open class — all green

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-04-28 22:16:38 -04:00
parent 379e0ab80c
commit 39e12d6a3d
5 changed files with 196 additions and 4 deletions

View File

@@ -8,7 +8,9 @@
<div class="natus-overlay"
id="id_natus_overlay"
data-preview-url="{% url 'epic:natus_preview' room.id %}"
data-save-url="{% url 'epic:natus_save' room.id %}">
data-save-url="{% url 'epic:natus_save' room.id %}"
data-sea-partial-url="{% url 'epic:sea_partial' room.id %}"
data-user-seat-role="{{ user_seat_role }}">
<div class="natus-modal-wrap">
<div class="natus-modal">
@@ -369,9 +371,11 @@
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(() => {
setStatus('Sky saved!');
setTimeout(closeNatus, 1200);
.then(data => {
if (!data.confirmed) {
setStatus('Sky saved!');
}
// Confirmed state is driven by the room:sky_confirmed WS event
})
.catch(err => {
setStatus(`Save failed: ${err.message}`, 'error');
@@ -379,6 +383,28 @@
});
});
// ── Sky confirmed → inject sea partial ───────────────────────────────────
const SEA_PARTIAL_URL = overlay.dataset.seaPartialUrl;
function _onSkyConfirmed() {
fetch(SEA_PARTIAL_URL, { credentials: 'same-origin' })
.then(r => r.text())
.then(html => {
// Remove natus overlay + backdrop; inject sea partial before body close
var backdrop = document.querySelector('.natus-backdrop');
if (backdrop) backdrop.remove();
overlay.remove();
document.documentElement.classList.remove('natus-open');
document.body.insertAdjacentHTML('beforeend', html);
document.documentElement.classList.add('sea-open');
})
.catch(() => {
// Fallback: just close natus and let page refresh handle the transition
closeNatus();
});
}
// ── CSRF ──────────────────────────────────────────────────────────────────
function _getCsrf() {
@@ -391,6 +417,15 @@
// openNatus() so the animation plays when the modal opens, not silently
// in the background on page load.
// WS: server broadcasts sky_confirmed when any gamer confirms their sky.
// Only act when the event's seat_role matches this browser's seat.
const MY_SEAT_ROLE = overlay.dataset.userSeatRole;
window.addEventListener('room:sky_confirmed', function (e) {
if (MY_SEAT_ROLE && e.detail.seat_role && e.detail.seat_role !== MY_SEAT_ROLE) return;
_onSkyConfirmed();
});
_restoreForm();
})();
</script>