From 4722ab47fb3b84a231d69e25c13f56025f3f6474 Mon Sep 17 00:00:00 2001 From: calixteair Date: Wed, 13 May 2026 20:40:43 +0200 Subject: [PATCH 1/2] fix(web): auto-start a duel game when Grid mounts in duel mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a friend clicks the shared duel link, /duel loads, displays the players summary, and they click 'Play this grid'. That mounted , but Grid.loadGrid short-circuited on duel mode without ever calling startGame — store.state stayed null, no template branch matched, the section rendered empty. Exactly the same bug we hit on /play in #56, just on a different mode. The 'consent step matters' comment in the previous version was wrong: the consent already happened upstream on /duel via the explicit CTA. By the time Grid mounts with mode=duel + duelGridId, we should immediately POST /games to materialise the cells. Mirror solo's mount logic but pinned to duelGridId: clear the store if it holds a different gridId, resume if it holds an unfinished session for this duel, otherwise startGame(). --- web/src/lib/components/Grid.svelte | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/web/src/lib/components/Grid.svelte b/web/src/lib/components/Grid.svelte index 4e19620..88ac8e1 100644 --- a/web/src/lib/components/Grid.svelte +++ b/web/src/lib/components/Grid.svelte @@ -66,9 +66,24 @@ }; const loadGrid = async (): Promise => { - // Duel grids materialise only when the player clicks 'Play this grid' on - // /duel — never auto-start, the consent step matters there. + // Duel: the consent step happened upstream on /duel (the friend clicked + // "Play this grid"). By the time Grid mounts with mode=duel + duelGridId, + // we should immediately start the game so cells render. Mirror solo's + // logic but pinned to the duel's grid_id rather than a fresh one: if the + // persisted session points at the same grid, resume; otherwise clear and + // start anew. if (mode === "duel") { + if (duelGridId && store.state && store.state.gridId !== duelGridId) { + store.clear(); + } + const hasActiveDuel = + store.state !== null && + !store.state.ended && + store.grid !== null && + store.state.gridId === duelGridId; + if (!hasActiveDuel) { + await startGame(); + } gridLoading = false; return; } From 8b200836560f4274b694012a73aed69155209009 Mon Sep 17 00:00:00 2001 From: calixteair Date: Wed, 13 May 2026 20:41:18 +0200 Subject: [PATCH 2/2] feat(web): create-duel CTA on /duel landing without query params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /duel without ?id= used to render an error 'invalid duel link' and a 'back to home' link — a dead-end. Repurpose it as the entry point for creating a fresh shared grid: - Landing screen with 'Create a grid to share' CTA. Clicking POSTs /api/duels and either fires the Web Share API or falls back to clipboard copy. - A 'Share again' + 'Copy' panel appears once the link is created so the player can re-share without rolling a new grid. - Errors from a broken/expired duel link (401/404/410) now show a banner above the create CTA instead of replacing the page — the player can immediately spin up a fresh duel. - 'view' screen (resolved ?id=&sig=) and 'playing' screen (mounted Grid) move to a typed 'screen' state machine so the three modes are explicit and the markup stays linear. i18n: 5 new keys (eyebrow, title, subtitle, button, hint) for fr + en. --- web/messages/en.json | 5 + web/messages/fr.json | 5 + web/src/lib/components/DuelView.svelte | 177 +++++++++++++++++++++---- 3 files changed, 160 insertions(+), 27 deletions(-) diff --git a/web/messages/en.json b/web/messages/en.json index c4668b0..bd578b5 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -118,6 +118,11 @@ "duel_share_action": "Challenge a friend on this grid", "duel_share_creating": "Creating link…", "duel_share_ready": "Here's your challenge link", + "duel_create_eyebrow": "New challenge", + "duel_create_title": "Start a duel in one click", + "duel_create_subtitle": "Generate a random grid and share the link with a friend — they'll play the same grid as you.", + "duel_create_button": "Create a grid to share", + "duel_create_share_hint": "The link expires in 30 days.", "modal_rules_title": "How to play", "modal_rules_intro": "Find the entity that satisfies both the row and column.", "modal_rules_step_1": "Tap an empty cell.", diff --git a/web/messages/fr.json b/web/messages/fr.json index 9754b1e..1c54029 100644 --- a/web/messages/fr.json +++ b/web/messages/fr.json @@ -118,6 +118,11 @@ "duel_share_action": "Défier un ami sur cette grille", "duel_share_creating": "Création du lien…", "duel_share_ready": "Voici votre lien de défi", + "duel_create_eyebrow": "Nouveau défi", + "duel_create_title": "Lance un duel en un clic", + "duel_create_subtitle": "Générez une grille aléatoire et partagez le lien à un ami : il jouera la même grille que vous.", + "duel_create_button": "Créer une grille à partager", + "duel_create_share_hint": "Le lien expire dans 30 jours.", "modal_rules_title": "Comment jouer", "modal_rules_intro": "Trouvez l'entité qui satisfait à la fois la ligne et la colonne.", "modal_rules_step_1": "Touchez une case vide.", diff --git a/web/src/lib/components/DuelView.svelte b/web/src/lib/components/DuelView.svelte index ce70f58..0f5930b 100644 --- a/web/src/lib/components/DuelView.svelte +++ b/web/src/lib/components/DuelView.svelte @@ -1,8 +1,13 @@