fix(call-to-play): expire elapsed calls clearly

Deadline completion previously shared the green Ready presentation with a full
roster and remained visible forever. An abandoned call therefore looked ready
to launch and required its creator to return and cancel it.

Give elapsed calls a distinct Time's up state and a five-minute grace period in
which the creator can start or extend them. After that, both the reducer and
peer store remove the call as a unit. Filled calls remain ready until their
deadline, and active calls continue to retain complete history for late joiners.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed, 147 peer tests
- `just frontend-test` -- passed, 22 tests
- `just build` -- passed
- `git diff --cached --check` -- passed
This commit is contained in:
2026-07-21 22:56:02 +02:00
parent 640d81d919
commit cc7dacf6c3
9 changed files with 171 additions and 40 deletions
@@ -7,9 +7,10 @@ import {
export const CHECKIN_LEAD_MS = 15 * 60_000;
export const STARTED_RETENTION_MS = 3_000;
export const EXPIRED_RETENTION_MS = 5 * 60_000;
export type CallToPlayPhase = 'now' | 'scheduled' | 'checkin';
export type CallToPlayStatus = 'started' | 'ready' | 'soon' | 'scheduled' | 'call';
export type CallToPlayStatus = 'started' | 'expired' | 'ready' | 'soon' | 'scheduled' | 'call';
interface MutableNomination extends Nomination {
cancelled: boolean;
@@ -58,6 +59,7 @@ export const inCountOf = (nomination: Nomination, now: number): number =>
export const statusOf = (nomination: Nomination, now: number): CallToPlayStatus => {
if (nomination.state === 'started') return 'started';
if (now >= nomination.deadline) return 'expired';
if (nomination.state === 'done' || readyCountOf(nomination, now) >= nomination.maxPlayers) {
return 'ready';
}
@@ -123,6 +125,11 @@ export const reduceCallToPlayEvents = (
) {
continue;
}
if (nomination.state !== 'started'
&& now - nomination.deadline > EXPIRED_RETENTION_MS
) {
continue;
}
const { cancelled: _, messageIds: __, ...result } = nomination;
nominations.push(result);