feedback7 UIBACK: the match's top bar lists the spectator seats — a 观战席 capsule (eye + count) beside the latency opens the roster, the host removes a spectator with its ✕ while the match runs (PR #120 by @salt-fishes, the part the owner accepted: the cap stays 2, no per-room spectator option); the server already took room.removeSpectator at any time — test/match/lobby-integration.test.js now covers the host removing one mid-match (room.closed kicked, the other spectator and the host told, no frame after it, the seat refilled), test/ui/spectator-roster.e2e.test.js the capsule; one new UI string in the four packs; the dev mock seats spectators on ?variant=spectators; golden: unchanged (client only)

This commit is contained in:
sganggs committed 2026-10-08 19:19:09 +08:00
1 parent 7fb41f4ff6
commit d7c665dbf6
13 files changed
+273 -9

No files matched your search

+9 -1
View File
@@ -159,6 +159,12 @@ function MatchScreen() {
const emotes = useStore((s) => s.emotes);
const roomSolo = useStore((s) => s.room?.mode === 'solo');
const spectator = useStore((s) => isSpectating(s.room, s.me.playerId));
// the room's spectator seats for the top bar's 观战席 capsule (ui/hud.js SpectatorPill; GitHub #120): room.state keeps coming
// during the match, the list is replaced only when a seat changes — shallowEqual keeps an unrelated push from re-rendering
const specFacts = useStore((s) => ({
list: Array.isArray(s.room?.spectators) ? s.room.spectators : null,
isHost: !!s.room && s.room.hostId === s.me.playerId,
}), shallowEqual);
const gd = useGameData();
const hostRef = useRef(null);
@@ -717,6 +723,7 @@ function MatchScreen() {
// ---- actions ----------------------------------------------------------------------------------------------
const buy = useCallback((i) => actions.buy(i), []);
const removeSpectator = useCallback((playerId) => actions.removeSpectator(playerId), []);
// 准备 with funds left asks first: the prep's end wipes them (community report #4; not 坎诺特, not at 0 funds, not
// under AI 托管 — gameLogic.readyFundsPrompt). The button and Space both come here.
const askingReady = useRef(false);
@@ -1363,7 +1370,8 @@ function MatchScreen() {
readyBusy=${readyBusy} readyCount=${readyCount} playerCount=${solo ? 1 : aliveCount}
pen=${pen} penAvail=${penAvail} onPen=${togglePen} config=${gd.config} frozenAt=${frozenAt}
pause=${canPause || paused ? { show: canPause, paused, busy: pauseBusy, onToggle: () => togglePause(!paused) } : null}
live=${liveLpNow} spectator=${spectator} />
live=${liveLpNow} spectator=${spectator}
spectators=${specFacts.list} myId=${myId} isHost=${specFacts.isHost} onRemoveSpectator=${removeSpectator} />
<div class="gm__bonds">
<button type="button" class="bonds-toggle" aria-expanded=${!bondsCollapsed} aria-controls="match-bond-strip"