feat: improve opening trainer flow and add deviation dialog

- Add tutor message guardrail to prevent rapid-fire messages
  - Track last message by move index
  - Wait for both player and opponent moves before speaking
  - Speak immediately when player deviates from theory

- Add deviation dialog with three options:
  - Continue Playing (Start Game) - transitions to game mode
  - Undo & Return to Opening - returns to theory
  - Explore This Variation - continues off-book practice

- Implement smooth game mode transition:
  - Pass opening context from trainer to game
  - Tutor welcomes player with context about their study
  - Computer makes first move if needed in starting position

- Fix generate-test-fixtures script:
  - Add safety check to prevent overwriting real data
  - Restore full opening database (12,379 openings)
  - Rebuild move index (12,377 sequences)
  - Add clear warnings about test fixtures

- Update ChessGame to accept openingContext prop
- Update Tutor to display contextual greeting in game mode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Stefan
2025-12-08 15:05:05 +01:00
parent 1e78d8114e
commit 8ed402bbe0
34 changed files with 111269 additions and 480 deletions
+21
View File
@@ -22,6 +22,13 @@ export default function Home() {
initialPersonality: Personality;
initialColor: 'white' | 'black';
initialStockfishDepth?: number;
openingContext?: {
openingName: string;
openingEco: string;
movesCompleted: number;
wikipediaSummary?: string;
contextMessage: string;
};
} | null>(null);
const [savedGames, setSavedGames] = useState<SavedGame[]>([]);
@@ -48,18 +55,31 @@ export default function Home() {
const personality = PERSONALITIES.find(p => p.id === pendingGame.personalityId) || PERSONALITIES[0];
// Check for opening context (from opening trainer)
const openingContextRaw = localStorage.getItem("chess_tutor_opening_context");
let openingContext;
if (openingContextRaw) {
try {
openingContext = JSON.parse(openingContextRaw);
} catch (err) {
console.error("Failed to parse opening context", err);
}
}
setGameProps({
gameId: crypto.randomUUID ? crypto.randomUUID() : `game-${Date.now()}`,
initialFen: pendingGame.fen,
initialPersonality: personality,
initialColor: pendingGame.color,
initialStockfishDepth: pendingGame.stockfishDepth,
openingContext,
});
setView('game');
} catch (err) {
console.error("Failed to load pending game", err);
} finally {
localStorage.removeItem("chess_tutor_pending_game");
localStorage.removeItem("chess_tutor_opening_context");
}
}
@@ -127,6 +147,7 @@ export default function Home() {
initialPersonality={gameProps.initialPersonality}
initialColor={gameProps.initialColor}
initialStockfishDepth={gameProps.initialStockfishDepth}
openingContext={gameProps.openingContext}
onBack={handleBackToMenu}
/>
)}