my-sea: second spectate broadcast — seat ring updates live on deposit / BYE — TDD
Extends the async-witness WS so a visitor joining (deposit) or leaving (BYE) pushes the seat ring to the other watchers — they see members come + go without a refresh, same channel as the live draw. - views.py: `_my_sea_seats(owner)` extracted (owner 1C + present invitees 2C-6C by deposit order, sans per-viewer is_self) — used by BOTH the my_sea_visit render (layers is_self on) AND a new guarded `_notify_sea_seats(owner_id)` broadcast. Fired from my_sea_visit_insert_token (seat taken) + my_sea_visit_leave (seat freed). - consumers.py: MySeaSpectateConsumer gains a `sea_seats` handler. - my_sea_visit.html: the WS client re-renders the `.table-seat` ring from a `sea_seats` message, re-marking the viewer's own --self chair via the embedded seat token + re-firing the one-shot seated glow (localStorage-gated). Tests: +1 channels relay IT (sea_seats received) + 2 view ITs (deposit / BYE each broadcast the ring). Existing multi-seat ITs stay green on the refactored helper. Client re-render needs live 3-party verification on staging. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,39 @@
|
||||
try { deck = JSON.parse((deckEl && deckEl.textContent) || '{}'); } catch (e) {}
|
||||
var byId = {};
|
||||
(deck.levity || []).concat(deck.gravity || []).forEach(function (c) { byId[c.id] = c; });
|
||||
// This viewer's own seat token — so a live seat re-render can re-mark
|
||||
// their --self chair without the server knowing who's watching.
|
||||
var MY_SEAT_TOKEN = 'visit-{{ sea_invite.id }}';
|
||||
|
||||
// Re-render the table-seat ring when presence changes (a deposit takes
|
||||
// a seat / a BYE frees one), so other watchers see members come + go
|
||||
// without a refresh. Mirrors the server seat loop in markup.
|
||||
function _renderSeats(seats) {
|
||||
var scene = document.querySelector('.room-table-scene');
|
||||
if (!scene || !seats) return;
|
||||
scene.querySelectorAll('.table-seat').forEach(function (s) { s.remove(); });
|
||||
seats.forEach(function (seat) {
|
||||
var isSelf = seat.token && seat.token === MY_SEAT_TOKEN;
|
||||
var div = document.createElement('div');
|
||||
div.className = 'table-seat' + (seat.present ? ' seated' : '')
|
||||
+ (isSelf ? ' table-seat--self' : '');
|
||||
div.setAttribute('data-slot', seat.n);
|
||||
if (seat.present && seat.token) div.setAttribute('data-seat-token', seat.token);
|
||||
div.innerHTML =
|
||||
'<i class="fa-solid fa-chair"></i>' +
|
||||
'<span class="seat-position-label">' + seat.label + '</span>' +
|
||||
'<i class="position-status-icon fa-solid ' +
|
||||
(seat.present ? 'fa-circle-check' : 'fa-ban') + '"></i>';
|
||||
scene.appendChild(div);
|
||||
});
|
||||
// Re-fire the one-shot "just seated" glow for fresh occupancies
|
||||
// (localStorage-gated per token, so it only flares once).
|
||||
if (window.playSeatGlow) {
|
||||
scene.querySelectorAll('.table-seat.seated[data-seat-token]')
|
||||
.forEach(window.playSeatGlow);
|
||||
}
|
||||
}
|
||||
window._mySeaRenderSeats = _renderSeats; // test seam
|
||||
|
||||
function _applyHand(hand) {
|
||||
if (!window.SeaDeal || !window.SeaDeal.register) return;
|
||||
@@ -192,7 +225,9 @@
|
||||
ws.onmessage = function (ev) {
|
||||
var msg;
|
||||
try { msg = JSON.parse(ev.data); } catch (e) { return; }
|
||||
if (msg && msg.type === 'sea_draw') _applyHand(msg.hand);
|
||||
if (!msg) return;
|
||||
if (msg.type === 'sea_draw') _applyHand(msg.hand);
|
||||
else if (msg.type === 'sea_seats') _renderSeats(msg.seats);
|
||||
};
|
||||
// Brief capped reconnect for transient blips (no infinite loop if
|
||||
// the spectate server/Redis is down).
|
||||
|
||||
Reference in New Issue
Block a user