Play hub: sync game status at the hub, not only on game-open
Reported sync gaps: the hub showed "Play" until you opened each game, and a game synced only if you'd reopened it on that device (so a desktop win that was never reopened never reached the server). Root cause: the /play hub read card status from localStorage only and never talked to the server — sync happened exclusively inside the game components on mount. Now the hub itself reconciles every game (word 5/6 + wordsearch small/med/large) with the server on load (signed-in): pushes this device's local state and writes the merged result back to localStorage, then refreshes the cards. So statuses appear cross-device WITHOUT opening each game, and local progress uploads even for games not reopened. Word Search card status derived from the (completion- gated) ms. 237 pytest + 11 vitest green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
import { goto, afterNavigate } from '$app/navigation';
|
import { goto, afterNavigate } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { getJSON } from '$lib/api.js';
|
import { getJSON } from '$lib/api.js';
|
||||||
|
import { pushGameState } from '$lib/gamesync.js';
|
||||||
|
import { auth } from '$lib/auth.svelte.js';
|
||||||
import WordGame from '$lib/components/WordGame.svelte';
|
import WordGame from '$lib/components/WordGame.svelte';
|
||||||
import WordSearchGame from '$lib/components/WordSearchGame.svelte';
|
import WordSearchGame from '$lib/components/WordSearchGame.svelte';
|
||||||
|
|
||||||
@@ -50,6 +52,29 @@
|
|||||||
return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
|
return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The hub itself reconciles every game with the server (signed-in), so cards
|
||||||
|
// show cross-device status WITHOUT having to open each game first, and this
|
||||||
|
// device's local progress gets uploaded even for games it hasn't reopened.
|
||||||
|
async function syncOne(g, v, key) {
|
||||||
|
let local = null;
|
||||||
|
try { local = JSON.parse(localStorage.getItem(key) || 'null'); } catch { /* ignore */ }
|
||||||
|
const merged = await pushGameState(g, v, date, local || {});
|
||||||
|
if (!merged) return;
|
||||||
|
if (g === 'wordsearch') merged.status = merged.ms ? 'done' : 'playing'; // card reads .status
|
||||||
|
try { localStorage.setItem(key, JSON.stringify(merged)); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
async function syncAllGames() {
|
||||||
|
if (!auth.user || !date) return;
|
||||||
|
await Promise.allSettled([
|
||||||
|
syncOne('word', '5', `goodnews:word:5:${date}`),
|
||||||
|
syncOne('word', '6', `goodnews:word:6:${date}`),
|
||||||
|
syncOne('wordsearch', 'small', `goodnews:wordsearch:small:${date}`),
|
||||||
|
syncOne('wordsearch', 'med', `goodnews:wordsearch:med:${date}`),
|
||||||
|
syncOne('wordsearch', 'large', `goodnews:wordsearch:large:${date}`),
|
||||||
|
]);
|
||||||
|
refreshStatus();
|
||||||
|
}
|
||||||
|
|
||||||
// Hub card one-liners
|
// Hub card one-liners
|
||||||
function wordLabel() {
|
function wordLabel() {
|
||||||
const a = wordStatus['5'], b = wordStatus['6'];
|
const a = wordStatus['5'], b = wordStatus['6'];
|
||||||
@@ -123,6 +148,7 @@
|
|||||||
try { date = (await getJSON('/api/puzzle/word?variant=5')).date; } catch { /* offline */ }
|
try { date = (await getJSON('/api/puzzle/word?variant=5')).date; } catch { /* offline */ }
|
||||||
try { wsTheme = (await getJSON('/api/puzzle/wordsearch?variant=med')).theme; } catch { /* offline */ }
|
try { wsTheme = (await getJSON('/api/puzzle/wordsearch?variant=med')).theme; } catch { /* offline */ }
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
|
syncAllGames(); // signed-in: pull cross-device status into the cards + upload local progress
|
||||||
});
|
});
|
||||||
// Refresh hub/selection statuses whenever we land on a screen (incl. Back).
|
// Refresh hub/selection statuses whenever we land on a screen (incl. Back).
|
||||||
afterNavigate(() => refreshStatus());
|
afterNavigate(() => refreshStatus());
|
||||||
|
|||||||
Reference in New Issue
Block a user