Promote development → master (initial production release: pre-release line + Stage 18) #104

Merged
developer merged 307 commits from development into master 2026-06-22 05:05:48 +00:00
3 changed files with 17 additions and 8 deletions
Showing only changes of commit 5f53ec81b9 - Show all commits
+3
View File
@@ -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 page.getByRole('button', { name: 'Chat' }).click(); // 💬
await expect(page.locator('.pane')).toHaveCount(1); await expect(page.locator('.pane')).toHaveCount(1);
await expect(page.getByRole('button', { name: 'Dictionary' })).toHaveCount(0); 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 }) => { test('lobby: hiding a finished game removes it (kebab → ❌), keeping the others', async ({ page }) => {
+10 -7
View File
@@ -9,6 +9,7 @@
busy, busy,
myTurn = false, myTurn = false,
canSend = false, canSend = false,
canNudge = false,
waiting = false, waiting = false,
nudgeOnCooldown = false, nudgeOnCooldown = false,
onsend, onsend,
@@ -17,16 +18,18 @@
messages: ChatMessage[]; messages: ChatMessage[];
myId: string; myId: string;
busy: boolean; busy: boolean;
// The input area has three turn-driven states: on your own turn the message field + // The input area is turn-driven, in priority order: while you may still post (canSend)
// send show while you may still post (canSend); once the one allowed message is sent a // the message field + send show; once your one allowed message is sent a short caption
// short caption replaces them until the next turn; on the opponent's turn only the // replaces them until the next turn (myTurn); on the opponent's turn of an active game
// nudge button shows (nudging your own turn makes no sense — there is no one to hurry). // the nudge button shows (canNudge) — disabled with an "awaiting reply" caption while the
// While the hourly nudge cooldown is active the nudge is disabled with an "awaiting // hourly cooldown is active; a finished game offers nothing (it is read-only).
// reply" caption.
myTurn?: boolean; myTurn?: boolean;
// canSend is true while the player may still post this turn (own turn and the per-turn // 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. // limit not yet reached); it gates the message field.
canSend?: boolean; 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 // 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). // are disabled (there is no one to message or hurry yet).
waiting?: boolean; waiting?: boolean;
@@ -71,7 +74,7 @@
<!-- Own turn, the one allowed message already sent: a caption holds the row until the <!-- Own turn, the one allowed message already sent: a caption holds the row until the
next turn (no nudge — there is no one to hurry on your own turn). --> next turn (no nudge — there is no one to hurry on your own turn). -->
<span class="cooldown">{t('chat.sentThisTurn')}</span> <span class="cooldown">{t('chat.sentThisTurn')}</span>
{:else} {:else if canNudge}
<!-- A flex:1 caption keeps the nudge pinned right whether or not the cooldown text shows. --> <!-- A flex:1 caption keeps the nudge pinned right whether or not the cooldown text shows. -->
<span class="cooldown">{nudgeOnCooldown ? t('chat.awaitingReply') : ''}</span> <span class="cooldown">{nudgeOnCooldown ? t('chat.awaitingReply') : ''}</span>
<button class="iconbtn" onclick={onnudge} disabled={busy || waiting || nudgeOnCooldown || !connection.online} aria-label={t('chat.nudgeAction')}>🛎️</button> <button class="iconbtn" onclick={onnudge} disabled={busy || waiting || nudgeOnCooldown || !connection.online} aria-label={t('chat.nudgeAction')}>🛎️</button>
+4 -1
View File
@@ -26,6 +26,9 @@
// the turn advances. canSend hides the field once the limit is reached (and always on // the turn advances. canSend hides the field once the limit is reached (and always on
// the opponent's turn). // the opponent's turn).
const canSend = $derived(canSendChat(isMyTurn, view ? sentThisTurn(messages, myId, view.game.lastActivityUnix) : 0)); 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. // While the auto-match game still has no opponent, chat and nudge are both disabled.
const waiting = $derived(!!view && view.game.status === 'open'); const waiting = $derived(!!view && view.game.status === 'open');
const nudgeCooldownSecs = 3600; const nudgeCooldownSecs = 3600;
@@ -110,4 +113,4 @@
} }
</script> </script>
<Chat {messages} {myId} {busy} myTurn={isMyTurn} {canSend} {waiting} {nudgeOnCooldown} onsend={sendChat} onnudge={nudge} /> <Chat {messages} {myId} {busy} myTurn={isMyTurn} {canSend} {canNudge} {waiting} {nudgeOnCooldown} onsend={sendChat} onnudge={nudge} />