- Open on a fresh 'You vs Opponent' game instead of the Immortal Game.
- Hide chess.js Seven-Tag-Roster placeholders ("?", "????.??.??") so a new
game reads just "You vs Opponent" rather than "You vs Opponent — ? — ????.??.??".
- Move the turn/move status onto the same row as the game info (top of the side
panel) instead of below the move list.
Gameplay (legal moves, move application, pressure maps, PGN parsing) now runs
entirely in the browser via a chess.js port of engine.py (static/engine.js +
vendored chess.min.js). The app no longer calls the server during play, so it
stays instant and works even when the backend is scaled to zero or offline.
- engine.js: line-for-line port of engine.py; parity-verified identical output
(pressure, FEN, status, SAN/UCI) across all 10 built-in games / 829 frames.
- app.js: replace all /api fetches with local engine calls; graceful PGN parse
errors; asset paths moved to root for static serving.
- Save games to localStorage + load from the game dropdown; export any game as
a .pgn file. Minimal additive UI in the existing export row.
- games.json generated from games.py (scripts/export_games.py).
- Production Dockerfile -> static-web-server (~9MB image, was ~150MB python).
- make dev -> bun static server; add make games / make parity targets.
- app.py kept as optional fallback (serves static app + /api routes).
- parity/: automated JS-vs-python engine parity test.
git diff stat:
16 files changed, 722 insertions(+), 128 deletions(-)
doMove only truncated moves/frames on the first divergence (forkPoint===null).
Subsequent rewind+move appended to the end of the line, corrupting the game
state irrecoverably. Always truncate to the current point before pushing.
Drop the dead forkedMoves state.
- All 5 playback buttons now use inline SVG triangles/bars — no more
emoji rendering on iOS/Android
- Pause icon in togglePlay() also switches to SVG
- Radio mode toggle becomes a segmented pill control (own row, full width)
- Piece/Board checkboxes each get their own tap-friendly row (min 2.5rem tall)