chess-pressure/dev-server.js
Michael Pilosov ba1d993676 feat: move engine fully client-side; serve as static; add save/export
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(-)
2026-06-25 19:41:55 +00:00

29 lines
1.1 KiB
JavaScript

// Lightweight static dev server for chess-pressure.
// Serves src/chess_pressure/static/ at the root, matching the production
// static-web-server layout. Files are read fresh per request, so editing a
// JS/CSS/HTML file and refreshing the browser picks up changes immediately.
//
// bun run dev-server.js # serves http://0.0.0.0:8888
// PORT=9000 bun run dev-server.js
const ROOT = new URL("./src/chess_pressure/static/", import.meta.url).pathname;
const PORT = Number(process.env.PORT || 8888);
Bun.serve({
port: PORT,
hostname: "0.0.0.0",
async fetch(req) {
let pathname = decodeURIComponent(new URL(req.url).pathname);
if (pathname === "/" || pathname === "") pathname = "/index.html";
// Strip leading slashes and neutralize any ".." path traversal.
const rel = pathname.replace(/^\/+/, "").replace(/\.\.(\/|\\|$)/g, "");
const file = Bun.file(ROOT + rel);
if (await file.exists()) {
return new Response(file, { headers: { "Cache-Control": "no-cache" } });
}
return new Response("Not found", { status: 404 });
},
});
console.log(`chess-pressure dev server → http://0.0.0.0:${PORT} (serving ${ROOT})`);