mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(desktop): persist zoom to JSON and save window state on first show (#56726)
Surgical reapply of the surviving halves of PR #57414 by @Sahil-SS9
(the branch predates the ts-ify migration and the zoom apply/notify
funnel, so a direct cherry-pick no longer applies):
- Zoom persists to a main-process zoom-state.json as the primary store.
The old localStorage-only store lives under Electron's cache/storage
folders, which crash recovery can move or recreate — wiping zoom
exactly when the user recovers from a crash. localStorage stays as a
secondary mirror; pre-JSON installs migrate on first read.
- Window geometry persists at ready-to-show, so a crash before the
first resize/move/close still captures the restored bounds.
The third half of #57414 (one-shot --no-sandbox relaunch on Windows
renderer crash loops) was superseded by #66842, which ships the same
recovery gated on the 0x80000003 sandbox-crash signature.
Adapted to current main: restore/persist route through the
applyZoomLevel funnel (39230d173) so the settings UI Scale control
stays in sync, and JSON writes go through writeFileAtomic.
This commit is contained in:
parent
af6b41b18b
commit
e632769269
1 changed files with 51 additions and 1 deletions
|
|
@ -2077,6 +2077,33 @@ function persistWindowState() {
|
|||
// resized/moved fire many times mid-drag on Linux; debounce to one write.
|
||||
const schedulePersistWindowState = debounce(persistWindowState, 250)
|
||||
|
||||
// Zoom's primary store is a main-process JSON file. The renderer localStorage
|
||||
// mirror lives under Electron's cache/storage folders, which crash recovery
|
||||
// can move or recreate — wiping the zoom setting exactly when the user just
|
||||
// recovered from a crash (#56726). JSON survives; localStorage is kept as a
|
||||
// secondary mirror so pre-JSON installs migrate transparently on first read.
|
||||
const DESKTOP_ZOOM_STATE_PATH = path.join(app.getPath('userData'), 'zoom-state.json')
|
||||
|
||||
function readZoomState() {
|
||||
try {
|
||||
const raw = JSON.parse(fs.readFileSync(DESKTOP_ZOOM_STATE_PATH, 'utf8'))
|
||||
const level = Number(raw?.zoomLevel)
|
||||
|
||||
return Number.isFinite(level) ? level : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeZoomState(zoomLevel) {
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(DESKTOP_ZOOM_STATE_PATH), { recursive: true })
|
||||
writeFileAtomic(DESKTOP_ZOOM_STATE_PATH, JSON.stringify({ zoomLevel }, null, 2))
|
||||
} catch (error) {
|
||||
rememberLog(`[zoom] json persist failed: ${error?.message || error}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Match the backend's source resolution but bias toward a real git checkout.
|
||||
// Dev → SOURCE_REPO_ROOT. Packaged/CLI install → ACTIVE_HERMES_ROOT.
|
||||
// HERMES_DESKTOP_HERMES_ROOT always wins so devs can pin a worktree.
|
||||
|
|
@ -4884,6 +4911,11 @@ function setAndPersistZoomLevel(window, zoomLevel) {
|
|||
// Apply + notify in one funnel so the settings UI stays in sync, including
|
||||
// changes made via the keyboard shortcuts or the View menu.
|
||||
const next = applyZoomLevel(window.webContents, zoomLevel)
|
||||
|
||||
// Primary store: main-process JSON (survives crash recovery — #56726).
|
||||
writeZoomState(next)
|
||||
// Secondary mirror: renderer localStorage (legacy store; kept in sync so a
|
||||
// downgrade or JSON read failure still finds a sane value).
|
||||
window.webContents
|
||||
.executeJavaScript(
|
||||
`try { localStorage.setItem(${JSON.stringify(ZOOM_STORAGE_KEY)}, ${JSON.stringify(String(next))}) } catch {}`
|
||||
|
|
@ -4896,6 +4928,19 @@ function restorePersistedZoomLevel(window) {
|
|||
return
|
||||
}
|
||||
|
||||
// Prefer the JSON file — it survives crash recovery wiping Electron's
|
||||
// cache/storage folders (#56726). applyZoomLevel notifies the renderer so
|
||||
// the Appearance UI Scale control stays in sync.
|
||||
const saved = readZoomState()
|
||||
|
||||
if (saved != null) {
|
||||
applyZoomLevel(window.webContents, saved)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Fall back to localStorage for installs that predate zoom-state.json,
|
||||
// migrating the value into the JSON store on first read.
|
||||
window.webContents
|
||||
.executeJavaScript(
|
||||
`(() => { try { return localStorage.getItem(${JSON.stringify(ZOOM_STORAGE_KEY)}) } catch { return null } })()`
|
||||
|
|
@ -4907,7 +4952,8 @@ function restorePersistedZoomLevel(window) {
|
|||
|
||||
// Notify the renderer too — otherwise the Appearance UI Scale control
|
||||
// can stay stuck at 100% even though the window zoom was restored.
|
||||
applyZoomLevel(window.webContents, Number(stored))
|
||||
const applied = applyZoomLevel(window.webContents, Number(stored))
|
||||
writeZoomState(applied)
|
||||
})
|
||||
.catch(error => rememberLog(`[zoom] restore failed: ${error?.message || error}`))
|
||||
}
|
||||
|
|
@ -7441,6 +7487,10 @@ function createWindow() {
|
|||
mainWindow.show()
|
||||
}
|
||||
|
||||
// Persist geometry as soon as the window is visible so a crash before the
|
||||
// first clean resize/move/close still captures the restored bounds (#56726).
|
||||
schedulePersistWindowState()
|
||||
|
||||
// #38216: clear the mid-boot marker only after a window is actually usable.
|
||||
// Keep sticky `fallback` when we launched with --no-sandbox so the next
|
||||
// Start Menu click does not re-enter the GPU FATAL crash loop. The marker
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue