my-sea spectator: render all present members on the hex (2C-6C), not just the viewer — TDD

The spectator hex showed only owner 1C + the viewer in 2C; other present
visitors were invisible. The view now builds a  list — owner 1C + each
present invitee in 2C-6C by deposit order (capped at MY_SEA_MAX_VISITORS) — so
every viewer sees the same absolute seating, with their own seat marked
.table-seat--self (a subtle --terUser tint).

- my_sea_visit:  context (present/empty + token + label + is_self).
- my_sea_visit.html: seat ring loops  instead of a hardcoded 1C/2C.
- _room.scss: .table-seat--self chair tint.
- +1 IT (3 present visitors → 2C-4C seated, viewer is the --self one); the
  both-seated IT updated for the --self marker. 292 gameboard ITs green.

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:
Disco DeDisco
2026-05-29 22:01:23 -04:00
parent cb7ca4b5f3
commit 2cbc1bf292
4 changed files with 62 additions and 17 deletions

View File

@@ -867,7 +867,7 @@ def my_sea_visit(request, owner_id):
sea-btn cascade (+ the @mailman post-attribution anchor) both land here,
and the click IS the acceptance. A stranger with no invite still 403s."""
from apps.lyric.models import User
from .models import SeaInvite
from .models import SeaInvite, MY_SEA_MAX_VISITORS
owner = get_object_or_404(User, id=owner_id)
if owner == request.user:
return redirect("my_sea")
@@ -895,6 +895,28 @@ def my_sea_visit(request, owner_id):
or owner_draw.paid_through_at is not None
)
owner_seated = owner_hand_non_empty or owner_paid
# Multi-seat hex (2026-05-29): every present member shows on the ring —
# owner in 1C, then each present invitee in 2C6C by deposit order (the
# same seats everyone sees). `seats` drives the table-seat loop; an empty
# seat renders the .fa-ban default. Capped at MY_SEA_MAX_VISITORS visitors.
owner_token = (f"owner-{owner.id}-{owner_draw.id}"
if owner_draw is not None else "")
seats = [{"n": 1, "label": "1C", "present": owner_seated, "token": owner_token}]
present_invitees = (
SeaInvite.objects
.filter(owner=owner, status=SeaInvite.ACCEPTED,
token_deposited_at__isnull=False, left_at__isnull=True)
.order_by("token_deposited_at")[:MY_SEA_MAX_VISITORS]
)
for idx, inv in enumerate(present_invitees):
seats.append({
"n": idx + 2, "label": f"{idx + 2}C", "present": True,
"token": f"visit-{inv.id}",
"is_self": inv.invitee_id == request.user.id,
})
while len(seats) < 6:
n = len(seats) + 1
seats.append({"n": n, "label": f"{n}C", "present": False, "token": ""})
# Read-only spectator render parity (Phase 1, 2026-05-29): the visitor's
# VIEW DRAW renders the SAME `.my-sea-cross` picker + `_sea_stage` the
# owner sees, populated from the owner's draw. `saved_by_position` fills
@@ -910,6 +932,7 @@ def my_sea_visit(request, owner_id):
"sea_invite": invite,
"seat1_present": owner_seated,
"seat2_present": invite.is_present,
"seats": seats,
"owner_draw_id": owner_draw.id if owner_draw is not None else "",
"voice_active": invite.voice_active,
"voice_room_id": f"mysea-{owner.id}",