diff --git a/TODO.md b/TODO.md index bd1763b..aa0c2fa 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,7 @@ - [x] In the admin panel, if a pi-rat doesn't have a name yet, it should show something like 'not chosen yet' rather than the player name - [x] In the admin panel, the "Open" and "Copy" buttons should be the same size - [x] Store the player name in local storage and pre-populate it when joining a new game (but don't force it, let the player edit it if they'd prefer a different name) -- [ ] If a player tries to rejoin a game that has ended or that they've been kicked from, they should get an error message and it should be removed from the list of re-joinable games +- [x] If a player tries to rejoin a game that has ended or that they've been kicked from, they should get an error message and it should be removed from the list of re-joinable games - [ ] Games should have join codes in addition to join links. Join codes should be a sequence of 5 alphanumeric characters (upper case letters only, no ambiguous letters like 0/O/1/I). Add a panel to the home page for joining a game via code, and display the join code on the admin page ## Words Words Words diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index 8324f6c..bbc0983 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -17,7 +17,9 @@ export async function apiRequest(endpoint, method = 'GET', data = null) { if (errBody.error) msg = errBody.error; else if (errBody.detail) msg = errBody.detail; } catch(e) {} - throw new Error(msg); + const error = new Error(msg); + error.status = res.status; + throw error; } return res.json(); } diff --git a/frontend/src/lib/changelog.js b/frontend/src/lib/changelog.js index 3531cd5..27c3b98 100644 --- a/frontend/src/lib/changelog.js +++ b/frontend/src/lib/changelog.js @@ -6,10 +6,17 @@ // - Add a CHANGELOG entry only when a commit changes something players can // see. Skip refactors, tests, and tooling. Keep wording player-facing. -export const VERSION = 17; +export const VERSION = 18; // Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }. export const CHANGELOG = [ + { + version: 18, + date: '2026-07-10', + changes: [ + 'Expired rejoin entries are now removed automatically when a game has ended or a player is no longer in the crew, with a clear explanation.', + ], + }, { version: 17, date: '2026-07-10', diff --git a/frontend/src/pages/Dashboard.svelte b/frontend/src/pages/Dashboard.svelte index e4d0149..ed4412a 100644 --- a/frontend/src/pages/Dashboard.svelte +++ b/frontend/src/pages/Dashboard.svelte @@ -31,14 +31,35 @@ let reconnectTimer = null; let reconnectDelay = 1000; let destroyed = false; + let staleSession = false; + + function discardStaleSession(message) { + staleSession = true; + removeSession(gameId, playerId); + state = null; + error = message; + if (reconnectTimer) clearTimeout(reconnectTimer); + if (ws) ws.close(); + } async function fetchState() { + if (staleSession) return; try { const data = await apiRequest(`/game/${gameId}/player/${playerId}/state`); + // Preserve the Game Over screen for players who were already present + // when play ended, but discard stale home-screen rejoin attempts. + if (!state && data.game.phase === 'ended') { + discardStaleSession('This game has ended and can no longer be rejoined.'); + return; + } state = data; error = ''; } catch (err) { - error = err.message; + if (!state && err.status === 404) { + discardStaleSession('This saved game can no longer be rejoined. You may have been removed from the crew.'); + } else { + error = err.message; + } } } @@ -53,7 +74,7 @@ }; ws.onmessage = () => fetchState(); ws.onclose = () => { - if (destroyed) return; + if (destroyed || staleSession) return; reconnectTimer = setTimeout(connectSocket, reconnectDelay); reconnectDelay = Math.min(reconnectDelay * 2, 10000); }; @@ -188,6 +209,11 @@ {#if error}