R4: push enrichment — events carry a state delta, kill the last poll #35

Merged
developer merged 1 commits from feature/r4-push-enrichment into development 2026-06-10 10:30:49 +00:00
Owner

Phase R4 of PRERELEASE.md (TODO 4 + 5). Enriches the in-app live stream into a delta channel so the UI renders a move from the event without a follow-up game.state, and makes the matchmaking poll a stream-down fallback.

What changed

  • Wire (pkg/fbs, trailing fields — backward-compatible): opponent_moved (+move/game/bag_len), your_turn (+move_count), match_found (+state), game_over (+game), notify (+account/invitation/state), MoveResult (+rack/bag_len).
  • Backend: notify keeps ownership of the FB encoding (new encode.go + payload.go input structs); game/lobby/social map their domain types in. emitMove builds the move delta; game.Service.InitialState feeds match_found/game_started the recipient's initial StateView; friends/invitations notify carry their account/invitation.
  • Move-commit response (submit_play/pass/exchange/resign) now returns the actor's refilled rack + bag size, so the mover renders the next turn without a self-refetch.
  • UI: a pure lib/gamedelta.ts reducer advances the per-game cache keyed on move_count (idempotent + gap-safe → fetch fallback); app.svelte seeds the cache on match_found/game_started; Game.svelte applies the delta (commit/pass/exchange/resign drop their load()); NewGame polls only while the stream is down.
  • Docs: ARCHITECTURE §10, FUNCTIONAL(+_ru), service READMEs; PRERELEASE R4 → done.

Notes for review

  • Actor-rack response enrichment is slightly beyond the literal "push events" scope but completes the "no per-move fetch" goal symmetrically for the mover — happy to split it out if you'd rather keep R4 to the stream.
  • notify (friends/invitations): the backend carries the full account/invitation payload (per "all events → push"); the UI seeds the game cache from game_started but keeps its lightweight authoritative badge refresh for the rare friend/invitation events rather than adding client-side lobby caches. The per-move hot path is fully de-fetched, which was the goal — deeper lobby-cache consumption is an easy follow-up.
  • No schema change (no migration) — the contour needs no DB wipe.

Verification

  • go build / vet / gofmt clean; go test -count=1 ./backend/... ./gateway/... + the integration suite + telegram — all green.
  • UI: pnpm check (0 errors), test:unit (138), test:e2e (72, Chromium + WebKit), build (76 KB gzip, under the 100 KB budget).
Phase **R4** of `PRERELEASE.md` (TODO 4 + 5). Enriches the in-app live stream into a **delta channel** so the UI renders a move from the event without a follow-up `game.state`, and makes the matchmaking poll a **stream-down fallback**. ## What changed - **Wire** (`pkg/fbs`, trailing fields — backward-compatible): `opponent_moved` (+`move`/`game`/`bag_len`), `your_turn` (+`move_count`), `match_found` (+`state`), `game_over` (+`game`), `notify` (+`account`/`invitation`/`state`), `MoveResult` (+`rack`/`bag_len`). - **Backend**: `notify` keeps ownership of the FB encoding (new `encode.go` + `payload.go` input structs); `game`/`lobby`/`social` map their domain types in. `emitMove` builds the move delta; `game.Service.InitialState` feeds `match_found`/`game_started` the recipient's initial `StateView`; friends/invitations `notify` carry their account/invitation. - **Move-commit response** (`submit_play`/`pass`/`exchange`/`resign`) now returns the actor's refilled rack + bag size, so the mover renders the next turn without a self-refetch. - **UI**: a pure `lib/gamedelta.ts` reducer advances the per-game cache keyed on `move_count` (idempotent + gap-safe → fetch fallback); `app.svelte` seeds the cache on `match_found`/`game_started`; `Game.svelte` applies the delta (`commit`/`pass`/`exchange`/`resign` drop their `load()`); `NewGame` polls only while the stream is down. - **Docs**: `ARCHITECTURE` §10, `FUNCTIONAL`(+`_ru`), service READMEs; `PRERELEASE` R4 → done. ## Notes for review - **Actor-rack response enrichment** is slightly beyond the literal "push events" scope but completes the "no per-move fetch" goal symmetrically for the mover — happy to split it out if you'd rather keep R4 to the stream. - **`notify` (friends/invitations)**: the backend carries the full account/invitation payload (per "all events → push"); the UI seeds the game cache from `game_started` but keeps its lightweight **authoritative** badge refresh for the rare friend/invitation events rather than adding client-side lobby caches. The per-move hot path is fully de-fetched, which was the goal — deeper lobby-cache consumption is an easy follow-up. - **No schema change** (no migration) — the contour needs no DB wipe. ## Verification - `go build` / `vet` / `gofmt` clean; `go test -count=1 ./backend/... ./gateway/...` + the **integration** suite + telegram — all green. - UI: `pnpm check` (0 errors), `test:unit` (138), `test:e2e` (72, Chromium + WebKit), `build` (76 KB gzip, under the 100 KB budget).
developer added 1 commit 2026-06-10 06:02:56 +00:00
R4: push enrichment — events carry a state delta, kill the last poll
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
41a642ef97
Enrich the in-app live stream into a delta channel so the UI renders a move from the event without a follow-up game.state, and make the matchmaking poll a stream-down fallback.

- pkg/fbs: trailing fields on opponent_moved (move+game+bag_len), your_turn (move_count), match_found (state), game_over (game), notify (account/invitation/state), MoveResult (rack+bag_len); regenerate Go + TS.
- backend: notify owns the FB encoding (encode.go + payload.go input structs); game/lobby/social map their domain types in. emitMove builds the move delta; game.Service.InitialState feeds match_found/game_started the recipient's initial StateView; friends/invitations notify carry their account/invitation. The move-commit response (submit_play/pass/exchange/resign) returns the actor's refilled rack + bag size.
- gateway: MoveResult transcode carries rack+bag_len.
- ui: pure lib/gamedelta.ts reducer advances the per-game cache keyed on move_count (idempotent + gap-safe); app.svelte seeds the cache on match_found/game_started; Game.svelte applies the delta (commit/pass/exchange/resign drop their load()); NewGame polls only while app.streamAlive is false.
- docs: ARCHITECTURE §10, FUNCTIONAL(+ru), backend/gateway/ui READMEs; PRERELEASE R4 marked done + Refinements.
owner approved these changes 2026-06-10 10:29:27 +00:00
developer merged commit 7ec17cdd53 into development 2026-06-10 10:30:49 +00:00
developer deleted branch feature/r4-push-enrichment 2026-06-10 10:30:49 +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#35