feat(lobby): keep lobby/game caches fresh from any screen + invitation delta channel
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
Builds on the cross-screen cache work: the global stream handler now keeps both caches current no matter which screen is mounted, and invitations become a live delta channel so the lobby's invitations list is fresh from any screen too. Client (boundary already started): - advanceCached now also folds opponent_joined into a not-currently-viewed game's cache via a new pure reducer applyOpponentJoined (extracted and reused by the mounted game board), so opening an open game that filled while you were elsewhere is flash-free. - patchLobbyInvitation upserts a still-pending invitation and removes a terminal one (started/declined/cancelled/expired); the global notify handler calls it on the invitation / invitation_update sub-kinds. Invitations delta channel (no wire/gateway/connector change — the notification already carries the full invitation with id/status/game_id end to end): - notify: a new in-app-only NotifyInvitationUpdate sub + NotificationInvitationUpdate constructor (shares encoding with NotificationInvitation). The Telegram connector renders no message for it, so a decline/cancel never becomes an out-of-app push. - lobby: emit the changed invitation to every participant on respond (accept/decline), on the final accept's game start, and on cancel — so each participant's lobby patches its list in place. The authoritative list holds only pending invitations, so the client's pending-vs-terminal rule matches it exactly. Tests: applyOpponentJoined + patchLobbyInvitation unit tests (TDD), the NotificationInvitationUpdate encoding unit test, and integration assertions that decline/cancel/accept publish invitation_update to every participant. Full local suite green (backend unit+integration, UI check/unit/build/bundle/e2e). Docs: ARCHITECTURE §10 (notify catalog + in-app-only note) and UI_DESIGN updated.
This commit is contained in:
@@ -27,7 +27,7 @@ import { connection, reportOffline, reportOnline, resetConnection } from './conn
|
||||
import { isConnectionCode } from './retry';
|
||||
import { clearGameCache, getCachedGame, setCachedGame } from './gamecache';
|
||||
import { advanceCached } from './gamedelta';
|
||||
import { clearLobby, patchLobbyGame } from './lobbycache';
|
||||
import { clearLobby, patchLobbyGame, patchLobbyInvitation } from './lobbycache';
|
||||
import type { BoardLabelMode } from './boardlabels';
|
||||
|
||||
export interface Toast {
|
||||
@@ -191,9 +191,16 @@ function openStream(): void {
|
||||
if (c) setCachedGame(e.gameId, c.view, c.moves);
|
||||
}
|
||||
} else if (e.kind === 'opponent_joined') {
|
||||
// The opponent took an open game's empty seat: mirror the new status into the lobby snapshot
|
||||
// from any screen. A mounted game board adopts the change in place itself (game/Game.svelte).
|
||||
if (e.state) patchLobbyGame(e.state.game);
|
||||
// The opponent took an open game's empty seat: mirror the new status into the lobby snapshot,
|
||||
// and warm a not-currently-viewed game's own cache, so opening it from any screen is flash-free.
|
||||
// The mounted game board owns its cache (game/Game.svelte), so skip the game in view.
|
||||
if (e.state) {
|
||||
patchLobbyGame(e.state.game);
|
||||
if (!viewingGame(e.gameId)) {
|
||||
const c = advanceCached(getCachedGame(e.gameId), e);
|
||||
if (c) setCachedGame(e.gameId, c.view, c.moves);
|
||||
}
|
||||
}
|
||||
} else if (e.kind === 'match_found') {
|
||||
// Seed both caches from the event's initial state so the game renders instantly on arrival
|
||||
// and the new game is already in the lobby on a later return, then navigate.
|
||||
@@ -209,6 +216,12 @@ function openStream(): void {
|
||||
setCachedGame(e.state.game.id, e.state, []);
|
||||
patchLobbyGame(e.state.game);
|
||||
}
|
||||
// An invitation created / updated / withdrawn elsewhere: keep the lobby's invitations list
|
||||
// fresh from any screen — a new pending one is added, a consumed (started), declined,
|
||||
// cancelled or expired one is removed (see patchLobbyInvitation).
|
||||
if ((e.sub === 'invitation' || e.sub === 'invitation_update') && e.invitation) {
|
||||
patchLobbyInvitation(e.invitation);
|
||||
}
|
||||
void refreshNotifications();
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user