From dc326a0e3f3b81775ed40cfa36629ad06c58d3dc Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 15 Sep 2026 23:41:43 +0200 Subject: [PATCH] fix(call-to-play): confirm early starts inline Starting an underfilled or scheduled call immediately sent Start. Ask "Start this call early?" inline, with Start now and Keep waiting choices, before publishing an early start. Ready and elapsed calls retain one-click starting, and local launch still waits for core acceptance. Match the existing cancel confirmation layout, reset pending confirmation on call or actor changes, and test the real action callbacks for dismissal, retry, deferred acceptance, normal starts, and missing local games. Test Plan: - `just frontend-test` -- passed, 113 tests - `just test` -- passed on rerun, 797 tests; the initial run hit an unchanged scoped-process test's two-second child-start timeout - `just clippy` -- passed with warnings denied - `just fmt` -- passed - `deno task build` -- passed TypeScript and Vite production build - `git diff --cached --check` -- passed --- .../components/calltoplay/NominationCard.tsx | 50 ++++++- .../src/styles/launcher.css | 24 +++- .../tests/nominationCard.test.ts | 135 +++++++++++++++++- design/launcher/SPEC.md | 8 +- 4 files changed, 200 insertions(+), 17 deletions(-) diff --git a/crates/lanspread-tauri-deno-ts/src/components/calltoplay/NominationCard.tsx b/crates/lanspread-tauri-deno-ts/src/components/calltoplay/NominationCard.tsx index 9380f1d..5652d21 100644 --- a/crates/lanspread-tauri-deno-ts/src/components/calltoplay/NominationCard.tsx +++ b/crates/lanspread-tauri-deno-ts/src/components/calltoplay/NominationCard.tsx @@ -70,10 +70,14 @@ export const NominationCard = ({ onLaunch, }: Props) => { const [confirmCancel, setConfirmCancel] = useState(false); + const [confirmStart, setConfirmStart] = useState(false); const cardRef = useRef(null); useEffect(() => { if (focused) cardRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' }); }, [focused]); + useEffect(() => { + setConfirmStart(false); + }, [nomination.id, nomination.state, actorId]); const now = Date.now(); const entries = Object.entries(nomination.participants); @@ -212,6 +216,8 @@ export const NominationCard = ({ actions={actions} onLaunch={onLaunch} now={now} + confirmStart={confirmStart} + onStartConfirmationChange={setConfirmStart} /> @@ -255,6 +261,8 @@ interface CardActionsProps { actions: CallToPlayActions; onLaunch: (game: Game) => void; now: number; + confirmStart: boolean; + onStartConfirmationChange: (confirm: boolean) => void; } const ReadyButtons = ({ nomination, actions, includeThirty = false }: { @@ -278,13 +286,15 @@ const ReadyButtons = ({ nomination, actions, includeThirty = false }: { ); -const CardActions = ({ +export const CardActions = ({ nomination, game, actorId, actions, onLaunch, now, + confirmStart, + onStartConfirmationChange, }: CardActionsProps) => { const myStatus = actorId === null ? undefined : nomination.participants[actorId]; const isMe = myStatus !== undefined; @@ -294,6 +304,11 @@ const CardActions = ({ const isExpired = statusOf(nomination, now) === 'expired'; const scheduled = phaseOf(nomination, now) === 'scheduled' && !isDone && !terminal; const readyCount = readyCountOf(nomination, now); + const startCall = async () => { + onStartConfirmationChange(false); + const accepted = await actions.startNow(nomination.id); + if (accepted && game) onLaunch(game); + }; if (terminal) { return ( @@ -310,12 +325,33 @@ const CardActions = ({ if (isCreator) { return ( <> - + {confirmStart && !isDone + ? ( +
+ Start this call early? +
+ + +
+
+ ) + : ( + + )} {isDone && (