From 858d326895a2fdb78ccfb0df6f2045cd7ed2ffc7 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Fri, 17 Apr 2026 12:15:23 -0600 Subject: [PATCH] fix(chess): preset toggles refresh engine state immediately; new-game flows reset the board --- packages/chess/src/app/App.tsx | 2 +- packages/chess/src/hooks/useChessEngine.ts | 11 ++++++ packages/chess/src/ui/GameView.tsx | 4 +- packages/chess/src/ui/Lobby.tsx | 43 +++++++++++++++++++--- packages/chess/src/ui/RulesDrawer.tsx | 12 +++++- packages/chess/src/ui/RulesView.tsx | 10 +++-- 6 files changed, 70 insertions(+), 12 deletions(-) diff --git a/packages/chess/src/app/App.tsx b/packages/chess/src/app/App.tsx index d91e462..7b926b3 100644 --- a/packages/chess/src/app/App.tsx +++ b/packages/chess/src/app/App.tsx @@ -32,7 +32,7 @@ export function App() { - } /> + } /> } /> } /> } /> diff --git a/packages/chess/src/hooks/useChessEngine.ts b/packages/chess/src/hooks/useChessEngine.ts index 0a9084c..20e6796 100644 --- a/packages/chess/src/hooks/useChessEngine.ts +++ b/packages/chess/src/hooks/useChessEngine.ts @@ -87,6 +87,16 @@ export function useChessEngine() { ? moveHistoryRef.current[moveHistoryRef.current.length - 1] : null; + /** + * Force the hook's derived state (facts, legalMoves, turn, result) to + * recompute. Call this after mutating external state that the engine + * reads but React can't see — for example, toggling PRESET_REGISTRY + * entries that change the set of legal moves without triggering a move. + */ + const refresh = useCallback(() => { + setTick(t => t + 1); + }, []); + return { engine, turn: getTurn(), @@ -97,6 +107,7 @@ export function useChessEngine() { undo, canUndo: moveHistoryRef.current.length > 0, loadEngine, + refresh, lastMove, }; } diff --git a/packages/chess/src/ui/GameView.tsx b/packages/chess/src/ui/GameView.tsx index c24e9c0..5641461 100644 --- a/packages/chess/src/ui/GameView.tsx +++ b/packages/chess/src/ui/GameView.tsx @@ -17,7 +17,7 @@ export function GameView({ engineState }: GameViewProps) { const localChessState = useChessEngine(); const state = engineState || localChessState; - const { facts, legalMoves, turn, result, applyMove, undo, canUndo, lastMove } = state; + const { facts, legalMoves, turn, result, applyMove, undo, canUndo, lastMove, refresh } = state; const handleMove = (from: number, to: number, promoteTo?: PieceType) => { applyMove(from, to, promoteTo || 'queen'); @@ -82,7 +82,7 @@ export function GameView({ engineState }: GameViewProps) { transition={{ duration: 0.3 }} className="flex flex-col items-center gap-8 py-8 w-full max-w-4xl mx-auto" > - +
{/* Header/Info section */} diff --git a/packages/chess/src/ui/Lobby.tsx b/packages/chess/src/ui/Lobby.tsx index 6164b53..a627db9 100644 --- a/packages/chess/src/ui/Lobby.tsx +++ b/packages/chess/src/ui/Lobby.tsx @@ -2,6 +2,8 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'motion/react'; import { pieceAssets } from '../assets/pieces'; +import { ChessEngine } from '../engine'; +import { clearAutoSave } from '../persist/autosave'; const WS_URL = (import.meta as { env?: Record }).env?.['VITE_WS_URL'] ?? @@ -86,13 +88,31 @@ function oneShotRoomRequest( }); } -export function Lobby() { +interface LobbyProps { + /** Optional — when provided, create/join/solo flows reset the local + * ChessEngine so the user starts from the standard opening rather + * than whatever was restored from autosave. */ + chessState?: { + loadEngine: (engine: ChessEngine) => void; + }; +} + +export function Lobby({ chessState }: LobbyProps = {}) { const [codeInput, setCodeInput] = useState(''); const [roomCode, setRoomCode] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const navigate = useNavigate(); + const resetToFreshGame = () => { + // Wipe the autosave so a later full-page reload doesn't re-hydrate + // the previous (finished) game, and replace the in-memory engine + // with a brand-new one now so the board shows the starting position + // the instant the user lands on /game. + clearAutoSave(); + chessState?.loadEngine(new ChessEngine()); + }; + const handleCreate = async () => { setLoading(true); setError(null); @@ -101,6 +121,7 @@ export function Lobby() { sessionStorage.setItem('room-code', code); sessionStorage.setItem('room-token', token); sessionStorage.setItem('player-color', color); + resetToFreshGame(); setRoomCode(code); } catch (err) { setError(err instanceof Error ? err.message : 'Could not connect to server'); @@ -122,6 +143,7 @@ export function Lobby() { sessionStorage.setItem('room-code', result.code); sessionStorage.setItem('room-token', result.token); sessionStorage.setItem('player-color', result.color); + resetToFreshGame(); navigate('/game'); } catch (err) { setError(err instanceof Error ? err.message : 'Invalid room code'); @@ -130,6 +152,11 @@ export function Lobby() { } }; + const handlePlaySolo = () => { + resetToFreshGame(); + navigate('/game'); + }; + return (
Paratype Chess

Realtime multiplayer with custom rules

@@ -187,7 +214,13 @@ export function Lobby() {