react-chess-puzzle-kit 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { type BoardThemeId } from 'react-chess-core';
2
+ import { type AnalysisEngineOptions, type BoardThemeId } from 'react-chess-core';
3
3
  export type LineTrainSide = 'w' | 'b';
4
4
  /** A line to drill: a start position plus the moves to walk, from one side. */
5
5
  export interface LineSpec {
@@ -38,6 +38,12 @@ export interface LineBoardWithControlsProps {
38
38
  onMove?: (feedback: LineMoveFeedback) => void;
39
39
  /** Omit to use {@link defaultRenderLineControls}. */
40
40
  renderControls?: (props: LineControlsRenderProps) => React.ReactNode;
41
+ /** Optional content above the board (e.g. eval bar), inside play-time engine context. */
42
+ renderAboveBoard?: (props: {
43
+ fen: string;
44
+ }) => React.ReactNode;
45
+ /** Stockfish options when {@link renderAboveBoard} is set. */
46
+ playTimeEngine?: AnalysisEngineOptions;
41
47
  boardWidth?: number;
42
48
  /** Delay before auto-playing each opponent move (ms). */
43
49
  opponentMoveDelayMs?: number;
@@ -49,4 +55,4 @@ export interface LineBoardWithControlsProps {
49
55
  * tactical puzzles. Mount one instance per drill (key it by line) so its
50
56
  * internal state resets between lines.
51
57
  */
52
- export declare const LineBoardWithControls: ({ theme, boardTheme, line, onComplete, onMove, renderControls, boardWidth, opponentMoveDelayMs, }: LineBoardWithControlsProps) => React.JSX.Element;
58
+ export declare const LineBoardWithControls: ({ theme, boardTheme, line, onComplete, onMove, renderControls, renderAboveBoard, playTimeEngine, boardWidth, opponentMoveDelayMs, }: LineBoardWithControlsProps) => React.JSX.Element;
@@ -82,6 +82,10 @@ export interface PuzzleBoardWithControlsProps {
82
82
  renderAnalysisSidebar?: (props: AnalysisSidebarRenderProps) => React.ReactNode;
83
83
  renderAnalysisContainer?: (props: AnalysisContainerRenderProps) => React.ReactNode;
84
84
  renderEngineEvaluation?: (props: EngineEvaluationRenderProps) => React.ReactNode;
85
+ /** Optional content above the board (e.g. eval bar), inside play-time engine context. */
86
+ renderAboveBoard?: (props: {
87
+ fen: string;
88
+ }) => React.ReactNode;
85
89
  /** Optional label below the board (e.g. side to move). */
86
90
  renderBoardCaption?: (props: BoardCaptionRenderProps) => React.ReactNode;
87
91
  /** Optional result feedback shown at the bottom of the controls column. */
@@ -95,6 +99,8 @@ export interface PuzzleBoardWithControlsProps {
95
99
  /** Custom board/sidebar placement (overrides {@link analysisLayout} grid). */
96
100
  renderAnalysisMain?: (props: AnalysisMainRenderProps) => React.ReactNode;
97
101
  engine?: AnalysisEngineOptions;
102
+ /** Background multipv on the setup position for instant refutation (silent on puzzles). */
103
+ playTimeEngine?: AnalysisEngineOptions;
98
104
  /** After a clean solve (no wrong move, hint, or solution reveal), load the next card. */
99
105
  autoAdvanceOnComplete?: boolean;
100
106
  /** With {@link autoAdvanceOnComplete}, also advance after finishing following a miss or hint. */
@@ -117,4 +123,4 @@ export interface PuzzleBoardWithControlsProps {
117
123
  refutationEngine?: AnalysisEngineOptions;
118
124
  answerArrowColor?: string;
119
125
  }
120
- export declare const PuzzleBoardWithControls: ({ theme, boardTheme, apiProxy, renderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth, analysisLayout, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete, autoAdvanceOnCompleteAfterIncorrect, autoAdvanceOnCompleteDelayMs, showCompletionRecap, revealAnswerOnIncorrect, showAnswerArrowOnIncorrect, allowRetryOnIncorrect, showRefutationOnIncorrect, autoShowWrongMoves, refutationEngine, answerArrowColor, }: PuzzleBoardWithControlsProps) => React.JSX.Element;
126
+ export declare const PuzzleBoardWithControls: ({ theme, boardTheme, apiProxy, renderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderAboveBoard, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth, analysisLayout, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete, autoAdvanceOnCompleteAfterIncorrect, autoAdvanceOnCompleteDelayMs, showCompletionRecap, revealAnswerOnIncorrect, showAnswerArrowOnIncorrect, allowRetryOnIncorrect, showRefutationOnIncorrect, autoShowWrongMoves, refutationEngine, answerArrowColor, }: PuzzleBoardWithControlsProps) => React.JSX.Element;
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { useBoardRevision, useCorrectMoveFeedback, useIncorrectMoveFeedback, useMissBoard, HighlightChessboard, DEFAULT_ANSWER_ARROW_COLOR, uciFromDrop, fenAtPlyFromStart, useSolutionLineRecap, lastMoveUciAtPly, ThemeProvider, AnalysisErrorBoundary, AnalysisBoardCore, AnalysisBoardLayout, AnalysisBoard, BoardCompleteCheckOverlay, DEFAULT_ANALYSIS_LAYOUT, AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, evaluateExpectedMoveDrop, fenAfterUci, boardSquareHighlightColors, analysisBoardHighlightColors } from 'react-chess-core';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { useBoardRevision, useCorrectMoveFeedback, useIncorrectMoveFeedback, useMissBoard, HighlightChessboard, DEFAULT_ANSWER_ARROW_COLOR, uciFromDrop, fenAtPlyFromStart, useSolutionLineRecap, lastMoveUciAtPly, isAnalyzableFen, ThemeProvider, AnalysisErrorBoundary, AnalysisBoardCore, AnalysisBoardLayout, AnalysisBoard, PlayTimeEngineProvider, BoardCompleteCheckOverlay, DEFAULT_ANALYSIS_LAYOUT, AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, evaluateExpectedMoveDrop, fenAfterUci, boardSquareHighlightColors, analysisBoardHighlightColors } from 'react-chess-core';
4
4
  export { DEFAULT_ANALYSIS_LAYOUT } from 'react-chess-core';
5
5
  import { Chess } from 'chess.js';
6
6
 
@@ -61,7 +61,7 @@ const EMPTY_BOARD_FEN = '8/8/8/8/8/8/8/8 w - - 0 1';
61
61
  * visible while the next position loads so layout and perspective do not flicker.
62
62
  */
63
63
  const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth, onResumeCorrect, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect = false, autoShowWrongMoves = true, refutationEngine, answerArrowColor = DEFAULT_ANSWER_ARROW_COLOR, positionLocked = false, onMissFeedbackChange, recapBoard = null, }) => {
64
- var _a, _b, _c, _d, _e, _f, _g;
64
+ var _a, _b, _c, _d, _e;
65
65
  const [showAnswerArrow, setShowAnswerArrow] = useState(false);
66
66
  const [incorrectActive, setIncorrectActive] = useState(false);
67
67
  const attemptMissedRef = useRef(false);
@@ -98,16 +98,14 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
98
98
  autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
99
99
  engineOptions: refutationEngine,
100
100
  });
101
- const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
101
+ const missPhase = missBoard.phase;
102
102
  const answerArrowVisible = useRefutation
103
103
  ? incorrectActive && missPhase === 'answer'
104
104
  : showAnswerArrow;
105
- const overlayIncorrectSquare = transientIncorrectSquare !== null && transientIncorrectSquare !== void 0 ? transientIncorrectSquare : (useRefutation && incorrectActive
106
- ? missBoard.missSequence.display.incorrectMoveSquare
107
- : null);
108
- const refutationMoveSquare = useRefutation && incorrectActive
109
- ? missBoard.missSequence.display.refutationMoveSquare
110
- : null;
105
+ const overlayIncorrectSquare = useRefutation && incorrectActive
106
+ ? missBoard.incorrectMoveSquare
107
+ : transientIncorrectSquare;
108
+ const refutationMoveSquare = useRefutation && incorrectActive ? missBoard.refutationMoveSquare : null;
111
109
  useEffect(() => {
112
110
  setShowAnswerArrow(false);
113
111
  setIncorrectActive(false);
@@ -122,14 +120,13 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
122
120
  position,
123
121
  ]);
124
122
  useEffect(() => {
125
- var _a, _b;
126
123
  if (!onMissFeedbackChange) {
127
124
  return;
128
125
  }
129
126
  if (useRefutation && incorrectActive) {
130
127
  onMissFeedbackChange({
131
128
  refutationSan: missBoard.refutation.refutationSan,
132
- phase: (_b = (_a = missBoard.missSequence.sequence) === null || _a === void 0 ? void 0 : _a.phase) !== null && _b !== void 0 ? _b : null,
129
+ phase: missBoard.phase,
133
130
  answerArrowVisible,
134
131
  });
135
132
  return;
@@ -146,7 +143,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
146
143
  }, [
147
144
  answerArrowVisible,
148
145
  incorrectActive,
149
- (_d = missBoard.missSequence.sequence) === null || _d === void 0 ? void 0 : _d.phase,
146
+ missBoard.phase,
150
147
  missBoard.refutation.refutationSan,
151
148
  onMissFeedbackChange,
152
149
  showAnswerArrow,
@@ -186,12 +183,8 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
186
183
  ? recapBoard.lastMoveUci
187
184
  : useRefutation && incorrectActive
188
185
  ? missBoard.lastMoveUci
189
- : ((_e = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _e !== void 0 ? _e : null);
190
- const missLocked = useRefutation &&
191
- incorrectActive &&
192
- (missBoard.boardAnimating ||
193
- missPhase === 'wrong' ||
194
- missPhase === 'refutation');
186
+ : ((_c = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _c !== void 0 ? _c : null);
187
+ const missLocked = useRefutation && incorrectActive && missBoard.inputLocked;
195
188
  const arePiecesDraggable = !isRecapping &&
196
189
  position !== null &&
197
190
  !positionLocked &&
@@ -221,7 +214,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
221
214
  });
222
215
  if (!guess.accepted) {
223
216
  attemptMissedRef.current = true;
224
- showIncorrectMove(useRefutation ? targetSquare : sourceSquare);
217
+ if (!useRefutation) {
218
+ showIncorrectMove(sourceSquare);
219
+ }
225
220
  onFeedback({
226
221
  index: position.getIndex(),
227
222
  guess: { sourceSquare, targetSquare, piece },
@@ -301,7 +296,11 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
301
296
  showCorrectMove(targetSquare, finishCorrectFeedback);
302
297
  return true;
303
298
  };
304
- return hasBoard ? (jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_f = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _f !== void 0 ? _f : ''), hintSquare: isRecapping ? null : ((_g = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _g !== void 0 ? _g : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping ? recapBoard.animationDuration : 0 }, revision)) : null;
299
+ return hasBoard ? (jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_d = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _d !== void 0 ? _d : ''), hintSquare: isRecapping ? null : ((_e = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _e !== void 0 ? _e : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping
300
+ ? recapBoard.animationDuration
301
+ : useRefutation && incorrectActive
302
+ ? missBoard.animationDuration
303
+ : 0 }, revision)) : null;
305
304
  };
306
305
 
307
306
  const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, answerArrowColor, }) => (jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, answerArrowColor: answerArrowColor }));
