diff --git a/electron/auto-updater.ts b/electron/auto-updater.ts index 95414268..2d391bb3 100644 --- a/electron/auto-updater.ts +++ b/electron/auto-updater.ts @@ -11,6 +11,18 @@ const IS_MAC = process.platform === "darwin"; let checkTimer: ReturnType | null = null; let macUpdater: MacCustomUpdater | null = null; +/** + * When true, the app is quitting because the user clicked "Restart" in the + * update panel. The before-quit handler uses this to decide whether to + * relaunch after installing the update. + */ +let updateRestartRequested = false; + +/** Returns true if the current quit was initiated by an update restart. */ +export function isUpdateRestart(): boolean { + return updateRestartRequested; +} + export function setupAutoUpdater(window: BrowserWindow): void { const checkFn = IS_MAC ? setupMacUpdater(window) @@ -37,10 +49,16 @@ export function stopAutoUpdater(): void { function setupMacUpdater(window: BrowserWindow): () => void { macUpdater = new MacCustomUpdater(window); - macUpdater.registerAutoInstallOnQuit(); + macUpdater.registerAutoInstallOnQuit(() => updateRestartRequested); ipcMain.on("updater:install", () => { - macUpdater?.quitAndInstall(); + // Instead of calling quitAndInstall() directly (which would fire + // app.quit() before the save-canvas dialog has a chance to run), + // set a flag and trigger the normal close flow. The before-quit + // handler in registerAutoInstallOnQuit will pick up the flag and + // run the install script with relaunch=true. + updateRestartRequested = true; + window.close(); // goes through the save-canvas dialog first }); ipcMain.handle("updater:check", () => @@ -91,7 +109,16 @@ function setupElectronUpdater(window: BrowserWindow): () => void { }); ipcMain.on("updater:install", () => { - autoUpdater.quitAndInstall(false, true); + // Same pattern as the mac updater: go through the save-canvas flow + // first, then install on before-quit. + updateRestartRequested = true; + window.close(); + }); + + app.on("before-quit", () => { + if (updateRestartRequested) { + autoUpdater.quitAndInstall(false, true); + } }); ipcMain.handle("updater:check", () => diff --git a/electron/mac-updater.ts b/electron/mac-updater.ts index a289247f..87ac506c 100644 --- a/electron/mac-updater.ts +++ b/electron/mac-updater.ts @@ -255,15 +255,19 @@ export class MacCustomUpdater { } /** - * Register a before-quit handler that silently replaces the .app - * when the user quits normally and an update is pending. - * Unlike explicit quitAndInstall, this does NOT relaunch the app. + * Register a before-quit handler that replaces the .app when + * the app quits and an update is pending. + * + * @param shouldRelaunch - callback that returns true when the user + * explicitly requested "Restart to update" (so the app relaunches + * after installing), false for a normal quit (silent install, no + * relaunch). */ - registerAutoInstallOnQuit(): void { + registerAutoInstallOnQuit(shouldRelaunch: () => boolean): void { app.on("before-quit", () => { if (this.pendingUpdate && !this.installing) { this.installing = true; - this.runInstallScript(false); + this.runInstallScript(shouldRelaunch()); } }); } diff --git a/src/components/UpdateModal.tsx b/src/components/UpdateModal.tsx index 3d929450..57ab1653 100644 --- a/src/components/UpdateModal.tsx +++ b/src/components/UpdateModal.tsx @@ -13,8 +13,9 @@ export function UpdateModal({ onClose }: Props) { const backdropRef = useRef(null); const handleInstall = useCallback(() => { + onClose(); window.termcanvas.updater.install(); - }, []); + }, [onClose]); const handleRetry = useCallback(() => { useUpdaterStore.setState({ status: "checking", errorMessage: null });