fix(electron): re-open a window when a second launch finds none

On macOS `window-all-closed` deliberately keeps the process alive while
the 'closed' handler clears App.mainWindow, so the single-instance guard
could hand a launch to a process with no window: focusExistingWindow(null)
did nothing and the new process quit, leaving the user with nothing on
screen.

The second-instance handler now re-creates the window when none is live,
via the same path the macOS dock 'activate' event uses. onActivate's body
moved to a public App.ensureMainWindow() so both entry points share it.

Reported by Codex review on #1272.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 committed 2026-07-27 20:08:13 +02:00
1 parent 917691b80c
commit f3425f8957
5 files changed
+116 -10

No files matched your search

@@ -168,6 +168,42 @@ test.describe('Electron Settings', () => {
await closeElectronApp(relaunch);
});
test('@settings @electron re-opens a window when a second launch arrives with none open', async ({
dataDir,
}) => {
// Only macOS keeps the process alive after its last window closes;
// elsewhere `window-all-closed` quits and the next launch is a plain
// cold start.
test.skip(
process.platform !== 'darwin',
'macOS-only windowless-process behaviour'
);
const runningApp = await launchElectronApp(dataDir);
try {
await runningApp.electronApp.evaluate(({ BrowserWindow }) => {
for (const window of BrowserWindow.getAllWindows()) {
window.close();
}
});
await expect
.poll(() => runningApp.electronApp.windows().length)
.toBe(0);
const recreatedWindow =
runningApp.electronApp.waitForEvent('window');
const competing = await launchCompetingElectronInstance(dataDir);
expect(competing.timedOut).toBe(false);
expect(competing.exitCode).toBe(0);
// Without this the guard would quit the launch into nothing and
// leave the user staring at no window at all.
await expect((await recreatedWindow).locator('body')).toBeVisible();
} finally {
await closeElectronApp(runningApp);
}
});
test('@settings @persistence @electron persists changed desktop settings across app restart', async ({
dataDir,
}) => {