fix(ui): poll/refetch fallback for a missed opponent_joined in the open-game wait #54

Merged
developer merged 2 commits from feature/open-game-wait-poll-fallback into development 2026-06-13 22:21:03 +00:00
Owner

Что и зачем

Закрываю регрессию из PR #51. Там ожидание соперника в авто-матче переехало с экрана матчмейкинга внутрь самой партии (status='open'), но poll-фоллбэк того экрана с собой не уехал.

Шина событий (notify.Hub) — best-effort: роняет событие при переполненном буфере и никогда не переотправляет; переподписка стрима не шлёт курсор; а Game.svelte обновлялся только из push. В отличие от opponent_moved/game_over, у opponent_joined нет последующего события, которое запустило бы догоночный refetch по moveCount. Итог: если opponent_joined потерян, пока стрим был недоступен (типичный кейс — уход приложения в фон на мобильном), стартер залипал на «ищу соперника» с пустой карточкой и заблокированными 🏁/чатом — пока вручную не выйдет и не зайдёт в игру заново.

Как починено (вариант A+B)

В Game.svelte:

  • (A) при переподключении стрима (streamAlive false→true) — один load() для догона пропущенного (заодно спасает пропущенные opponent_moved/game_over; покрывает частый suspend/resume на мобильном);
  • (B) пока ждём соперника и стрим недоступен — poll game.state каждые 2.5 с (повторяет удалённый startPoll из лобби); как только место занято — таймер сам гасится.

Плюс: mock-seam window.__stream для e2e, два изолированных e2e-теста (один проверяет именно poll, другой — refetch на reconnect), правка устаревшего комментария к streamAlive, запись в ARCHITECTURE.md §10.

Проверка локально

pnpm check (0 ошибок) · test:unit (164) · build · bundle-size gate · e2e 112 passed (chromium + webkit). Тайминги тестов подтверждают изоляцию: poll-тест ~4.6 с (ждёт тик 2.5 с), reconnect-тест ~1.9 с (мгновенный refetch до тика).

Go не трогал.

## Что и зачем Закрываю регрессию из PR #51. Там ожидание соперника в авто-матче переехало с экрана матчмейкинга внутрь самой партии (`status='open'`), но **poll-фоллбэк** того экрана с собой не уехал. Шина событий (`notify.Hub`) — best-effort: роняет событие при переполненном буфере и **никогда не переотправляет**; переподписка стрима не шлёт курсор; а `Game.svelte` обновлялся **только из push**. В отличие от `opponent_moved`/`game_over`, у `opponent_joined` нет последующего события, которое запустило бы догоночный refetch по `moveCount`. Итог: если `opponent_joined` потерян, пока стрим был недоступен (типичный кейс — уход приложения в фон на мобильном), стартер залипал на «ищу соперника» с пустой карточкой и заблокированными 🏁/чатом — пока вручную не выйдет и не зайдёт в игру заново. ## Как починено (вариант A+B) В `Game.svelte`: - **(A)** при переподключении стрима (`streamAlive` false→true) — один `load()` для догона пропущенного (заодно спасает пропущенные `opponent_moved`/`game_over`; покрывает частый suspend/resume на мобильном); - **(B)** пока ждём соперника и стрим недоступен — poll `game.state` каждые 2.5 с (повторяет удалённый `startPoll` из лобби); как только место занято — таймер сам гасится. Плюс: mock-seam `window.__stream` для e2e, два изолированных e2e-теста (один проверяет именно poll, другой — refetch на reconnect), правка устаревшего комментария к `streamAlive`, запись в `ARCHITECTURE.md §10`. ## Проверка локально `pnpm check` (0 ошибок) · `test:unit` (164) · `build` · bundle-size gate · **e2e 112 passed** (chromium + webkit). Тайминги тестов подтверждают изоляцию: poll-тест ~4.6 с (ждёт тик 2.5 с), reconnect-тест ~1.9 с (мгновенный refetch до тика). Go не трогал.
developer added 1 commit 2026-06-13 22:03:18 +00:00
fix(ui): poll/refetch fallback for a missed opponent_joined in the open-game wait
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
16402e64c0
PR #51 moved the auto-match "wait for an opponent" from the lobby matchmaking
screen into the open game but did not carry over that screen's poll fallback.
The notify hub is best-effort and never replays, the live-stream resubscribe
sends no cursor, and the game screen refreshed only from push events — so an
opponent_joined dropped while the stream was down (e.g. a mobile suspend) left
the starter stuck on "searching for opponent" until they re-entered the game.
Unlike opponent_moved/game_over, opponent_joined has no follow-up event to
trigger the existing move-count gap refetch.

Recover it in Game.svelte: (A) refetch once on stream reconnect (covers the
common suspend/resume case and rescues a missed move/game_over too), and
(B) poll game.state every 2.5s while still waiting with the stream down
(mirrors the old matchmaking startPoll). Add a mock-mode __stream e2e seam and
two specs isolating each path, fix the now-stale streamAlive comment, and
document the fallback in ARCHITECTURE §10.
owner approved these changes 2026-06-13 22:05:21 +00:00
Dismissed
developer added 1 commit 2026-06-13 22:14:50 +00:00
fix(ui): also resync an open game on a foreground regain without a stream drop
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 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 59s
4409253dce
Closes the residual tail of the previous commit: when the live stream stays
alive across a brief suspend (Telegram/iOS can pause the socket without tearing
it down), an in-game event shed from a full hub buffer is never recovered by
the reconnect refetch (no reconnect) or the open-game poll (it only runs while
the stream is down). Mirror the lobby's focus re-poll: bump app.resync on a
foreground regain that did not drop the stream, and have Game.svelte refetch
the open game once per resync. Also rescues a missed move/game_over after a
suspend. Add a silent-join mock seam + an e2e isolating this path; extend the
ARCHITECTURE §10 fallback note.
developer dismissed owner's review 2026-06-13 22:14:50 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

owner approved these changes 2026-06-13 22:19:46 +00:00
developer merged commit 38fa104f7f into development 2026-06-13 22:21:03 +00:00
developer deleted branch feature/open-game-wait-poll-fallback 2026-06-13 22:21:03 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#54