@@ -370,6 +369,7 @@ const usePuzzleCompletionRecap = ({ source, active, onComplete, }) => {
370
369
  segmentStartFen: startFen,
371
370
  setupUci,
372
371
  onComplete,
372
+ completeImmediatelyWhenNoMisses: true,
373
373
  resolveFen,
374
374
  });
375
375
  };
@@ -879,8 +879,8 @@ const buildCompletionRecapSource = (position, missedIndices) => {
879
879
  setupUci: startIndex > 0 ? (_b = movesUci[startIndex - 1]) !== null && _b !== void 0 ? _b : null : null,
880
880
  };
881
881
  };
882
- const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
883
- var _a, _b, _c, _d;
882
+ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderAboveBoard, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
883
+ var _a, _b, _c, _d, _e;
884
884
  const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
885
885
  const stackControlsBelow = useStackPuzzleControlsBelow();
886
886
  const controlsPlacement = stackControlsBelow
@@ -1176,8 +1176,12 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1176
1176
  return;
1177
1177
  }
1178
1178
  completionFlowStartedRef.current = true;
1179
+ if (missedMoveIndices.length === 0) {
1180
+ setCompletionRecapDone(true);
1181
+ return;
1182
+ }
1179
1183
  setCompletionCheckVisible(true);
1180
- }, [loadingNextPuzzle, resultStatus, showCompletionRecap]);
1184
+ }, [loadingNextPuzzle, missedMoveIndices, resultStatus, showCompletionRecap]);
1181
1185
  useEffect(() => {
1182
1186
  if (!completionCheckVisible) {
1183
1187
  return;
@@ -1206,32 +1210,38 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1206
1210
  (position.isSolutionRevealed() || !position.isFinished()),
1207
1211
  };
1208
1212
  const analysisSnapshot = analysis.isOpen && analysis.snapshot ? analysis.snapshot : null;
1213
+ const setupFen = (_a = position === null || position === void 0 ? void 0 : position.fen()) !== null && _a !== void 0 ? _a : '';
1214
+ const playTimeEnabled = Boolean(position) &&
1215
+ !(position === null || position === void 0 ? void 0 : position.isFinished()) &&
1216
+ !analysisSnapshot &&
1217
+ isAnalyzableFen(setupFen);
1218
+ const resolvedPlayTimeEngine = useMemo(() => (Object.assign({ scriptUrl: engine === null || engine === void 0 ? void 0 : engine.scriptUrl }, playTimeEngine)), [engine === null || engine === void 0 ? void 0 : engine.scriptUrl, playTimeEngine]);
1209
1219
  const resolvedAnalysisBoardWidth = analysisBoardWidth !== null && analysisBoardWidth !== void 0 ? analysisBoardWidth : analysisLayout.boardWidth;
1210
1220
  const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
1211
1221
  renderAnalysisContainer &&
1212
1222
  (renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
1213
- return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsx(AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsx(AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsx(AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsx(AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle ||
1214
- completionCheckVisible ||
1215
- isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1216
- ? {
1217
- fen: completionRecap.fen,
1218
- lastMoveUci: completionRecap.lastMoveUci,
1219
- customArrows: completionRecap.customArrows,
1220
- animationDuration: completionRecap.animationDuration,
1221
- }
1222
- : null }) }), completionCheckVisible && (jsx(BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1223
- ? 'partial'
1224
- : 'success' }))] }), renderBoardCaption && (jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1225
- sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
1223
+ return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsx(AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsx(AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsx(AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsx(AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxs(PlayTimeEngineProvider, { fen: setupFen, enabled: playTimeEnabled, options: resolvedPlayTimeEngine, children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: setupFen }), jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle ||
1224
+ completionCheckVisible ||
1225
+ isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1226
+ ? {
1227
+ fen: completionRecap.fen,
1228
+ lastMoveUci: completionRecap.lastMoveUci,
1229
+ customArrows: completionRecap.customArrows,
1230
+ animationDuration: completionRecap.animationDuration,
1231
+ }
1232
+ : null }) }), completionCheckVisible && (jsx(BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1233
+ ? 'partial'
1234
+ : 'success' }))] })] }), renderBoardCaption && (jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1235
+ sideToMove: (_b = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _b !== void 0 ? _b : null,
1226
1236
  playerColor: position
1227
1237
  ? position.getPlayerColor()
1228
1238
  : null,
1229
1239
  incorrectAttempt: resultStatus === 'incorrect',
1230
1240
  complete: resultStatus === 'complete',
1231
1241
  cleanSolve: !hasIncorrectAttempt,
1232
- refutationSan: (_b = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan) !== null && _b !== void 0 ? _b : null,
1233
- missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
1234
- answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
1242
+ refutationSan: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan) !== null && _c !== void 0 ? _c : null,
1243
+ missPhase: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _d !== void 0 ? _d : null,
1244
+ answerArrowVisible: (_e = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _e !== void 0 ? _e : false,
1235
1245
  completedAfterMiss,
1236
1246
  hintUsed,
1237
1247
  }) }))] }), jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
