Mirrors the Export PGN dialog: Save now has White/Black fields (pre-filled,
placeholders blank) and writes the names back to headers + on-screen info, so a
saved game stores real player names instead of You/Opponent. Shared prefill/
write-back helpers between the two dialogs. Bump cache version.
Export PGN now opens a dialog with White/Black fields (pre-filled from the game
headers, placeholders treated as blank). Entered names are written back, so they
appear in the exported PGN, update the on-screen 'X vs Y' line, and persist if the
game is saved — instead of always exporting 'You vs Opponent'. Bump cache version.
Replaces the last native alert() calls (corrupted saved game / unparseable PGN)
and the delete confirm with the same <dialog>-based modalAlert/modalConfirm
helpers, so no flow relies on native popups that the browser may suppress.
Bump cache version.
The browser suppresses native confirm()/prompt() when the page isn't the active
tab ('dialog was suppressed because this page is not the active tab'). A
suppressed confirm() returns false, so the overwrite-on-save check silently
cancelled — no dialog, no overwrite. Replaced the prompt+confirm flow with a real
<dialog> save modal (name input + live 'this overwrites' warning, so Save is the
confirmation) and moved delete onto an in-app confirm dialog too. No native
popups in these flows now. Bump cache version.
saveCurrentGame always pushed a new entry, so re-saving under the same title
produced two dropdown entries. Now it warns + confirms, then replaces the
same-named game (case-insensitive), collapsing any existing duplicates into one.
Bump asset cache version.
- sw.js + manifest.json: cache-first service worker for instant repeat-visit
loads and full offline use. Registered only when window.CHESS_PWA is true.
- Disabled by default: an active SW serves returning visitors from cache, so
they'd stop hitting the server and disappear from the Fly logs. Enable at
build time with --build-arg ENABLE_CHESS_PWA=1 (Dockerfile flips the flag);
ENABLE_CHESS_PWA=1 make dev does the same locally.
- scripts/bump_version.sh + 'make bump': bump the asset cache version across
index.html (?v=N) and sw.js (CACHE) in sync. Required before deploy so the
service worker / browser cache pick up changed assets. Bumped v25 -> v26.
- README: document cache versioning + PWA flag.
New games have no reference line to return to, so rewinding to fix a finger-slip
no longer shows a 'forked' badge — corrections just overwrite silently. Loaded
games (built-in / uploaded / saved) keep 'Reset to original', but the badge now
reads 'Modified from original' (no move number), which fixes the stale-fork-point
bug: the indicator stayed pinned to the first divergence and ignored later
rewinds. Resettable correctly across any number of rewinds now.
- 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.
- btn-prev/next now use |◄ / ►| (single triangle + bar, was btn-start/end)
- btn-start/end now use |◄◄ / ►►| (double triangle + bar)
- playback-buttons row fills full width with flex:1 on all buttons
- outer two buttons (start/end) slightly smaller via flex:0.65 + 13px SVG
- 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)