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
+48
View File
@@ -0,0 +1,48 @@
{
"rn1qkbnr/ppp2ppp/8/3p4/5p2/6PB/PPPPP2P/RNBQK2R w KQkq - 0 5": {
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3 d5 2. g3 e5 3. f4 Bxh3 4. Bxh3 exf4",
"name": "Amar Gambit"
},
"rnbqkbnr/pppppppp/8/8/8/7N/PPPPPPPP/RNBQKB1R b KQkq - 1 1": {
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3",
"name": "Amar Opening",
"scid": "A00g",
"aliases": {
"scid": "Amar/Paris Opening",
"eco_wikip": "Irregular Openings: Amar Opening",
"ct": "Amar Opening, General",
"chessGraph": "Amar; Paris Opening",
"chronos": "Amar (Paris) opening",
"icsbot": "Amar; Paris Opening "
}
},
"rn1qkbnr/ppp2ppp/8/3p4/8/6PB/PPPPP3/RNBQ1RK1 b kq - 0 6": {
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3 d5 2. g3 e5 3. f4 Bxh3 4. Bxh3 exf4 5. O-O fxg3 6. hxg3",
"name": "Amar Opening: Paris Gambit, Gent Gambit",
"aliases": {
"ct": "Amar Opening, Gent Gambit"
}
},
"rnbqkbnr/ppp2ppp/8/3pp3/5P2/6PN/PPPPP2P/RNBQKB1R b KQkq - 0 3": {
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3 d5 2. g3 e5 3. f4",
"name": "Amar Opening: Paris Gambit",
"scid": "A00g",
"aliases": {
"scid": "Amar: Paris Gambit"
}
},
"r1bqkb1r/ppp2ppp/2np1n2/4p3/2P5/1PN1P3/P2P1PPP/R1BQKBNR w KQkq - 1 5": {
"src": "eco_tsv",
"eco": "A00",
"moves": "1. e3 e5 2. c4 d6 3. Nc3 Nc6 4. b3 Nf6",
"name": "Amsterdam Attack"
}
}
+46
View File
@@ -0,0 +1,46 @@
{
"rnbqkbnr/ppppp1pp/5p2/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2": {
"src": "eco_tsv",
"eco": "B00",
"moves": "1. e4 f6",
"name": "Barnes Defense"
},
"rnbqkbnr/1pppppp1/p6p/8/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3": {
"src": "scid",
"eco": "B00",
"moves": "1. e4 a6 2. d4 h6",
"name": "Basman's Creepy-Crawly System (as Black)",
"scid": "B00c"
},
"rnbqkbnr/pppppp1p/8/6p1/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2": {
"src": "eco_tsv",
"eco": "B00",
"moves": "1. e4 g5",
"name": "Borg Defense",
"scid": "B00b",
"aliases": {
"scid": "Reversed Grob (Borg/Basman Defence)",
"ct": "Borg Defense, General",
"chessGraph": "Basman Defense; Macho Grob",
"chronos": "Reversed Grob (Borg/Basman defence/macho Grob)",
"icsbot": "Basman Defense; Macho Grob "
}
},
"rnbqk1nr/ppppppbp/8/6p1/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 1 3": {
"src": "eco_tsv",
"eco": "B00",
"moves": "1. e4 g5 2. d4 Bg7",
"name": "Borg Defense: Borg Gambit",
"aliases": {
"eco_js": "Borg Defense: Borg Gambit, 2. d4 Bg7",
"chessGraph": "Borg Gambit",
"icsbot": "Borg Gambit "
}
},
"rnbqkbnr/p2p1p1p/1p2p3/2P3p1/4P3/2P5/PP3PPP/RNBQKBNR w KQkq - 0 5": {
"src": "eco_js",
"eco": "B00",
"moves": "1. e4 g5 2. d4 e6 3. c3 c5 4. dxc5 b6",
"name": "Borg Defense: Langhorst Gambit"
}
}
+66
View File
@@ -0,0 +1,66 @@
{
"rnbqkbnr/pppp1ppp/4p3/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2": {
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6",
"name": "French Defense",
"scid": "C00a",
"aliases": {
"scid": "French",
"ct": "French Defense, General",
"chessGraph": "French Defense; C00",
"icsbot": "French Defense; C00 "
},
"isEcoRoot": true,
"wikipediaSlug": "french-defense"
},
"rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3": {
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. d4 d5",
"name": "French Defense",
"scid": "C00x",
"aliases": {
"scid": "French: 2.d4 d5",
"eco_js": "French Defense, 2. d4 d5",
"ct": "French Defense, Main Line"
},
"wikipediaSlug": "french-defense"
},
"rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/4B3/PPP2PPP/RN1QKBNR b KQkq - 1 3": {
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. d4 d5 3. Be3",
"name": "French Defense: Alapin Gambit",
"scid": "C00x",
"aliases": {
"scid": "French: Alapin-Diemer Gambit (ADG)",
"chessGraph": "Alapin; Witaker Gambit, French",
"chronos": "French: Alapin variation",
"icsbot": "Alapin; Witaker Gambit, French "
},
"wikipediaSlug": "french-defense"
},
"rnbqkbnr/p1pp1ppp/4p3/1p6/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3": {
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. d4 b5",
"name": "French Defense: Baeuerle Gambit",
"aliases": {
"chessGraph": "Baeuerle Gambit, French",
"icsbot": "Baeuerle Gambit, French "
},
"wikipediaSlug": "french-defense"
},
"rnbqkbnr/pppp1ppp/4p3/8/1P2P3/8/P1PP1PPP/RNBQKBNR b KQkq - 0 2": {
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. b4",
"name": "French Defense: Banzai-Leong Gambit",
"aliases": {
"chessGraph": "Banzai-Leong Gambit, French",
"icsbot": "Banzai-Leong Gambit, French "
},
"wikipediaSlug": "french-defense"
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"rnbqkbnr/ppp1pppp/8/3p4/3P4/3Q4/PPP1PPPP/RNB1KBNR b KQkq - 1 2": {
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. Qd3",
"name": "Amazon Attack"
},
"rnbqkbnr/ppp1pppp/8/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2": {
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. e4",
"name": "Blackmar-Diemer Gambit",
"scid": "D00l",
"aliases": {
"scid": "Blackmar-Diemer Gambit (BDG): 2.e4",
"ct": "Blackmar-Diemer Gambit, General",
"chronos": "Blackmar gambit"
}
},
"rnbqkb1r/ppp1pppp/5n2/8/3Pp3/2N5/PPP2PPP/R1BQKBNR w KQkq - 2 4": {
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. e4 dxe4 3. Nc3 Nf6",
"name": "Blackmar-Diemer Gambit",
"aliases": {
"chessGraph": "Vienna Variation, BDG",
"icsbot": "Vienna Variation, BDG "
}
},
"rnbqkbnr/ppp1pppp/8/8/3Pp3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3": {
"src": "scid",
"eco": "D00",
"moves": "1. d4 d5 2. e4 dxe4",
"name": "Blackmar-Diemer Gambit (BDG): 2.e4 dxe4",
"scid": "D00l"
},
"rnbqkb1r/ppp1pppp/5n2/8/3P4/2N2p2/PPP3PP/R1BQKBNR w KQkq - 0 5": {
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. e4 dxe4 3. Nc3 Nf6 4. f3 exf3",
"name": "Blackmar-Diemer Gambit Accepted",
"scid": "D00v",
"aliases": {
"scid": "Blackmar-Diemer: Accepted"
}
}
}
+56
View File
@@ -0,0 +1,56 @@
{
"rnbqkb1r/pppp1ppp/4pn2/8/2PP4/6P1/PP2PP1P/RNBQKBNR b KQkq - 0 3": {
"src": "eco_tsv",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3",
"name": "Catalan Opening",
"scid": "E00g",
"aliases": {
"scid": "Catalan",
"ct": "Catalan Opening, General"
},
"wikipediaSlug": "catalan-opening"
},
"rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/6P1/PP2PP1P/RNBQKBNR w KQkq - 0 4": {
"src": "eco_tsv",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 d5",
"name": "Catalan Opening",
"scid": "E00n",
"aliases": {
"scid": "Catalan: 3...d5",
"eco_js": "Catalan Opening, 2. c4 e6 3. g3 d5",
"chessGraph": "E01"
},
"wikipediaSlug": "catalan-opening"
},
"rnbqkb1r/pppp1ppp/5n2/4p3/2PP4/6P1/PP2PP1P/RNBQKBNR w KQkq - 0 4": {
"src": "eco_tsv",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 e5",
"name": "Catalan Opening: Hungarian Gambit",
"scid": "E00g",
"aliases": {
"scid": "Catalan: Hungarian Gambit",
"chessGraph": "Hungarian Gambit, Catalan",
"icsbot": "Hungarian Gambit, Catalan "
},
"wikipediaSlug": "catalan-opening"
},
"rnbqk2r/pppp1ppp/4pn2/8/1bPP4/6P1/PP2PP1P/RNBQKBNR w KQkq - 1 4": {
"src": "scid",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 Bb4+",
"name": "Catalan: 3...Bb4+",
"scid": "E00i",
"wikipediaSlug": "catalan"
},
"rnbqk2r/pppp1ppp/4pn2/8/1bPP4/6P1/PP1BPP1P/RN1QKBNR b KQkq - 2 4": {
"src": "scid",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 Bb4+ 4. Bd2",
"name": "Catalan: 3...Bb4+ 4.Bd2",
"scid": "E00j",
"wikipediaSlug": "catalan"
}
}
+305
View File
@@ -0,0 +1,305 @@
{
"1. Nh3 d5 2. g3 e5 3. f4 Bxh3 4. Bxh3 exf4": [
{
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3 d5 2. g3 e5 3. f4 Bxh3 4. Bxh3 exf4",
"name": "Amar Gambit"
}
],
"1. Nh3": [
{
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3",
"name": "Amar Opening",
"scid": "A00g",
"aliases": {
"scid": "Amar/Paris Opening",
"eco_wikip": "Irregular Openings: Amar Opening",
"ct": "Amar Opening, General",
"chessGraph": "Amar; Paris Opening",
"chronos": "Amar (Paris) opening",
"icsbot": "Amar; Paris Opening "
}
}
],
"1. Nh3 d5 2. g3 e5 3. f4 Bxh3 4. Bxh3 exf4 5. O-O fxg3 6. hxg3": [
{
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3 d5 2. g3 e5 3. f4 Bxh3 4. Bxh3 exf4 5. O-O fxg3 6. hxg3",
"name": "Amar Opening: Paris Gambit, Gent Gambit",
"aliases": {
"ct": "Amar Opening, Gent Gambit"
}
}
],
"1. Nh3 d5 2. g3 e5 3. f4": [
{
"src": "eco_tsv",
"eco": "A00",
"moves": "1. Nh3 d5 2. g3 e5 3. f4",
"name": "Amar Opening: Paris Gambit",
"scid": "A00g",
"aliases": {
"scid": "Amar: Paris Gambit"
}
}
],
"1. e3 e5 2. c4 d6 3. Nc3 Nc6 4. b3 Nf6": [
{
"src": "eco_tsv",
"eco": "A00",
"moves": "1. e3 e5 2. c4 d6 3. Nc3 Nc6 4. b3 Nf6",
"name": "Amsterdam Attack"
}
],
"1. e4 f6": [
{
"src": "eco_tsv",
"eco": "B00",
"moves": "1. e4 f6",
"name": "Barnes Defense"
}
],
"1. e4 a6 2. d4 h6": [
{
"src": "scid",
"eco": "B00",
"moves": "1. e4 a6 2. d4 h6",
"name": "Basman's Creepy-Crawly System (as Black)",
"scid": "B00c"
}
],
"1. e4 g5": [
{
"src": "eco_tsv",
"eco": "B00",
"moves": "1. e4 g5",
"name": "Borg Defense",
"scid": "B00b",
"aliases": {
"scid": "Reversed Grob (Borg/Basman Defence)",
"ct": "Borg Defense, General",
"chessGraph": "Basman Defense; Macho Grob",
"chronos": "Reversed Grob (Borg/Basman defence/macho Grob)",
"icsbot": "Basman Defense; Macho Grob "
}
}
],
"1. e4 g5 2. d4 Bg7": [
{
"src": "eco_tsv",
"eco": "B00",
"moves": "1. e4 g5 2. d4 Bg7",
"name": "Borg Defense: Borg Gambit",
"aliases": {
"eco_js": "Borg Defense: Borg Gambit, 2. d4 Bg7",
"chessGraph": "Borg Gambit",
"icsbot": "Borg Gambit "
}
}
],
"1. e4 g5 2. d4 e6 3. c3 c5 4. dxc5 b6": [
{
"src": "eco_js",
"eco": "B00",
"moves": "1. e4 g5 2. d4 e6 3. c3 c5 4. dxc5 b6",
"name": "Borg Defense: Langhorst Gambit"
}
],
"1. e4 e6": [
{
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6",
"name": "French Defense",
"scid": "C00a",
"aliases": {
"scid": "French",
"ct": "French Defense, General",
"chessGraph": "French Defense; C00",
"icsbot": "French Defense; C00 "
},
"isEcoRoot": true,
"wikipediaSlug": "french-defense"
}
],
"1. e4 e6 2. d4 d5": [
{
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. d4 d5",
"name": "French Defense",
"scid": "C00x",
"aliases": {
"scid": "French: 2.d4 d5",
"eco_js": "French Defense, 2. d4 d5",
"ct": "French Defense, Main Line"
},
"wikipediaSlug": "french-defense"
}
],
"1. e4 e6 2. d4 d5 3. Be3": [
{
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. d4 d5 3. Be3",
"name": "French Defense: Alapin Gambit",
"scid": "C00x",
"aliases": {
"scid": "French: Alapin-Diemer Gambit (ADG)",
"chessGraph": "Alapin; Witaker Gambit, French",
"chronos": "French: Alapin variation",
"icsbot": "Alapin; Witaker Gambit, French "
},
"wikipediaSlug": "french-defense"
}
],
"1. e4 e6 2. d4 b5": [
{
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. d4 b5",
"name": "French Defense: Baeuerle Gambit",
"aliases": {
"chessGraph": "Baeuerle Gambit, French",
"icsbot": "Baeuerle Gambit, French "
},
"wikipediaSlug": "french-defense"
}
],
"1. e4 e6 2. b4": [
{
"src": "eco_tsv",
"eco": "C00",
"moves": "1. e4 e6 2. b4",
"name": "French Defense: Banzai-Leong Gambit",
"aliases": {
"chessGraph": "Banzai-Leong Gambit, French",
"icsbot": "Banzai-Leong Gambit, French "
},
"wikipediaSlug": "french-defense"
}
],
"1. d4 d5 2. Qd3": [
{
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. Qd3",
"name": "Amazon Attack"
}
],
"1. d4 d5 2. e4": [
{
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. e4",
"name": "Blackmar-Diemer Gambit",
"scid": "D00l",
"aliases": {
"scid": "Blackmar-Diemer Gambit (BDG): 2.e4",
"ct": "Blackmar-Diemer Gambit, General",
"chronos": "Blackmar gambit"
}
}
],
"1. d4 d5 2. e4 dxe4 3. Nc3 Nf6": [
{
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. e4 dxe4 3. Nc3 Nf6",
"name": "Blackmar-Diemer Gambit",
"aliases": {
"chessGraph": "Vienna Variation, BDG",
"icsbot": "Vienna Variation, BDG "
}
}
],
"1. d4 d5 2. e4 dxe4": [
{
"src": "scid",
"eco": "D00",
"moves": "1. d4 d5 2. e4 dxe4",
"name": "Blackmar-Diemer Gambit (BDG): 2.e4 dxe4",
"scid": "D00l"
}
],
"1. d4 d5 2. e4 dxe4 3. Nc3 Nf6 4. f3 exf3": [
{
"src": "eco_tsv",
"eco": "D00",
"moves": "1. d4 d5 2. e4 dxe4 3. Nc3 Nf6 4. f3 exf3",
"name": "Blackmar-Diemer Gambit Accepted",
"scid": "D00v",
"aliases": {
"scid": "Blackmar-Diemer: Accepted"
}
}
],
"1. d4 Nf6 2. c4 e6 3. g3": [
{
"src": "eco_tsv",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3",
"name": "Catalan Opening",
"scid": "E00g",
"aliases": {
"scid": "Catalan",
"ct": "Catalan Opening, General"
},
"wikipediaSlug": "catalan-opening"
}
],
"1. d4 Nf6 2. c4 e6 3. g3 d5": [
{
"src": "eco_tsv",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 d5",
"name": "Catalan Opening",
"scid": "E00n",
"aliases": {
"scid": "Catalan: 3...d5",
"eco_js": "Catalan Opening, 2. c4 e6 3. g3 d5",
"chessGraph": "E01"
},
"wikipediaSlug": "catalan-opening"
}
],
"1. d4 Nf6 2. c4 e6 3. g3 e5": [
{
"src": "eco_tsv",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 e5",
"name": "Catalan Opening: Hungarian Gambit",
"scid": "E00g",
"aliases": {
"scid": "Catalan: Hungarian Gambit",
"chessGraph": "Hungarian Gambit, Catalan",
"icsbot": "Hungarian Gambit, Catalan "
},
"wikipediaSlug": "catalan-opening"
}
],
"1. d4 Nf6 2. c4 e6 3. g3 Bb4+": [
{
"src": "scid",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 Bb4+",
"name": "Catalan: 3...Bb4+",
"scid": "E00i",
"wikipediaSlug": "catalan"
}
],
"1. d4 Nf6 2. c4 e6 3. g3 Bb4+ 4. Bd2": [
{
"src": "scid",
"eco": "E00",
"moves": "1. d4 Nf6 2. c4 e6 3. g3 Bb4+ 4. Bd2",
"name": "Catalan: 3...Bb4+ 4.Bd2",
"scid": "E00j",
"wikipediaSlug": "catalan"
}
]
}