@@ -1286,7 +1296,7 @@ const boardOrientationForLine = (side) => side === 'b' ? 'black' : 'white';
1286
1296
  * tactical puzzles. Mount one instance per drill (key it by line) so its
1287
1297
  * internal state resets between lines.
1288
1298
  */
1289
- const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1299
+ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, renderAboveBoard, playTimeEngine, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1290
1300
  const chessRef = useRef(new Chess(line.startFen));
1291
1301
  const perMoveRef = useRef([]);
1292
1302
  const completedRef = useRef(false);
@@ -1411,7 +1421,9 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1411
1421
  const controlsPlacement = stackControlsBelow
1412
1422
  ? 'below'
1413
1423
  : 'beside';
1414
- return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1424
+ const playTimeEnabled = isAnalyzableFen(boardFen);
1425
+ const boardColumn = (jsxs(Fragment, { children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: boardFen }), jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) })] }));
1426
+ return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: renderAboveBoard ? (jsx(PlayTimeEngineProvider, { fen: boardFen, enabled: playTimeEnabled, options: playTimeEngine, children: boardColumn })) : (boardColumn) }), jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1415
1427
  trainSide: line.trainSide,
1416
1428
  moveNumber,
1417
1429
  total,
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ const EMPTY_BOARD_FEN = '8/8/8/8/8/8/8/8 w - - 0 1';
62
62
  * visible while the next position loads so layout and perspective do not flicker.
