feat: remove a saved game & redirect on missing/deleted game (#20)
CI / build + unit + e2e (pull_request) Successful in 1m14s

- DELETE /api/games/:id route + Db.delete() (404 when absent)
- PresetPicker: per-saved-game trash button + confirm dialog (Cancel/Delete)
- App: runDeleteGame; useSse onError -> goHome on 'deleted'/'not found'
  (a deleted/missing game now returns the live tab and any deep link home)
- DeleteGameResponse in shared/protocol.ts; api.deleteGame()
- unit test (Db.delete) + 2 e2e tests (home-screen delete; in-flight delete
  returns the active tab home and a fresh deep link lands on home)
- version 0.7.0 -> 0.8.0 + changelog

Fixes #20
This commit is contained in:
2026-10-11 20:32:55 +00:00
parent f05ddb304d
commit 17d6cecab9
11 changed files with 250 additions and 7 deletions
+30
View File
@@ -309,6 +309,12 @@ export default function App() {
useSse(game?.id ?? null, {
onState: applyState,
onTurn: applyState,
// The game disappeared while this tab had it open — either deleted from
// another tab (the server broadcasts an SSE `error`) or a stale deep link to
// a missing game (the SSE connect emits `error`). Return this tab home.
onError: (msg) => {
if (/deleted|not found/i.test(msg)) latestRef.current?.goHome();
},
});
const play = useCallback(async (presetId: string) => {
@@ -465,6 +471,29 @@ export default function App() {
setAdvisors([]);
}, []);
// Delete a saved game. Removes it from the "Saved games" list, clears it if it
// was the remembered active game, and heads home if it's the one currently
// shown. The server also broadcasts an SSE `error` to any *other* open tab of
// this game, which the `useSse` `onError` handler above turns into a go-home.
const runDeleteGame = useCallback(
async (id: string) => {
try {
await api.deleteGame(id);
} catch (e) {
toastError(e);
return;
}
setSavedGames((prev) => prev.filter((g) => g.id !== id));
if (activeGameId === id) {
clearActiveGameId();
setActiveGameId(null);
}
if (game?.id === id) goHome();
toast.success('Game deleted');
},
[activeGameId, game, goHome],
);
// Expose the latest game + handlers to the mount-once hashchange listener.
latestRef.current = { game, goHome, loadGame };
@@ -509,6 +538,7 @@ export default function App() {
savedGames={savedGames}
activeGameId={activeGameId}
onResume={resumeGame}
onDelete={runDeleteGame}
/>
</>
);