From f04e900354ee5c5864f0d1f645bf491d0dcbdb3f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 00:27:40 +0000 Subject: [PATCH] Fix double-launch flashing a port-in-use error app.quit() is asynchronous: when a second instance lost the single-instance lock, the top-level whenReady() handler still ran, booted a server into the occupied port, and showed the EADDRINUSE dialog before the queued quit landed. Gate the whenReady registration on holding the lock so the losing instance exits silently. The second-instance handler in the surviving app now also restores a minimized window (tray mode already recreated it) and brings it to the foreground - relaunching the app is now just 'show me the deck'. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01FNRQP2h3GUQVThuE2KVBEu --- electron/main.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/electron/main.js b/electron/main.js index 71bc47f..fd234e0 100644 --- a/electron/main.js +++ b/electron/main.js @@ -25,12 +25,21 @@ const SMOKE = Boolean(process.env.HG_SMOKE); // CI/smoke mode: no UI, auto-quit // Required for Windows notifications/toasts to render at all. app.setAppUserModelId('com.hypeghost.app'); -// Only one Hype Ghost at a time — a second launch just shows the existing window. +// Only one Hype Ghost at a time — a second launch hands focus to the first +// instance instead of starting a rival. NOTE: app.quit() is asynchronous, so +// losing the lock must also gate the whenReady() block below — otherwise the +// doomed second instance still boots a server into the occupied port and +// flashes a "port already in use" error before its quit lands. const gotLock = app.requestSingleInstanceLock(); if (!gotLock) { app.quit(); } else { - app.on('second-instance', () => showWindow()); + // Fired in the FIRST instance when a second launch is attempted: restore + // from minimized, recreate from tray mode, and bring to the foreground. + app.on('second-instance', () => { + if (win && !win.isDestroyed() && win.isMinimized()) win.restore(); + showWindow(); + }); } // Installed app: data lives in %APPDATA%/Hype Ghost (writable, survives @@ -163,7 +172,9 @@ function createTray() { tray.on('double-click', () => showWindow()); } -app.whenReady().then(() => { +// Gated on the lock: without this, the losing instance's whenReady still +// fires before its queued app.quit() and boots a second server (see above). +if (gotLock) app.whenReady().then(() => { ensureConfig(); try { server = startServer({