63
63
  */
64
64
  const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth, onResumeCorrect, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect = false, autoShowWrongMoves = true, refutationEngine, answerArrowColor = reactChessCore.DEFAULT_ANSWER_ARROW_COLOR, positionLocked = false, onMissFeedbackChange, recapBoard = null, }) => {
65
- var _a, _b, _c, _d, _e, _f, _g;
65
+ var _a, _b, _c, _d, _e;
66
66
  const [showAnswerArrow, setShowAnswerArrow] = react.useState(false);
67
67
  const [incorrectActive, setIncorrectActive] = react.useState(false);
68
68
  const attemptMissedRef = react.useRef(false);
@@ -99,16 +99,14 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
99
99
  autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
100
100
  engineOptions: refutationEngine,
101
101
  });
102
- const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
102
+ const missPhase = missBoard.phase;
103
103
  const answerArrowVisible = useRefutation
104
104
  ? incorrectActive && missPhase === 'answer'
105
105
  : showAnswerArrow;
106
- const overlayIncorrectSquare = transientIncorrectSquare !== null && transientIncorrectSquare !== void 0 ? transientIncorrectSquare : (useRefutation && incorrectActive
107
- ? missBoard.missSequence.display.incorrectMoveSquare
108
- : null);
109
- const refutationMoveSquare = useRefutation && incorrectActive
110
- ? missBoard.missSequence.display.refutationMoveSquare
111
- : null;
106
+ const overlayIncorrectSquare = useRefutation && incorrectActive
107
+ ? missBoard.incorrectMoveSquare
108
+ : transientIncorrectSquare;
109
+ const refutationMoveSquare = useRefutation && incorrectActive ? missBoard.refutationMoveSquare : null;
112
110
  react.useEffect(() => {
113
111
  setShowAnswerArrow(false);
114
112
  setIncorrectActive(false);
@@ -123,14 +121,13 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
123
121
  position,
124
122
  ]);
