Commit Graph

7 Commits

Author SHA1 Message Date
Ilia Denisov 12d128f1cc feat(nudge): name the sender; blink lobby cards; re-animate toasts
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 16s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m21s
The "waiting for your move" popup was the nudge (chat.nudge), shown without a
sender name. Resolve the nudger's per-game seat name server-side and carry it on
a new NudgeEvent.sender_name field, so:

- the in-app toast reads "<opponent>: Waiting for your move 🤭" (chat.nudgeBy);
- the out-of-app Telegram push names the sender too (render nudgeBy);

falling back to the plain phrase when the name is absent (RU mirrored). The
your_turn toast already named the opponent and is unchanged.

Lobby: when a card transitions into "your turn" or "finished" while the lobby is
open, its status emoji blinks twice (two 1s fades); the opponent's-turn change
stays in place. Blink state is keyed by game id (SvelteSet + per-id nonce/timer)
so overlapping events animate in isolation; suppressed under reduce-motion.

Toast: a per-message seq re-keys Toast.svelte, so the freshest toast cancels the
previous one and replays its entrance, uniformly on every screen.

Tests: notify.Nudge round-trip + render named/fallback (Go), game.Service.SeatName
(integration), codec/i18n/gamePhase/shouldBlink (UI). Docs (FUNCTIONAL +_ru,
UI_DESIGN) + PLAN TODO-7 (deferred FLIP card-relocation animation) updated.
2026-06-16 19:09:24 +02:00
Ilia Denisov ac62d29ef7 feat(ui): name the previous player in the your-turn toast
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 46s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
The in-app "your turn" toast (shown when it becomes your turn in another
game) now reads "<Opponent>: Your turn!", naming the player who moved just
before this one — the previous seat in turn order, so it reads the same in
games with any number of players. Falls back to the bare "Your turn" label
when no name is present (an older peer, or a gap event without one).

YourTurnEvent already carries opponent_name end to end: the backend sets it
(game.displayName of the last mover) and the gateway forwards the payload
opaquely, so this is a client-only change — decode the field, thread it
through PushEvent, and pick the localized string.
2026-06-14 22:52:15 +02:00
Ilia Denisov e68fe61e39 UI: render move history as a per-seat column grid + swipe-down to open
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 42s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
Replace the flat chronological move list with a ruled matrix aligned under the
score plaque: one column per seat, each seat's moves filling its column top to
bottom. A cell is the move's word(s) and its score, "WORD (12)", centred; the
player names and the running total are dropped (the plaque heads the column and
shows the live total). Non-play moves keep their dim parenthesised tag; the
awaited opponent's next cell shows a dim "thinking..." (never the viewer's own
turn). Thin 1px rules between columns and rows match the panel's separator.

Re-introduce a swipe-down-on-the-board gesture to open the history, gated to the
zoom-out board scrolled to its top so it never fights the zoomed board's pan or
the stage's own vertical scroll (the conflict that retired this gesture before).

Grid layout extracted to lib/history.ts (unit-tested); add game.thinking to the
EN/RU catalogs; e2e covers the gesture and the grid on Chromium and WebKit.
2026-06-11 20:18:23 +02:00
Ilia Denisov b14cc38919 UI: render non-play history moves as a dim lowercase tag
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 40s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
Per owner feedback: pass/exchange/resign/timeout rows in the move history now read
as a dim, parenthesised, lowercase tag — e.g. «(обмен)» — so they stand apart from a
scored word. The move.* catalog values are lowercased (resign RU → «сдаюсь»); the
parentheses and the muted colour (var(--text-muted)) are applied in the view via a
.ha.sys modifier.
2026-06-11 19:23:55 +02:00
Ilia Denisov ac29dca865 UI: fix last-move highlight, localize move history, clamp zoom overscroll
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 41s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
- Highlight tracks the last move overall (not the last word): a trailing
  pass/exchange now highlights nothing, so the board no longer lights up the
  opponent's old word after our own empty move.
- Make the highlight event-driven: refreshed only on a real game event
  (open/refresh, opponent move, our own committed move) and dismissed the moment
  composing starts, so recalling a just-placed tile never re-triggers it.
- Localize non-play move-history labels via new move.* catalog keys
  (pass/exchange/resign/timeout); the label printed the raw English action.
- Clamp the zoomed board's pan at its edge (overscroll-behavior: none), removing
  the native rubber-band past the content.

Tests: lastMoveCells unit coverage (trailing pass/exchange -> empty), i18n RU
label assertions, an e2e overscroll-contract check on the zoomed viewport.
2026-06-11 18:50:10 +02:00
Ilia Denisov b15fd30c4f Stage 17 (contour round 4a): quick fixes
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s
- #4 bag label: '{n} in the bag' / 'Bag is empty' (was 'Bag {n}')
- #6 allow a single trailing dot in display names (backend + UI regex + tests)
- #1 double-tap zooms toward the tapped cell, not the top-left
- #8 shuffle fires a short multi-pulse haptic
- #11 highlighted/flashing tiles darken their bottom edge too (shadow joins the flash)
- #13 toast slides up from the bottom and fades out
- #7 hide the logout button (kept wired behind `hidden`)
- #16 admin game seats: left-align numeric columns, clarify the 'Hints used' header
2026-06-06 14:08:40 +02:00
Ilia Denisov 0284c9b83a Stage 7 (wip): tests + UI CI
- Vitest units: board replay, placement machine, premium parity, i18n key parity, FlatBuffers codec round-trips (19 tests)
- Playwright smoke (mock transport): guest -> lobby -> board -> place tile -> preview
- ui-test.yaml workflow: check/unit/build + bundle-size budget (67.5KB gzip < 100KB) + chromium e2e
- gateway transcode tests for games.list (seat display_name), pass, hint
- backend integration test for game.ListForAccount
2026-06-03 00:55:38 +02:00