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
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.
This commit is contained in:
@@ -34,6 +34,9 @@ import type { BoardLabelMode } from './boardlabels';
|
||||
export interface Toast {
|
||||
kind: 'error' | 'info';
|
||||
text: string;
|
||||
/** A monotonic id bumped on every showToast, so the Toast component re-keys and replays its
|
||||
* entrance — the freshest toast cancels the previous one and starts its own appear cycle. */
|
||||
seq: number;
|
||||
}
|
||||
|
||||
export const app = $state<{
|
||||
@@ -95,6 +98,7 @@ export const app = $state<{
|
||||
let unsubscribeStream: (() => void) | null = null;
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let toastTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let toastSeq = 0;
|
||||
|
||||
// Background/foreground tracking, to silence the reconnect banner during a normal app
|
||||
// suspend (iOS lock / home, Telegram tab switch) and reconnect quietly on return.
|
||||
@@ -135,7 +139,7 @@ function goForeground(): void {
|
||||
}
|
||||
|
||||
export function showToast(text: string, kind: Toast['kind'] = 'info'): void {
|
||||
app.toast = { kind, text };
|
||||
app.toast = { kind, text, seq: ++toastSeq };
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => (app.toast = null), 4000);
|
||||
}
|
||||
@@ -223,7 +227,9 @@ function openStream(): void {
|
||||
showToast(e.message.kind === 'nudge' ? t('chat.nudge') : e.message.body, 'info');
|
||||
}
|
||||
} else if (e.kind === 'nudge') {
|
||||
showToast(t('chat.nudge'), 'info');
|
||||
// Name the nudger (their per-game seat name, carried on the event), so the toast reads
|
||||
// "<opponent>: …" like the your-turn toast; fall back to the plain phrase when absent.
|
||||
showToast(e.senderName ? t('chat.nudgeBy', { name: e.senderName }) : t('chat.nudge'), 'info');
|
||||
} else if (e.kind === 'your_turn') {
|
||||
// Name the player who moved just before this one (the previous seat in turn order), so the
|
||||
// toast reads the same in games with any number of players. Fall back to the bare label when
|
||||
|
||||
Reference in New Issue
Block a user