125
123
  react.useEffect(() => {
126
- var _a, _b;
127
124
  if (!onMissFeedbackChange) {
128
125
  return;
129
126
  }
130
127
  if (useRefutation && incorrectActive) {
131
128
  onMissFeedbackChange({
132
129
  refutationSan: missBoard.refutation.refutationSan,
133
- phase: (_b = (_a = missBoard.missSequence.sequence) === null || _a === void 0 ? void 0 : _a.phase) !== null && _b !== void 0 ? _b : null,
130
+ phase: missBoard.phase,
134
131
  answerArrowVisible,
135
132
  });
136
133
  return;
@@ -147,7 +144,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
147
144
  }, [
148
145
  answerArrowVisible,
149
146
  incorrectActive,
150
- (_d = missBoard.missSequence.sequence) === null || _d === void 0 ? void 0 : _d.phase,
147
+ missBoard.phase,
151
148
  missBoard.refutation.refutationSan,
152
149
  onMissFeedbackChange,
153
150
  showAnswerArrow,
@@ -187,12 +184,8 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
187
184
  ? recapBoard.lastMoveUci
188
185
  : useRefutation && incorrectActive
189
186
  ? missBoard.lastMoveUci
190
- : ((_e = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _e !== void 0 ? _e : null);
191
- const missLocked = useRefutation &&
192
- incorrectActive &&
193
- (missBoard.boardAnimating ||
194
- missPhase === 'wrong' ||
195
- missPhase === 'refutation');
187
+ : ((_c = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _c !== void 0 ? _c : null);
188
+ const missLocked = useRefutation && incorrectActive && missBoard.inputLocked;
196
189
  const arePiecesDraggable = !isRecapping &&
197
190
  position !== null &&
198
191
  !positionLocked &&
@@ -222,7 +215,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
222
215
  });
223
216
  if (!guess.accepted) {
224
217
  attemptMissedRef.current = true;
225
- showIncorrectMove(useRefutation ? targetSquare : sourceSquare);
218
+ if (!useRefutation) {
219
+ showIncorrectMove(sourceSquare);
220
+ }
226
221
  onFeedback({
227
222
  index: position.getIndex(),
228
223
  guess: { sourceSquare, targetSquare, piece },
@@ -302,7 +297,11 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
302
297
  showCorrectMove(targetSquare, finishCorrectFeedback);
303
298
  return true;
304
299
  };
305
- return hasBoard ? (jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_f = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _f !== void 0 ? _f : ''), hintSquare: isRecapping ? null : ((_g = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _g !== void 0 ? _g : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping ? recapBoard.animationDuration : 0 }, revision)) : null;
300
+ return hasBoard ? (jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_d = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _d !== void 0 ? _d : ''), hintSquare: isRecapping ? null : ((_e = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _e !== void 0 ? _e : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping
301
+ ? recapBoard.animationDuration
302
+ : useRefutation && incorrectActive
303
+ ? missBoard.animationDuration
304
+ : 0 }, revision)) : null;
306
305
  };
307
306
 
308
307
  const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, answerArrowColor, }) => (jsxRuntime.jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, answerArrowColor: answerArrowColor }));
