The per-screen in-memory caches (lobbycache, gamecache) were refreshed only by
the screen that owns them while it was mounted, so a state change that crossed
screens left the other screen's cache stale and it visibly redrew on the next
navigation:
- game -> lobby: the player's own move advanced the game cache but not the lobby
snapshot, and an own move carries no self-directed push event, so returning to
the lobby painted the pre-move status until the background refresh corrected it.
- lobby -> game (and from any other screen): an opponent's move / game-over
refreshed the lobby (while mounted) but never the per-game cache, so opening
that game flashed the pre-move board.
Make cache freshness cross-screen, owned by the single global stream handler
that runs for every live event regardless of the mounted screen:
- patchLobbyGame upserts the affected game's GameView into the lobby snapshot;
the global handler calls it on opponent_moved / game_over / opponent_joined and
on a match_found / game_started seed (so a game started elsewhere is present
too). The game board still mirrors the player's own move and its own load() —
the two updates no live event carries.
- advanceCached (a pure wrapper over the existing delta reducers) advances a
not-currently-viewed game's cache from opponent_moved / game_over; the game in
view is skipped so its mounted board stays the sole owner (no double apply).
End-state behaviour is unchanged (the background refresh always reconciled);
this removes the transient stale frame. Unit-tested patchLobbyGame and
advanceCached; docs/UI_DESIGN.md updated.