diff --git a/ui/e2e/social.spec.ts b/ui/e2e/social.spec.ts index 6744c15..64185ea 100644 --- a/ui/e2e/social.spec.ts +++ b/ui/e2e/social.spec.ts @@ -116,6 +116,9 @@ test('finished game draws an inert footer and trims live-only controls', async ( await page.getByRole('button', { name: 'Chat' }).click(); // 💬 await expect(page.locator('.pane')).toHaveCount(1); await expect(page.getByRole('button', { name: 'Dictionary' })).toHaveCount(0); + // A finished game is read-only: neither the message field nor the nudge is offered. + await expect(page.getByRole('button', { name: 'Send' })).toHaveCount(0); + await expect(page.getByRole('button', { name: 'Nudge' })).toHaveCount(0); }); test('lobby: hiding a finished game removes it (kebab → ❌), keeping the others', async ({ page }) => { diff --git a/ui/src/game/Chat.svelte b/ui/src/game/Chat.svelte index 1d88146..1b47305 100644 --- a/ui/src/game/Chat.svelte +++ b/ui/src/game/Chat.svelte @@ -9,6 +9,7 @@ busy, myTurn = false, canSend = false, + canNudge = false, waiting = false, nudgeOnCooldown = false, onsend, @@ -17,16 +18,18 @@ messages: ChatMessage[]; myId: string; busy: boolean; - // The input area has three turn-driven states: on your own turn the message field + - // send show while you may still post (canSend); once the one allowed message is sent a - // short caption replaces them until the next turn; on the opponent's turn only the - // nudge button shows (nudging your own turn makes no sense — there is no one to hurry). - // While the hourly nudge cooldown is active the nudge is disabled with an "awaiting - // reply" caption. + // The input area is turn-driven, in priority order: while you may still post (canSend) + // the message field + send show; once your one allowed message is sent a short caption + // replaces them until the next turn (myTurn); on the opponent's turn of an active game + // the nudge button shows (canNudge) — disabled with an "awaiting reply" caption while the + // hourly cooldown is active; a finished game offers nothing (it is read-only). myTurn?: boolean; // canSend is true while the player may still post this turn (own turn and the per-turn // limit not yet reached); it gates the message field. canSend?: boolean; + // canNudge is true on the opponent's turn of an active game; it gates the nudge button + // (nudging your own turn or a finished game makes no sense). + canNudge?: boolean; // waiting is true while an auto-match game still has no opponent: both send and nudge // are disabled (there is no one to message or hurry yet). waiting?: boolean; @@ -71,7 +74,7 @@ {t('chat.sentThisTurn')} - {:else} + {:else if canNudge} {nudgeOnCooldown ? t('chat.awaitingReply') : ''} diff --git a/ui/src/game/ChatScreen.svelte b/ui/src/game/ChatScreen.svelte index ba5a527..551d045 100644 --- a/ui/src/game/ChatScreen.svelte +++ b/ui/src/game/ChatScreen.svelte @@ -26,6 +26,9 @@ // the turn advances. canSend hides the field once the limit is reached (and always on // the opponent's turn). const canSend = $derived(canSendChat(isMyTurn, view ? sentThisTurn(messages, myId, view.game.lastActivityUnix) : 0)); + // The nudge is offered only on the opponent's turn of an active game (you hurry the player + // who is to move). A finished game is read-only: neither chat nor nudge is offered. + const canNudge = $derived(!!view && view.game.status === 'active' && view.game.toMove !== view.seat); // While the auto-match game still has no opponent, chat and nudge are both disabled. const waiting = $derived(!!view && view.game.status === 'open'); const nudgeCooldownSecs = 3600; @@ -110,4 +113,4 @@ } - +