fix(call-to-play): extend from the current deadline

Pass the effective nomination deadline into the Add time action and extend
from whichever is later: that deadline or the current time. This preserves
remaining time when a call becomes ready early while still giving an overdue
call a fresh five-minute window.

Test Plan:
- just fmt
- just frontend-test
- just build
- git diff --cached --check
This commit is contained in:
2026-07-23 18:05:21 +02:00
parent 9c34efa705
commit 8d3affe19c
4 changed files with 29 additions and 4 deletions
@@ -343,7 +343,10 @@ const CardActions = ({
if (accepted && game) onLaunch(game); if (accepted && game) onLaunch(game);
})} })}
><Icon.play /><span>{game ? 'Start now' : 'Mark as running'}</span></button> ><Icon.play /><span>{game ? 'Start now' : 'Mark as running'}</span></button>
<button className="ghost-btn" onClick={() => actions.addTime(nomination.id)}> <button
className="ghost-btn"
onClick={() => actions.addTime(nomination.id, nomination.deadline)}
>
Add 5 more minutes Add 5 more minutes
</button> </button>
</> </>
@@ -4,6 +4,7 @@ import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { import {
callToPlayEvent, callToPlayEvent,
extendDeadline,
pruneCallToPlayEvents, pruneCallToPlayEvents,
reduceCallToPlayEvents, reduceCallToPlayEvents,
} from '../lib/callToPlay'; } from '../lib/callToPlay';
@@ -22,7 +23,7 @@ export interface CallToPlayActions {
leave: (callId: string) => void; leave: (callId: string) => void;
cancel: (callId: string) => void; cancel: (callId: string) => void;
startNow: (callId: string) => Promise<boolean>; startNow: (callId: string) => Promise<boolean>;
addTime: (callId: string, minutes?: number) => void; addTime: (callId: string, currentDeadline: number, minutes?: number) => void;
} }
export interface UseCallToPlay { export interface UseCallToPlay {
@@ -175,8 +176,8 @@ export const useCallToPlay = (username: string): UseCallToPlay => {
leave: callId => void publish(callId, 'Leave'), leave: callId => void publish(callId, 'Leave'),
cancel: callId => void publish(callId, 'Cancel'), cancel: callId => void publish(callId, 'Cancel'),
startNow: callId => publish(callId, 'Start'), startNow: callId => publish(callId, 'Start'),
addTime: (callId, minutes = 5) => void publish(callId, { addTime: (callId, currentDeadline, minutes = 5) => void publish(callId, {
AddTime: { deadline: Date.now() + minutes * 60_000 }, AddTime: { deadline: extendDeadline(Date.now(), currentDeadline, minutes) },
}), }),
}), [publish]); }), [publish]);
@@ -9,6 +9,12 @@ export const CHECKIN_LEAD_MS = 15 * 60_000;
export const EXPIRED_RETENTION_MS = 5 * 60_000; export const EXPIRED_RETENTION_MS = 5 * 60_000;
export const TERMINAL_RETENTION_MS = 15 * 60_000; export const TERMINAL_RETENTION_MS = 15 * 60_000;
export const extendDeadline = (
now: number,
currentDeadline: number,
durationMinutes = 5,
): number => Math.max(now, currentDeadline) + durationMinutes * 60_000;
export type CallToPlayPhase = 'now' | 'scheduled' | 'checkin'; export type CallToPlayPhase = 'now' | 'scheduled' | 'checkin';
export type CallToPlayStatus = export type CallToPlayStatus =
| 'running' | 'running'
@@ -3,6 +3,7 @@ import {
EXPIRED_RETENTION_MS, EXPIRED_RETENTION_MS,
TERMINAL_RETENTION_MS, TERMINAL_RETENTION_MS,
activeCallCount, activeCallCount,
extendDeadline,
phaseOf, phaseOf,
bumpTime, bumpTime,
normalizeTimeInput, normalizeTimeInput,
@@ -163,6 +164,20 @@ Deno.test('creator can extend, start, and cancel a call', () => {
assertEquals(cancelled.terminalAt, NOW + 1, 'cancel timestamp'); assertEquals(cancelled.terminalAt, NOW + 1, 'cancel timestamp');
}); });
Deno.test('adding time extends from the current deadline or the current time', () => {
const futureDeadline = NOW + 30 * 60_000;
assertEquals(
extendDeadline(NOW, futureDeadline),
futureDeadline + 5 * 60_000,
'ready-early call keeps its remaining time',
);
assertEquals(
extendDeadline(NOW, NOW - 60_000),
NOW + 5 * 60_000,
'overdue call gets five minutes from now',
);
});
Deno.test('reduction is order-independent and deduplicates events and messages', () => { Deno.test('reduction is order-independent and deduplicates events and messages', () => {
const message = event('message-event', 'Bob', { const message = event('message-event', 'Bob', {
SendMessage: { message_id: 'message-1', text: 'Ready?' }, SendMessage: { message_id: 'message-1', text: 'Ready?' },