feat(ui): explicit offline state inside an online game (+ offline word check)
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Failing after 12s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Failing after 1s
CI / deploy (pull_request) Has been skipped

When an online game loses the connection the game screen now says so and freezes
instead of silently greying out: a "connection lost" banner appears and the rack,
the move controls, the add-friend/block controls and the chat/dictionary entry all
disable (a started move stays a draft, committed by the player on reconnect). It is
driven off the net-state machine (netState.offline), so it also covers the
Telegram/VK mini-apps, where a lost connection was previously mute in-game.

If the player is already in the dictionary when the drop happens, the word check
falls back to the game's pinned on-device dictionary (exact when that dawg is
cached; "unavailable offline" otherwise) and the network-only complaint + external
look-up hide. Chat, when already open, keeps its existing read-only degrade
(send/nudge disable). New pure helper localWordCheck is unit-tested; the in-game
gating gets an e2e.
This commit is contained in:
Ilia Denisov
2026-07-14 08:53:18 +02:00
parent c34e2a8dbf
commit 28afcff551
11 changed files with 212 additions and 11 deletions
+11 -4
View File
@@ -118,10 +118,17 @@ dropped). Horizontal scaling is explicit future work.
one whose response was lost — its button is disabled while offline and re-issued on reconnect). A
reachability watcher (a lightweight `profile.get` probe; a session-less native guest reconciles a
server guest instead — that reconcile IS the probe) drives recovery, and the live `Subscribe` stream's
drop/recovery feeds the same machine. **Telegram/VK are exempt** — always online: they are never fed an
offline signal, and `offlineMode.active` is additionally hard-gated on `offlineCapable()` (false in
those mini-apps), so nothing — not even a version lock — puts them in offline mode: no blue chrome, no
local lobby, no transport kill switch and no device-local create paths.
drop/recovery feeds the same machine. **Telegram/VK are exempt from the offline-*play* model**
`offlineMode.active` is hard-gated on `offlineCapable()` (false in those mini-apps), so nothing — not
even a version lock — puts them in offline mode: no blue chrome, no local lobby, no transport kill
switch and no device-local create paths. The machine still tracks reachability there, though: a
connection lost **inside an online game** is surfaced on **every platform**, driven off the machine's
confirmed-offline state (`netState.offline`, not `offlineMode.active`) — the game screen shows a
*connection lost* banner and **freezes** the tray, the move controls, the add-friend/block controls and
the chat/dictionary entry (a started move stays a draft, re-committed by the player on reconnect), and
the word-check tool falls back to the game's pinned on-device dictionary (`ui/src/lib/dict/check.ts`,
exact when that dawg is cached, else *unavailable*) with its network-only complaint and external
look-up hidden.
**Edge hardening:** every request body on the public listener is capped at
`GATEWAY_MAX_BODY_BYTES` (default 1 MiB — far above any legitimate payload), both at the HTTP
layer (`http.MaxBytesReader`) and as the Connect per-message read limit, so an oversized