diff --git a/build/splash.bmp b/build/splash.bmp new file mode 100644 index 0000000..ecc9a40 Binary files /dev/null and b/build/splash.bmp differ diff --git a/docs/release-notes/v1.1.1.md b/docs/release-notes/v1.1.1.md new file mode 100644 index 0000000..a5aa4d1 --- /dev/null +++ b/docs/release-notes/v1.1.1.md @@ -0,0 +1,16 @@ +## Fixes the Windows app not opening + +### Fixed +- **The app could fail to open on Windows, and then do nothing on every later launch.** If Windows reserved the app's local port (common with Hyper-V, WSL, or Docker), v1.1.0 never showed a window and kept running in the background. It now falls back to another port, and if it truly can't start it shows an error instead of hanging. +- **The engine didn't start in the desktop app.** The multi-threaded engine needs a browser feature that the desktop shell wasn't enabling. It's now turned on explicitly, so the full-strength, multi-core engine runs as intended. +- Launching the app while it's already open now brings up the existing window, and opens a new one if there isn't one. + +### Improved +- The Windows portable `.exe` now shows a splash screen while it unpacks, so you can tell it's starting. + +### If v1.1.0 wouldn't open for you +A hidden v1.1.0 process may still be running. Open Task Manager, end any **Chess Solver** processes (or run `taskkill /IM "Chess Solver.exe" /F`), then launch v1.1.1. + +### Downloads +- **Windows:** `Chess.Solver.1.1.1.exe`, a portable build. SmartScreen may warn because it's unsigned: choose *More info → Run anyway*. +- **macOS:** `Chess.Solver-1.1.1-universal.dmg` (Intel + Apple Silicon). It isn't notarized, so right-click the app → **Open** on first launch. diff --git a/electron/main.cjs b/electron/main.cjs index 6b4402d..805f54e 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -1,4 +1,4 @@ -const { app, BrowserWindow, shell } = require('electron'); +const { app, BrowserWindow, dialog, shell } = require('electron'); const path = require('path'); const http = require('http'); const fs = require('fs'); @@ -20,7 +20,9 @@ const MIME_TYPES = { // A fixed port keeps the app's origin stable between launches. localStorage // (theme, line count, think time) and the HTTP cache are keyed by origin, so a // random port silently reset settings every launch and forced a full -// recompile of the 100MB+ engine. Falls back to any free port if taken. +// recompile of the 100MB+ engine. Falls back to any free port if the +// preferred one can't be used — taken (EADDRINUSE) or, on Windows, inside a +// range reserved by Hyper-V/WSL/Docker (EACCES). const PREFERRED_PORT = 47813; // 'wasm-unsafe-eval' and blob: workers are required by the multi-threaded @@ -108,8 +110,11 @@ function startServer() { const server = http.createServer(handleRequest); const listen = (port) => server.listen(port, '127.0.0.1'); server.once('listening', () => resolve(server.address().port)); + let triedFallback = false; server.on('error', (err) => { - if (err.code === 'EADDRINUSE' && server.address() === null) { + if (!triedFallback && server.address() === null) { + triedFallback = true; + console.warn(`[server] port ${PREFERRED_PORT} unavailable (${err.code}); using a random port`); listen(0); } else { reject(err); @@ -119,6 +124,13 @@ function startServer() { }); } +// The multi-threaded Stockfish build needs SharedArrayBuffer. The server's +// COOP/COEP headers make browsers cross-origin isolate the page, but +// Electron's BrowserWindow doesn't reliably honor them (crossOriginIsolated +// stays false), which left SharedArrayBuffer undefined and the engine unable +// to start. Enable it explicitly; must run before the app is ready. +app.commandLine.appendSwitch('enable-features', 'SharedArrayBuffer'); + let appOrigin = null; function createWindow() { @@ -170,16 +182,29 @@ if (!gotInstanceLock) { const [win] = BrowserWindow.getAllWindows(); if (win) { if (win.isMinimized()) win.restore(); + win.show(); win.focus(); + } else if (appOrigin !== null) { + createWindow(); } }); } app.whenReady().then(async () => { if (!gotInstanceLock) return; - const port = await startServer(); - appOrigin = `http://127.0.0.1:${port}`; - createWindow(); + try { + const port = await startServer(); + appOrigin = `http://127.0.0.1:${port}`; + createWindow(); + } catch (err) { + // Never linger as a windowless background process: it would hold the + // single-instance lock and make every later launch silently do nothing. + dialog.showErrorBox( + 'Chess Solver could not start', + `The app's local server failed to start:\n\n${err && err.message ? err.message : err}` + ); + app.exit(1); + } }); // macOS convention: the app stays alive with no windows and reopens one when diff --git a/package-lock.json b/package-lock.json index b7ebf6b..c12a63d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chess-solver", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chess-solver", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "dependencies": { "@fontsource/dm-sans": "^5.3.0", diff --git a/package.json b/package.json index e5e8252..b5c9d2f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "chess-solver", "private": true, - "version": "1.1.0", + "version": "1.1.1", "description": "Desktop chess analysis tool powered by Stockfish 18 — set up any position and get engine-recommended moves with evaluations, tactical motifs, and principal variations.", "author": "ccharafeddine", "license": "MIT", @@ -37,6 +37,9 @@ "target": "portable", "icon": "build/icon.ico" }, + "portable": { + "splashImage": "build/splash.bmp" + }, "mac": { "target": [ { diff --git a/progress.txt b/progress.txt index 731f441..e185e48 100644 --- a/progress.txt +++ b/progress.txt @@ -1,5 +1,20 @@ # Chess Solver — Progress +## v1.1.1 (2026-09-24): Windows launch fix +- v1.1.0 .exe did nothing on launch. Two causes: (1) a listen error other + than EADDRINUSE on the fixed port (Windows returns EACCES for ports in + Hyper-V/WSL/Docker excluded ranges) rejected startServer unhandled, so no + window was created but the process lived on, holding the single-instance + lock and making every later launch exit silently. Now any listen error + falls back to a random port, and a total failure shows an error box and + exits. (2) Electron's BrowserWindow doesn't honor COOP/COEP + (crossOriginIsolated stays false), so SharedArrayBuffer was undefined and + the multi-threaded engine never started; fixed with + app.commandLine.appendSwitch('enable-features', 'SharedArrayBuffer'). + Both verified in real Electron under Xvfb (engine runs 3 threads, ~1M + nps; port-taken fallback; second-instance handoff). +- Portable .exe shows build/splash.bmp while it unpacks (~320MB per launch). + ## v1.1.0 (2026-09-24): engine speed/strength + reliability audit - The app had been shipping the SINGLE-threaded Stockfish build (public/stockfish.* byte-matched stockfish-18-single), so the Threads