@@ -371,6 +370,7 @@ const usePuzzleCompletionRecap = ({ source, active, onComplete, }) => {
371
370
  segmentStartFen: startFen,
372
371
  setupUci,
373
372
  onComplete,
373
+ completeImmediatelyWhenNoMisses: true,
374
374
  resolveFen,
375
375
  });
376
376
  };
@@ -880,8 +880,8 @@ const buildCompletionRecapSource = (position, missedIndices) => {
880
880
  setupUci: startIndex > 0 ? (_b = movesUci[startIndex - 1]) !== null && _b !== void 0 ? _b : null : null,
881
881
  };
882
882
  };
883
- const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = reactChessCore.AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
884
- var _a, _b, _c, _d;
883
+ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderAboveBoard, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = reactChessCore.AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
884
+ var _a, _b, _c, _d, _e;
885
885
  const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
886
886
  const stackControlsBelow = useStackPuzzleControlsBelow();
887
887
  const controlsPlacement = stackControlsBelow
@@ -1177,8 +1177,12 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1177
1177
  return;
1178
1178
  }
1179
1179
  completionFlowStartedRef.current = true;
1180
+ if (missedMoveIndices.length === 0) {
1181
+ setCompletionRecapDone(true);
1182
+ return;
1183
+ }
1180
1184
  setCompletionCheckVisible(true);
