fix(offline): zero tile weights on cold boot + wrong-mode game in the lobby
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m29s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m29s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
Two pre-existing offline bugs, both exposed once offline mode became usable (cold boot + toggling), owner-reported on the contour. Bug 1 -- offline tiles render weight 0. The rack/board read tile values from the lib/alphabet cache, populated only by the online wire codec (server-sent alphabet) or the mock (so the e2e masked it). A local game never goes through the codec, so on a cold offline boot with no prior online session the cache was empty and every value read 0 (scores stayed correct -- they come from the offline ruleset). Fix: the local source seeds lib/alphabet from the static offline ruleset (letters + values) when it builds a game view. Bug 2 -- the lobby showed (and could open) the other mode's game after a toggle. Two causes: (a) the lobby cache was not mode-tagged, so getLobby() instant-rendered the last snapshot regardless of mode; (b) load() wrote the module list after an await with no generation guard, so a slow online gamesList() finishing after a fast offline list() (or vice versa) left the wrong mode's games on screen. Fix: tag the snapshot with offline and gate getLobby() on it; add a load-sequence guard so only the latest load() writes. - localgame/source.ts: ensureAlphabet from the ruleset in gameView (+ unit test). - lobbycache.ts: LobbySnapshot.offline + getLobby(offline) mode check (+ tests). - Lobby.svelte: setLobby tags the mode; load() carries a generation guard. check 0 / unit 485 / e2e 198 / app entry 113.8/114.
This commit is contained in:
@@ -34,35 +34,47 @@
|
||||
// operator feedback reply (the Settings → Info badge folds in here).
|
||||
const settingsBadge = $derived(app.notifications + (app.feedbackReplyUnread ? 1 : 0));
|
||||
|
||||
// A generation guard so only the LATEST load() writes the module lists. Three triggers (onMount, a
|
||||
// game event, the offline-mode flip) can overlap, and a slow online gamesList() completing after a
|
||||
// fast offline list() (or vice versa) would otherwise leave the other mode's games on screen — the
|
||||
// "wrong game in the lobby after a toggle" bug. A superseded run bails at the checks below.
|
||||
let loadSeq = 0;
|
||||
async function load() {
|
||||
const seq = ++loadSeq;
|
||||
// Deliberate offline mode: never touch the network. The lobby shows only the device-local
|
||||
// vs_ai games (reconstructed from the store) plus the New-vs-AI entry; there are no online
|
||||
// games, invitations or incoming requests to fetch.
|
||||
if (offlineMode.active) {
|
||||
games = await localSource.list();
|
||||
const local = await localSource.list();
|
||||
if (seq !== loadSeq) return;
|
||||
games = local;
|
||||
invitations = [];
|
||||
incoming = [];
|
||||
atGameLimit = false;
|
||||
app.notifications = 0;
|
||||
setLobby({ games, invitations, incoming, atGameLimit });
|
||||
setLobby({ games, invitations, incoming, atGameLimit, offline: offlineMode.active });
|
||||
app.lobbyReady = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const list = await gateway.gamesList();
|
||||
if (seq !== loadSeq) return;
|
||||
games = list.games;
|
||||
// Seed the per-game unread badges from the authoritative list. The live stream only raises
|
||||
// unread (it never seeds it from a GameView), so a persisted unread survives a reload here.
|
||||
for (const g of games) seedChatUnread(g.id, g.unreadChat, g.unreadMessages);
|
||||
atGameLimit = list.atGameLimit;
|
||||
if (!guest) {
|
||||
[invitations, incoming] = await Promise.all([gateway.invitationsList(), gateway.friendsIncoming()]);
|
||||
const [inv, inc] = await Promise.all([gateway.invitationsList(), gateway.friendsIncoming()]);
|
||||
if (seq !== loadSeq) return;
|
||||
invitations = inv;
|
||||
incoming = inc;
|
||||
// The ⚙️ badge counts incoming friend requests plus an awaiting feedback reply;
|
||||
// invitations surface in their own lobby section above.
|
||||
app.notifications = incoming.length;
|
||||
void refreshFeedbackBadge();
|
||||
}
|
||||
setLobby({ games, invitations, incoming, atGameLimit });
|
||||
setLobby({ games, invitations, incoming, atGameLimit, offline: offlineMode.active });
|
||||
// Warm the cache for the ongoing games so opening one from the lobby is instant. The list
|
||||
// just loaded, so the connection is up; the call is non-blocking and re-running it on each
|
||||
// lobby refresh cheaply warms any newly appeared game (already-cached ones are skipped).
|
||||
@@ -85,7 +97,7 @@
|
||||
onMount(() => {
|
||||
// Render instantly from the cached lists (so the screen does not "draw in" during
|
||||
// the back-slide), then refresh in the background.
|
||||
const cached = getLobby();
|
||||
const cached = getLobby(offlineMode.active);
|
||||
if (cached) {
|
||||
games = cached.games;
|
||||
invitations = cached.invitations;
|
||||
@@ -214,7 +226,7 @@
|
||||
revealedId = null;
|
||||
const prev = games;
|
||||
games = games.filter((g) => g.id !== id); // optimistic; the backend already filters it out
|
||||
setLobby({ games, invitations, incoming, atGameLimit });
|
||||
setLobby({ games, invitations, incoming, atGameLimit, offline: offlineMode.active });
|
||||
try {
|
||||
// A local (offline) game is deleted from the device store; an online game is hidden on the
|
||||
// backend. Routing by id keeps the delete off the network for a local game (the transport
|
||||
@@ -223,7 +235,7 @@
|
||||
else await gateway.hideGame(id);
|
||||
} catch (e) {
|
||||
games = prev;
|
||||
setLobby({ games, invitations, incoming, atGameLimit });
|
||||
setLobby({ games, invitations, incoming, atGameLimit, offline: offlineMode.active });
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user