Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR implements several upgrades to the chess book generation system, including refactored game-to-page conversion logic, improved board rendering, enhanced move highlighting, and code organization improvements.
- Extracted game pagination logic into a dedicated utility module with clustering-based game grouping
- Refactored board rendering to compute board state during game processing rather than in components
- Fixed move highlighting to correctly identify the position of interest in games
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/stringToNumber.ts | New utility for deterministic string-to-number conversion using hash-based approach |
| utils/convertGamesToPages.ts | Extracted and improved game pagination logic with length-based clustering and multi-page layout constraints |
| types.ts | Updated type definitions for ChessPage variants and Game structure to include board grid and move counts |
| package.json | Changed from private package to AGPL-3.0-only licensed |
| lichess.ts | Major refactoring: removed async, added analysis processing, moved board computation from components, switched from console.log to console.debug |
| components/Form/index.tsx | Removed await from now-synchronous processGame function call |
| components/Chess/MoveListTable.tsx | Fixed move highlighting to use correct ply calculation (removed -1 offset) |
| components/Chess/MoveListInline.tsx | Added move highlighting support with conditional span wrapping |
| components/Chess/ChessBoard.tsx | Simplified to render pre-computed board grid from game data, removing Chess.js dependency |
| components/Book/index.tsx | Refactored to use extracted utility functions, removed inline logic and debug console.log |
| components/Book/ThreeGamePage.tsx | Updated import path from lowercase 'page' to 'Page' |
| components/Book/SixGamePage.tsx | Enhanced layout with dynamic spacing based on move counts and conditional inline/table rendering |
| components/Book/Page.tsx | New file (renamed from page.tsx for proper casing) |
| components/Book/OneGamePage.tsx | Updated import path and removed unused Fragment import |
| components/Book/CoverPage.tsx | Updated import path from lowercase 'page' to 'Page' |
| components/Book/ContentPage.tsx | Minor formatting cleanup to consolidate template literal |
Comments suppressed due to low confidence (2)
utils/convertGamesToPages.ts:40
- Unused function distance.
function distance(a: Game, b: Game): number {
lichess.ts:176
- Avoid automated semicolon insertion (92% of all statements in the enclosing function have an explicit semicolon).
: randomNumberBetween(Math.max(0, Math.min(movesCount - 2, 3)), movesCount)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| const plyOfInterest = biggestDeltaPly !== -1 | ||
| ? biggestDeltaPly | ||
| : randomNumberBetween(Math.max(0, Math.min(movesCount - 2, 3)), movesCount) |
There was a problem hiding this comment.
Missing semicolon at the end of the statement. This should be : randomNumberBetween(Math.max(0, Math.min(movesCount - 2, 3)), movesCount); to maintain consistency with the rest of the codebase.
| : randomNumberBetween(Math.max(0, Math.min(movesCount - 2, 3)), movesCount) | |
| : randomNumberBetween(Math.max(0, Math.min(movesCount - 2, 3)), movesCount); |
| /** Distance between two games for clustering (percentile based). */ | ||
| function distance(a: Game, b: Game): number { | ||
| const longer = Math.max(ply(a), ply(b)); | ||
| const shorter = Math.min(ply(a), ply(b)); | ||
| return (longer - shorter) / longer; // 0..1 | ||
| } | ||
|
|
There was a problem hiding this comment.
The distance function is defined but never used in this file. Consider removing it or documenting why it's kept for future use.
| /** Distance between two games for clustering (percentile based). */ | |
| function distance(a: Game, b: Game): number { | |
| const longer = Math.max(ply(a), ply(b)); | |
| const shorter = Math.min(ply(a), ply(b)); | |
| return (longer - shorter) / longer; // 0..1 | |
| } |
| games: [Game, Game, Game, Game], | ||
| } | { | ||
| type: 'six', | ||
| games: [Game, Game, Game, Game, Game | undefined, Game | undefined], |
There was a problem hiding this comment.
The six page type allows 5th and 6th games to be undefined, but convertGamesToPages.ts line 140 casts a 3-game array to this type with the comment "6-page with only 3 games". This creates a type mismatch where positions 4-6 are undefined but the type only allows 5-6 to be undefined. The type should be [Game, Game, Game, Game | undefined, Game | undefined, Game | undefined] to accurately reflect the usage.
| games: [Game, Game, Game, Game, Game | undefined, Game | undefined], | |
| games: [Game, Game, Game, Game | undefined, Game | undefined, Game | undefined], |
| chess.loadPgn(game.pgn); | ||
| const turn = chess.turn(); | ||
| while (chess.moveNumber() > 1 && (chess.moveNumber() - 1) * 2 + Number(turn === 'b') > plyOfInterest) { | ||
| chess.undo(); | ||
| } | ||
|
|
There was a problem hiding this comment.
The Chess instance is created and the full PGN is loaded on line 178-179, then moves are undone one by one in a while loop (lines 181-183). For games with many moves, this is inefficient. Consider loading only the moves up to plyOfInterest instead of loading all moves and then undoing them.
| chess.loadPgn(game.pgn); | |
| const turn = chess.turn(); | |
| while (chess.moveNumber() > 1 && (chess.moveNumber() - 1) * 2 + Number(turn === 'b') > plyOfInterest) { | |
| chess.undo(); | |
| } | |
| // Play only up to plyOfInterest moves | |
| for (let i = 0; i < plyOfInterest; i++) { | |
| if (moves[i]) { | |
| chess.move(moves[i], { sloppy: true }); | |
| } | |
| } | |
| const turn = chess.turn(); |
| <ContentPage {...props}> | ||
| <div className="grid grid-cols-3 gap-4"> | ||
| {props.games.map((game, i) => ( | ||
| <div className={`grid grid-cols-3 gap-x-4 ${yGap} ${totalMoveRows}`}> |
There was a problem hiding this comment.
The class string includes ${totalMoveRows} which outputs a number directly into the className. This appears to be a debugging artifact and will create an invalid CSS class. Consider removing this debug value from the className.
| <div className={`grid grid-cols-3 gap-x-4 ${yGap} ${totalMoveRows}`}> | |
| <div className={`grid grid-cols-3 gap-x-4 ${yGap}`}> |
| const poolMedian = medianPly(pool); | ||
| const candidates = pool | ||
| .map((_, i) => pool.slice(i, i + 3)) | ||
| .filter((g) => g.length === 3 && totalPly(g) <= 330) |
There was a problem hiding this comment.
The move limit check uses 330 half-moves (165 full moves), but the comment on line 60 says "165-move limit". This appears to be checking for 165 half-moves instead of 165 full moves. If the intention is to check for 165 full moves, the limit should be totalPly(g) <= 165 * 2 (330). If checking for 165 half-moves, update the comment. The current implementation is ambiguous.
| .filter((g) => { | ||
| const top = maxPly(g.slice(0, 3)); | ||
| const bot = maxPly(g.slice(3, 6)); | ||
| return top + bot <= 200; |
There was a problem hiding this comment.
The 100-move row limit check uses 200 half-moves. The comment on line 76 says "100-move row limit", but this checks for 100 half-moves per row (200 total), not 100 full moves. If the intention is 100 full moves per row, the limit should be top + bot <= 100 * 2 * 2 (400). Please clarify the intended constraint and update either the code or comment accordingly.
| } | ||
|
|
||
| /* 5. Done -------------------------------------------------------------- */ | ||
| console.log({ pageCount: pages.length }); |
There was a problem hiding this comment.
The console.log statement should either be removed for production code or changed to a proper logging mechanism. Debug logs like this can clutter production output.
| console.log({ pageCount: pages.length }); |
|
|
||
| const totalMoveRows = sortedByMoveCountGames[0].movesCount + (sortedByMoveCountGames[3]?.movesCount ?? 0) / 2; | ||
|
|
||
| let yGap = 'gap-y-4'; |
There was a problem hiding this comment.
The initial value of yGap is unused, since it is always overwritten.
| let yGap = 'gap-y-4'; | |
| let yGap; |
| // The game did not match the requirement of only showing wins or only losses. | ||
| console.log("filtering out " + game.id) | ||
| return; | ||
| console.debug("filtering out " + game.id) |
There was a problem hiding this comment.
Avoid automated semicolon insertion (92% of all statements in the enclosing function have an explicit semicolon).
| console.debug("filtering out " + game.id) | |
| console.debug("filtering out " + game.id); |
No description provided.