diff --git a/package-lock.json b/package-lock.json index 8cf6b6d..53f4dbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,6 +112,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -699,6 +700,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -722,6 +724,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -743,6 +746,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2945,6 +2949,7 @@ "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2954,6 +2959,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2964,6 +2970,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3051,6 +3058,7 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -3587,6 +3595,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4107,6 +4116,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -5085,6 +5095,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5270,6 +5281,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7080,6 +7092,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -8066,6 +8079,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -10119,6 +10133,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10146,6 +10161,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11307,6 +11323,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11498,6 +11515,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11688,6 +11706,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12420,6 +12439,7 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/app/analysis/__tests__/page.test.tsx b/src/app/analysis/__tests__/page.test.tsx index db43817..19db74c 100644 --- a/src/app/analysis/__tests__/page.test.tsx +++ b/src/app/analysis/__tests__/page.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { DebugProvider } from "@/contexts/DebugContext"; jest.mock("next/navigation", () => ({ useRouter: jest.fn(() => ({ @@ -83,7 +84,11 @@ describe("AnalysisPage", () => { `; const loadGame = () => { - render(); + render( + + + + ); const textarea = screen.getByPlaceholderText(/Paste PGN or FEN here/i); fireEvent.change(textarea, { target: { value: samplePgn } }); fireEvent.click(screen.getByText(/Start Analysis/i)); @@ -125,7 +130,11 @@ describe("AnalysisPage", () => { }); it("shows an error when the notation cannot be parsed", () => { - render(); + render( + + + + ); const textarea = screen.getByPlaceholderText(/Paste PGN or FEN here/i); fireEvent.change(textarea, { target: { value: "invalid" } }); fireEvent.click(screen.getByText(/Start Analysis/i)); diff --git a/src/app/analysis/page.tsx b/src/app/analysis/page.tsx index 941e2ee..a3efe01 100644 --- a/src/app/analysis/page.tsx +++ b/src/app/analysis/page.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Chess } from "chess.js"; import { Chessboard } from "react-chessboard"; -import { Brain, ChevronLeft, ChevronRight, Loader2, ArrowLeft, Download } from "lucide-react"; +import { Brain, ChevronLeft, ChevronRight, Loader2, ArrowLeft, Download, PlayCircle, Upload, RotateCcw } from "lucide-react"; import { useRouter } from "next/navigation"; import Header from "@/components/Header"; @@ -20,6 +20,7 @@ import ReactMarkdown from "react-markdown"; import { useDebug } from "@/contexts/DebugContext"; import { GameImportModal } from "@/components/GameImportModal"; import { EvaluationBar } from "@/components/EvaluationBar"; +import { OpeningsModal } from "@/components/OpeningsModal"; interface MoveStep { san: string; @@ -65,14 +66,35 @@ export default function AnalysisPage() { const [comments, setComments] = useState>({}); const [chatSession, setChatSession] = useState(null); const [showImportModal, setShowImportModal] = useState(false); + const [showPlayModal, setShowPlayModal] = useState(false); + const [showOpeningsModal, setShowOpeningsModal] = useState(false); + const [playPersonality, setPlayPersonality] = useState(PERSONALITIES[0]); + const [playColor, setPlayColor] = useState<"white" | "black">("white"); + const [playStrength, setPlayStrength] = useState(15); useEffect(() => { const storedKey = localStorage.getItem("gemini_api_key"); const storedLang = localStorage.getItem("chess_tutor_language"); if (storedKey) setApiKey(storedKey); if (storedLang) setLanguage(storedLang as SupportedLanguage); + + // Check for pending analysis from saved game + const pendingAnalysis = localStorage.getItem("chess_tutor_pending_analysis"); + if (pendingAnalysis) { + localStorage.removeItem("chess_tutor_pending_analysis"); + setInput(pendingAnalysis); + setDetectedFormat(detectChessFormat(pendingAnalysis)); + // Load the game after a short delay to ensure stockfish is ready + setTimeout(() => { + loadGameFromPgnOrFen(pendingAnalysis); + }, 100); + } }, []); + useEffect(() => { + setPlayPersonality(selectedPersonality); + }, [selectedPersonality]); + useEffect(() => { const sf = new Stockfish(); setStockfish(sf); @@ -219,6 +241,31 @@ IMPORTANT: loadGameFromPgnOrFen(pgn); }; + const handleResetAnalysis = () => { + setInput(""); + setDetectedFormat(null); + setSteps([]); + setCurrentIndex(0); + setStepDetails({}); + setComments({}); + setInitialFen(DEFAULT_START); + evaluationCache.current = {}; + setEvaluationVersion(v => v + 1); + setError(null); + }; + + const handleStartGameFromPosition = () => { + const payload = { + fen: currentFen, + personalityId: playPersonality.id, + color: playColor, + stockfishDepth: playStrength, + }; + + localStorage.setItem("chess_tutor_pending_game", JSON.stringify(payload)); + router.push("/"); + }; + useEffect(() => { if (!stockfish || !currentFen) return; ensureEvaluation(currentFen); @@ -381,90 +428,137 @@ INSTRUCTIONS:
-
-
-
-

- {t.analysis.modeTitle} -

-

{t.analysis.modeDescription}

+ {/* Phase 1: Import View - shown when no game is loaded */} + {steps.length === 0 && ( +
+
+
+

+ {t.analysis.modeTitle} +

+

{t.analysis.modeDescription}

+
-
- - + +
+
+ +