Phase 2: async play-by-turn — the games ledger and turn signals
The lobby is now the front door to all your games. A "your games" ledger lists every seat this browser holds — room code, whose turn it is (including counteraction/discard/interrupt waits, which count as your turn), the round, and how long since the last move — with one-click resume, a forget control, and a green highlight when a game waits on you. The server answers a token-validated myGames query with per-seat summaries; the client polls every 45 seconds, so turns in other rooms reach you wherever you are. Turn signals travel three ways: the tab title flips to "● Your turn", the favicon grows a green dot, and — opt-in via "notify me on my turn" — a browser notification fires when a turn becomes yours anywhere. Landing in the app now shows the ledger rather than teleporting into the last game; mid-game socket drops still walk straight back to the table. Verified live: two seeded games, ledger showing "gandalf's turn" and YOUR TURN, one-click resume into the correct game with history. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7cfffb8ab5
commit
bc8013863b
@@ -140,6 +140,39 @@ export function runCommand(
|
||||
return { events: result.events };
|
||||
}
|
||||
|
||||
export interface GameSummary {
|
||||
roomId: string;
|
||||
name: PlayerId;
|
||||
players: PlayerId[];
|
||||
started: boolean;
|
||||
finished: boolean;
|
||||
winner: PlayerId | null;
|
||||
activePlayerId: PlayerId | null;
|
||||
yourTurn: boolean;
|
||||
round: number | null;
|
||||
lastMoveAt: string | null;
|
||||
}
|
||||
|
||||
/** A seat-holder's one-line view of a room, for the lobby ledger. */
|
||||
export function summarize(room: Room, playerId: PlayerId): GameSummary {
|
||||
const s = room.state;
|
||||
const active = s && s.phase === "playing" ? s.players[s.turn.activeIndex]!.id : null;
|
||||
const waitingOn = s?.stack?.waitingOn ?? s?.pendingDiscard ?? s?.outOfTurnWindow?.playerId ?? null;
|
||||
const turnHolder = waitingOn ?? active;
|
||||
return {
|
||||
roomId: room.id,
|
||||
name: playerId,
|
||||
players: [...room.players],
|
||||
started: s !== null,
|
||||
finished: s?.phase === "finished",
|
||||
winner: s?.winner ?? null,
|
||||
activePlayerId: active,
|
||||
yourTurn: s?.phase === "playing" && turnHolder === playerId,
|
||||
round: s?.turn.round ?? null,
|
||||
lastMoveAt: room.log.length > 0 ? room.log[room.log.length - 1]!.at : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function viewForPlayer(room: Room, playerId: PlayerId): GameView | null {
|
||||
return room.state ? viewFor(room.state, playerId) : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user