Desktop zoom level persistence and consistency fixes
NousResearch/hermes-agent · cluster 38854 · apps/desktop
Data as of
Nothing open on GitHub. Fix merged in #66901. Check it covers your case.
The short answer
- Known?
- Yes. 7 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #66901. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #94396. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and pull requests address problems with zoom level not being reliably persisted or applied app-wide on the desktop client, particularly across hash-route navigation and full page loads.
Open issues0
Open PRs0
Fixes on record
-
#66901 fix(desktop): persist zoom to JSON and save window state on first show (#56726)
files:
apps/desktop/electron/main.tsShow the diff
-
apps/desktop/electron/main.ts@@ -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
-
-
#66989 fix(desktop): re-apply persisted zoom on every full load (#46429)
files:
apps/desktop/electron/main.ts,contributors/emails/[redacted:email] -
#94396 fix(desktop): keep UI scale across in-page route navigation
files:
apps/desktop/e2e/zoom-preservation.spec.ts,apps/desktop/electron/main.ts,apps/desktop/electron/zoom.test.ts,apps/desktop/electron/zoom.ts
2 PRs closed without merging
From the close comments: done elsewhere 1 · merged via another change 1
Closed issues · 2
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.26: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab