fix(desktop): restore closed main window on second launch (#64800)

* fix(desktop): restore closed main window on second launch

* fix(desktop): reset deep-link readiness when main window closes
This commit is contained in:
Andry Lloyd Paez 2026-07-16 12:33:38 -07:00 committed by GitHub
parent 7d27a31ce7
commit b0ca12192e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 117 additions and 10 deletions

View file

@ -0,0 +1,69 @@
import assert from 'node:assert/strict'
import { test } from 'vitest'
import { ensureMainWindow } from './main-window-lifecycle'
test('recreates a destroyed primary window without focusing it', () => {
const destroyedWindow = {
isDestroyed: () => true
}
let createCalls = 0
let focusCalls = 0
ensureMainWindow(destroyedWindow, {
isReady: true,
createWindow: () => {
createCalls += 1
},
focusWindow: () => {
focusCalls += 1
}
})
assert.equal(createCalls, 1)
assert.equal(focusCalls, 0)
})
test('waits for app readiness before recreating a primary window', () => {
let createCalls = 0
ensureMainWindow(null, {
isReady: false,
createWindow: () => {
createCalls += 1
},
focusWindow: () => assert.fail('missing window must not be focused')
})
assert.equal(createCalls, 0)
})
test('focuses a live primary window for a normal second launch', () => {
const liveWindow = {
isDestroyed: () => false
}
let focusedWindow = null
ensureMainWindow(liveWindow, {
isReady: true,
createWindow: () => assert.fail('live window must not be replaced'),
focusWindow: window => {
focusedWindow = window
}
})
assert.equal(focusedWindow, liveWindow)
})
test('leaves live-window focus to deep-link delivery', () => {
const liveWindow = {
isDestroyed: () => false
}
ensureMainWindow(liveWindow, {
isReady: true,
createWindow: () => assert.fail('live window must not be replaced'),
focusWindow: () => assert.fail('deep-link delivery owns focus'),
focusExisting: false
})
})

View file

@ -0,0 +1,28 @@
type MainWindowLike = {
isDestroyed: () => boolean
}
type EnsureMainWindowOptions<T extends MainWindowLike> = {
isReady: boolean
createWindow: () => unknown
focusWindow: (window: T) => unknown
focusExisting?: boolean
}
export function ensureMainWindow<T extends MainWindowLike>(
window: T | null | undefined,
{ isReady, createWindow, focusWindow, focusExisting = true }: EnsureMainWindowOptions<T>
) {
if (!window || window.isDestroyed()) {
// a closed electron window stays truthy, so replace it before invoking native methods.
if (isReady) {
createWindow()
}
return
}
if (focusExisting) {
focusWindow(window)
}
}

View file

@ -101,6 +101,7 @@ import {
TEXT_PREVIEW_SOURCE_MAX_BYTES
} from './hardening'
import { createLinkTitleWindow, guardLinkTitleSession, readLinkTitleWindowTitle } from './link-title-window'
import { ensureMainWindow } from './main-window-lifecycle'
import { serializeJsonBody, setJsonRequestHeaders } from './oauth-net-request'
import { decideProfileDeleteAction, profileNameFromDeleteRequest, resolveRouteProfile } from './profile-delete-routing'
import {
@ -7309,10 +7310,17 @@ function createWindow() {
mainWindow.on('unmaximize', schedulePersistWindowState)
mainWindow.on('close', () => schedulePersistWindowState.flush())
// The overlay rides the main window — closing the app's primary window must
// tear it down too (otherwise it strands as an orphan that blocks
// window-all-closed from quitting on Windows/Linux).
mainWindow.on('closed', () => closePetOverlay())
// the closed wrapper remains truthy, so clear only the window this callback owns.
const createdMainWindow = mainWindow
mainWindow.on('closed', () => {
closePetOverlay()
if (mainWindow === createdMainWindow) {
mainWindow = null
// the replacement renderer must register before queued links can be delivered.
_rendererReadyForDeepLink = false
}
})
wireCommonWindowHandlers(mainWindow, zoomWiringForWindowKind('chat'))
@ -9099,13 +9107,15 @@ if (!_gotSingleInstanceLock) {
if (url) {
handleDeepLink(url)
} else if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore()
}
mainWindow.focus()
}
ensureMainWindow(mainWindow, {
isReady: app.isReady(),
createWindow,
focusWindow,
// deep-link delivery focuses a live window after its renderer is ready.
focusExisting: !url
})
})
}