mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
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:
parent
7d27a31ce7
commit
b0ca12192e
3 changed files with 117 additions and 10 deletions
69
apps/desktop/electron/main-window-lifecycle.test.ts
Normal file
69
apps/desktop/electron/main-window-lifecycle.test.ts
Normal 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
|
||||
})
|
||||
})
|
||||
28
apps/desktop/electron/main-window-lifecycle.ts
Normal file
28
apps/desktop/electron/main-window-lifecycle.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue