feat(chess): add localStorage auto-save and restore (P3.14)

This commit is contained in:
Joey Yakimowich-Payne 2026-04-16 16:24:49 -06:00
commit a75b6f041b
No known key found for this signature in database
4 changed files with 43 additions and 2 deletions

View file

@ -1,3 +1,4 @@
import { useEffect } from 'react'
import { Routes, Route, useNavigate } from 'react-router-dom'
import { GameView } from '../ui/GameView'
import { RulesView } from '../ui/RulesView'
@ -5,11 +6,23 @@ import { SavePanel } from '../ui/SavePanel'
import { ImportExport } from '../ui/ImportExport'
import { useChessEngine } from '../hooks/useChessEngine'
import { ChessEngine } from '../engine'
import { loadAutoSave } from '../persist/autosave.js'
import type { AttrKey, FactValue, EntityId } from '@paratype/rete'
export function App() {
const chessState = useChessEngine()
// Restore autosave on mount — [] deps is intentional (run once, avoid infinite loop)
useEffect(() => {
const saved = loadAutoSave();
if (!saved) return;
const newEngine = new ChessEngine();
const oldFacts = newEngine.session.allFacts();
for (const f of oldFacts) newEngine.session.retract(f.id, f.attr as AttrKey);
for (const f of saved) newEngine.session.insert(f.id as EntityId, f.attr as AttrKey, f.value as FactValue);
chessState.loadEngine(newEngine);
}, []); // intentional: run once on mount, chessState ref is stable
return (
<div data-testid="app-root">
<Routes>

View file

@ -1,6 +1,7 @@
import { useState, useRef, useCallback } from 'react';
import { ChessEngine, type GameResult } from '../engine';
import type { PieceType } from '../schema';
import { saveAutoSave } from '../persist/autosave.js';
export function useChessEngine() {
const engineRef = useRef<ChessEngine | null>(null);
@ -19,6 +20,7 @@ export function useChessEngine() {
const loadEngine = useCallback((newEngine: ChessEngine) => {
engineRef.current = newEngine;
moveHistoryRef.current = [];
saveAutoSave(newEngine.session.allFacts());
setTick(t => t + 1);
}, []);
@ -43,6 +45,7 @@ export function useChessEngine() {
if (move) {
moveHistoryRef.current.push({ from, to, promoteTo });
const result = engine.applyMove(move, promoteTo);
saveAutoSave(engine.session.allFacts());
setTick(t => t + 1); // trigger re-render
return result;
}
@ -60,6 +63,7 @@ export function useChessEngine() {
}
}
engineRef.current = newEngine;
saveAutoSave(newEngine.session.allFacts());
setTick(t => t + 1);
}
}, []);

View file

@ -0,0 +1,23 @@
import { exportGame, importGame } from './io.js';
const AUTOSAVE_KEY = 'paratype-chess:v1:autosave';
export function saveAutoSave(facts: Array<{ id: number; attr: string; value: unknown }>): void {
try {
localStorage.setItem(AUTOSAVE_KEY, exportGame(facts));
} catch { /* ignore quota errors */ }
}
export function loadAutoSave(): Array<{ id: number; attr: string; value: unknown }> | null {
try {
const raw = localStorage.getItem(AUTOSAVE_KEY);
if (!raw) return null;
return importGame(raw);
} catch {
return null; // silently discard invalid/stale/version-mismatched data
}
}
export function clearAutoSave(): void {
localStorage.removeItem(AUTOSAVE_KEY);
}

View file

@ -2,7 +2,8 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"composite": true
},
"include": ["src/**/*", "tests/**/*"]
"include": ["src/**/*"]
}