post applet: unify header across post.html + reelhouse chat; seat-based recipients/access; fix async chip styling

Unifies the Post applet across post.html and the room game-views POST chat:

- Extract _post_recipient.html (the @handle chip: post-recipient + post-attribution) + _post_header.html (title + 'shared between … & me' / 'just me' prose). post.html's owner branch + the reelhouse POST view both render via _post_header; the invitee branch stays bespoke but reuses the chip.

- Async-chip styling bug (post.html): the bud-invite append built a bare <span class=post-recipient> with the raw display name, so the recipient rendered without the --quaUser key + the @ until a refresh. Now billboard:share_post returns recipient_chip_html (the server-rendered _post_recipient.html) and the bud panel splices THAT in — identical classes + @handle. Also fixed the 'just me'→'& me' flip to mutate only the leading text node so the self line's own .post-attribution span survives.

- Reelhouse POST chat: gains the full .post-header — title hardcoded to 'Gamer Introduction' (dynamic template later) + recipients = the gamers OCCUPYING SEATS (room.table_seats, deduped, viewer excluded), NOT gate-slot/token depositors. And room_post ACCESS now requires a TableSeat, not a filled gate slot: a depositor who never took a seat can retract + leave, so they must not have R/W access to the private chat.

Tests: header IT (seatmate listed, transient gate-slot-only depositor not — scoped to the recipients paragraph since the position strip carries every gate-slot handle elsewhere); room_post seat-access ITs (seated OK; non-seated + gate-slot-only → 403); share_post recipient_chip_html IT; carousel FT setUp now seats disco/amigo/bud (pal/dude/bro stay transient). All green: 255 ITs, 11 carousel FTs, 29 bud-btn/composer FTs.

[[project-room-game-views-carousel]] [[feedback-at-handle-for-usernames]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-02 16:34:28 -04:00
parent 73644e226b
commit b243d512e4
10 changed files with 158 additions and 44 deletions

View File

@@ -665,6 +665,19 @@ def room_view(request, room_id):
ctx["room_post"] = room_post
ctx["room_post_lines"] = room_post.lines.select_related("author").all()
ctx["text_btn_active"] = True
# The POST chat's participants are the gamers OCCUPYING SEATS (a TableSeat
# with their gamer) — NOT mere gate-slot/token depositors, who can retract
# their token + leave without ever committing to the game. `seated_others`
# excludes the viewer (who is the "& me" line); deduped, slot-ordered.
seen_seated = set()
seated_others = []
for seat in (room.table_seats.filter(gamer__isnull=False)
.exclude(gamer=request.user)
.select_related("gamer").order_by("slot_number")):
if seat.gamer_id not in seen_seated:
seen_seated.add(seat.gamer_id)
seated_others.append(seat.gamer)
ctx["seated_others"] = seated_others
return render(request, "apps/gameboard/room.html", ctx)
@@ -680,10 +693,10 @@ def room_post(request, room_id):
room = Room.objects.get(id=room_id)
if request.method != "POST":
return redirect("epic:room", room_id=room_id)
# Only gamers holding a filled seat at this table may post to its thread.
if not room.gate_slots.filter(
gamer=request.user, status=GateSlot.FILLED
).exists():
# Only gamers OCCUPYING A SEAT (a TableSeat with their gamer) may speak in
# the chat — NOT gate-slot/token depositors who haven't committed to a seat
# (they can retract + leave, so they must not have R/W access to the chat).
if not room.table_seats.filter(gamer=request.user).exists():
return HttpResponseForbidden()
post = room.get_thread_post()
form = ExistingPostLineForm(for_post=post, data=request.POST)