mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 16:03:45 +00:00
fix(main): schedule the relaunch exit pair once per process
Each app.relaunch() registration starts another replacement instance after exit, and renderer-side click guards cannot see each other across surfaces (error-boundary Restart, hydration toast, settings panes). Gate the schedule in the app:relaunch handler so concurrent or repeated invokes commit exactly one relaunch+exit.
This commit is contained in:
@@ -228,6 +228,23 @@ describe('registerAppHandlers', () => {
|
||||
expect(appExitMock).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('schedules a single relaunch exit no matter how many surfaces invoke it', async () => {
|
||||
registerAppHandlers({} as never, {})
|
||||
|
||||
// Two surfaces (error-boundary Restart, hydration toast) can race a relaunch;
|
||||
// each app.relaunch() registration would start another replacement instance.
|
||||
const first = Promise.resolve(handlers.get('app:relaunch')?.(null))
|
||||
const second = Promise.resolve(handlers.get('app:relaunch')?.(null))
|
||||
await first
|
||||
await second
|
||||
const third = Promise.resolve(handlers.get('app:relaunch')?.(null))
|
||||
await third
|
||||
await vi.advanceTimersByTimeAsync(150)
|
||||
|
||||
expect(relaunchAppMock).toHaveBeenCalledTimes(1)
|
||||
expect(appExitMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('marks restart as expected shutdown before quitting through the normal pipeline', async () => {
|
||||
const onBeforeRelaunch = vi.fn()
|
||||
registerAppHandlers({} as never, { onBeforeRelaunch })
|
||||
|
||||
@@ -288,9 +288,19 @@ export function registerAppHandlers(store: Store, options: RegisterAppHandlersOp
|
||||
|
||||
ipcMain.handle('app:getKeyboardLayoutSnapshot', () => readMacKeyboardLayoutSnapshot())
|
||||
|
||||
// Every app.relaunch() registration starts another replacement instance after
|
||||
// exit, and renderer-side click guards cannot see each other across surfaces
|
||||
// (error-boundary Restart, hydration toast, settings panes). Once the exit
|
||||
// pair is scheduled the process is committed, so later invokes must not
|
||||
// schedule a second one.
|
||||
let relaunchExitScheduled = false
|
||||
ipcMain.handle('app:relaunch', async () => {
|
||||
// Why: brief delay lets the renderer paint "Restarting…" before the window tears down.
|
||||
await runBeforeRelaunchCleanup(options.onBeforeRelaunch)
|
||||
if (relaunchExitScheduled) {
|
||||
return
|
||||
}
|
||||
relaunchExitScheduled = true
|
||||
setTimeout(() => {
|
||||
// Why: app.exit(0) skips before-quit, so destroy the Windows tray manually to avoid a stale icon.
|
||||
destroySystemTray()
|
||||
|
||||
Reference in New Issue
Block a user