1181
- }, [loadingNextPuzzle, resultStatus, showCompletionRecap]);
1185
+ }, [loadingNextPuzzle, missedMoveIndices, resultStatus, showCompletionRecap]);
1182
1186
  react.useEffect(() => {
1183
1187
  if (!completionCheckVisible) {
1184
1188
  return;
@@ -1207,32 +1211,38 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1207
1211
  (position.isSolutionRevealed() || !position.isFinished()),
1208
1212
  };
1209
1213
  const analysisSnapshot = analysis.isOpen && analysis.snapshot ? analysis.snapshot : null;
1214
+ const setupFen = (_a = position === null || position === void 0 ? void 0 : position.fen()) !== null && _a !== void 0 ? _a : '';
1215
+ const playTimeEnabled = Boolean(position) &&
1216
+ !(position === null || position === void 0 ? void 0 : position.isFinished()) &&
1217
+ !analysisSnapshot &&
1218
+ reactChessCore.isAnalyzableFen(setupFen);
1219
+ const resolvedPlayTimeEngine = react.useMemo(() => (Object.assign({ scriptUrl: engine === null || engine === void 0 ? void 0 : engine.scriptUrl }, playTimeEngine)), [engine === null || engine === void 0 ? void 0 : engine.scriptUrl, playTimeEngine]);
1210
1220
  const resolvedAnalysisBoardWidth = analysisBoardWidth !== null && analysisBoardWidth !== void 0 ? analysisBoardWidth : analysisLayout.boardWidth;
1211
1221
  const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
1212
1222
  renderAnalysisContainer &&
1213
1223
  (renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
1214
- return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsxRuntime.jsx(reactChessCore.AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsxRuntime.jsx(reactChessCore.AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxRuntime.jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle ||
1215
- completionCheckVisible ||
1216
- isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1217
- ? {
1218
- fen: completionRecap.fen,
1219
- lastMoveUci: completionRecap.lastMoveUci,
1220
- customArrows: completionRecap.customArrows,
1221
- animationDuration: completionRecap.animationDuration,
1222
- }
1223
- : null }) }), completionCheckVisible && (jsxRuntime.jsx(reactChessCore.BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1224
- ? 'partial'
1225
- : 'success' }))] }), renderBoardCaption && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1226
- sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
1224
+ return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsxRuntime.jsx(reactChessCore.AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsxRuntime.jsx(reactChessCore.AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxRuntime.jsxs(reactChessCore.PlayTimeEngineProvider, { fen: setupFen, enabled: playTimeEnabled, options: resolvedPlayTimeEngine, children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: setupFen }), jsxRuntime.jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle ||
1225
+ completionCheckVisible ||
1226
+ isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1227
+ ? {
1228
+ fen: completionRecap.fen,
1229
+ lastMoveUci: completionRecap.lastMoveUci,
1230
+ customArrows: completionRecap.customArrows,
1231
+ animationDuration: completionRecap.animationDuration,
1232
+ }
1233
+ : null }) }), completionCheckVisible && (jsxRuntime.jsx(reactChessCore.BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1234
+ ? 'partial'
1235
+ : 'success' }))] })] }), renderBoardCaption && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1236
+ sideToMove: (_b = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _b !== void 0 ? _b : null,
1227
1237
  playerColor: position
1228
1238
  ? position.getPlayerColor()
1229
1239
  : null,
1230
1240
  incorrectAttempt: resultStatus === 'incorrect',
1231
1241
  complete: resultStatus === 'complete',
1232
1242
  cleanSolve: !hasIncorrectAttempt,
1233
- refutationSan: (_b = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan) !== null && _b !== void 0 ? _b : null,
1234
- missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
1235
- answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
1243
+ refutationSan: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan) !== null && _c !== void 0 ? _c : null,
1244
+ missPhase: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _d !== void 0 ? _d : null,
1245
+ answerArrowVisible: (_e = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _e !== void 0 ? _e : false,
1236
1246
  completedAfterMiss,
1237
1247
  hintUsed,
1238
1248
  }) }))] }), jsxRuntime.jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
@@ -1287,7 +1297,7 @@ const boardOrientationForLine = (side) => side === 'b' ? 'black' : 'white';
1287
1297
  * tactical puzzles. Mount one instance per drill (key it by line) so its
1288
1298
  * internal state resets between lines.
1289
1299
  */
1290
- const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1300
+ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, renderAboveBoard, playTimeEngine, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1291
1301
  const chessRef = react.useRef(new chess_js.Chess(line.startFen));
1292
1302
  const perMoveRef = react.useRef([]);
1293
1303
  const completedRef = react.useRef(false);
@@ -1412,7 +1422,9 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1412
1422
  const controlsPlacement = stackControlsBelow
1413
1423
  ? 'below'
1414
1424
  : 'beside';
1415
- return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1425
+ const playTimeEnabled = reactChessCore.isAnalyzableFen(boardFen);
1426
+ const boardColumn = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: boardFen }), jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) })] }));
1427
+ return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: renderAboveBoard ? (jsxRuntime.jsx(reactChessCore.PlayTimeEngineProvider, { fen: boardFen, enabled: playTimeEnabled, options: playTimeEngine, children: boardColumn })) : (boardColumn) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1416
1428
  trainSide: line.trainSide,
1417
1429
  moveNumber,
1418
1430
  total,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-chess-puzzle-kit",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "React chess puzzle kit: play, controls, analysis, and browser Stockfish for endchess.training and other apps",
5
5
  "license": "MIT",
6
6
  "author": "Robert Blackwell",
@@ -76,7 +76,7 @@
76
76
  "jest": "^29.7.0",
77
77
  "jest-environment-jsdom": "^29.7.0",
78
78
  "react": "^18.3.1",
79
- "react-chess-core": "^0.1.8",
79
+ "react-chess-core": "^0.1.12",
80
80
  "react-chessboard": "^4.7.1",
81
81
  "react-dom": "^18.3.1",
82
82
  "storybook": "^8.2.9",