From c4337e3a7b03238d04a138a402005a07342b9168 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sun, 23 Aug 2026 20:57:55 -0400 Subject: [PATCH] The nameless viewer may watch the whole tale too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Whole-game share links minted fine and then 404ed: the share page rebuilds through catchUpSteps, whose seat check never learned the SPECTATOR exemption momentSteps got — the anonymous viewer held no seat, so the rebuild refused and the page said "no such replay". The spectator is now admitted to finished games' full replays, public knowledge only, same as single turns. Co-Authored-By: Claude Fable 5 --- packages/server/src/rooms.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index 651000c..b284009 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -384,7 +384,10 @@ export interface CatchUpStep { */ export function catchUpSteps(room: Room, playerId: PlayerId, sinceSeq: number, full = false): CatchUpStep[] | { error: string } { if (!room.state) return { error: "game not started" }; - if (!room.players.includes(playerId)) return { error: "you hold no seat in this room" }; + // The SPECTATOR builds whole-game share pages: public knowledge, no seat. + if (playerId !== SPECTATOR && !room.players.includes(playerId)) { + return { error: "you hold no seat in this room" }; + } if (full && room.state.phase !== "finished") return { error: "full replays wait for the game to finish" }; const MAX_STEPS = 200; const from = full ? 0 : Math.max(sinceSeq, room.log.length - MAX_STEPS);