{"repo":"NousResearch/hermes-agent","public_id":"37619","display_label":"Desktop UI zoom, font size, and scaling controls","summary":"These issues and pull requests collectively address the lack of zoom, font size, and UI scaling support in the desktop application, including keyboard shortcuts, appearance settings controls, and persistence of zoom state across sessions.","subsystem":"apps/desktop","counts":{"members":24,"open_issues":2,"open_issues_distinct":2,"open_prs":2,"closed_issues":16,"merged_prs":1,"attempt_prs":5},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":60457},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-07-07T20:04:33+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.10971014492753615},"fix_edges":[{"issue_number":40166,"pr_number":60457,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":47499,"pr_number":60457,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":48336,"pr_number":60457,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":53411,"pr_number":60457,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":60248,"pr_number":60457,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":37619,"title":"Windows desktop app: add zoom / UI scaling support","url":null,"state":"closed"},{"kind":"issue","number":37917,"title":"Desktop (Windows): Ctrl +/- zoom (text size) does nothing — application menu is null on non-mac with no fallback","url":null,"state":"closed"},{"kind":"issue","number":38969,"title":"Desktop: Add text scaling and configurable text width / container margins","url":null,"state":"open"},{"kind":"issue","number":40166,"title":"[Desktop app]: add font size / zoom control","url":null,"state":"closed"},{"kind":"issue","number":40768,"title":"[Desktop][Windows] Supplement to #40399: font size, chat text size, and density controls in Appearance","url":null,"state":"closed"},{"kind":"issue","number":41879,"title":"Feature: Add font size scaling option for Hermes Desktop UI","url":null,"state":"closed"},{"kind":"issue","number":43417,"title":"[Feature]: 请问能增加调节字体大小功能？","url":null,"state":"open"},{"kind":"issue","number":45736,"title":"Feature: adjustable font size in Hermes desktop GUI (or CLI / config flag)","url":null,"state":"closed"},{"kind":"issue","number":46097,"title":"Feature request: Desktop font size setting","url":null,"state":"closed"},{"kind":"issue","number":47499,"title":"Feature request: UI zoom/scale controls","url":null,"state":"closed"},{"kind":"issue","number":48336,"title":"Hermes Desktop: No UI zoom / font scaling support (Ctrl+/- ineffective, no config option)","url":null,"state":"closed"},{"kind":"issue","number":48474,"title":"[Feature]: Default font size in settings","url":null,"state":"closed"},{"kind":"issue","number":51918,"title":"[Feature]:  Font size / zoom setting in Desktop UI","url":null,"state":"closed"},{"kind":"issue","number":53010,"title":"[Feature Request] Add font size settings for conversation area (accessibility & health)","url":null,"state":"closed"},{"kind":"issue","number":53411,"title":"[Feature]: Can the UI font in the Desktop version be adjusted?","url":null,"state":"closed"},{"kind":"issue","number":58161,"title":"[Feature]: I hope the desktop app adds a feature to adjust the interface font size.","url":null,"state":"closed"},{"kind":"issue","number":60039,"title":"feat(desktop): increase default font size in chat interface (reference Codex Desktop)","url":null,"state":"closed"},{"kind":"issue","number":64790,"title":"[Feature]: Add a font selector (family, size, color) to Desktop Appearance settings","url":null,"state":"closed"},{"kind":"pr","number":41936,"title":"feat(desktop): add font size scaling option in Appearance settings","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"8ce3c2f99152e20be26abe604c57929fb8496dc7","number":0},{"kind":"pr","value":"#41879","number":41879}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":51582,"title":"fix(desktop): app-level zoom that sticks, and session windows that inherit parent size","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":60249,"title":"feat(desktop): add UI scale setting to appearance settings","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#60457","number":60457}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":60457,"title":"feat(desktop): add UI scale setting to appearance settings (salvage #60249)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/electron/main.cjs","apps/desktop/electron/preload.cjs","apps/desktop/electron/zoom.cjs","apps/desktop/electron/zoom.test.cjs","apps/desktop/package.json","apps/desktop/src/app/settings/appearance-settings.tsx","apps/desktop/src/global.d.ts","apps/desktop/src/i18n/en.ts","apps/desktop/src/i18n/ja.ts","apps/desktop/src/i18n/types.ts","apps/desktop/src/i18n/zh-hant.ts","apps/desktop/src/i18n/zh.ts","apps/desktop/src/store/zoom.ts"],"fix_hunks":[{"file":"apps/desktop/electron/main.cjs","hunks":["@@ -4167,17 +4167,15 @@ function installPreviewShortcut(window) {\n // survives reloads/restarts) rather than a main-process JSON file. The main\n // process owns setZoomLevel, so we mirror each change into localStorage and\n // read it back on did-finish-load to re-apply after reloads or crash recovery.\n-const ZOOM_STORAGE_KEY = 'hermes:desktop:zoomLevel'\n-\n-function clampZoomLevel(value) {\n-  if (!Number.isFinite(value)) return 0\n-  return Math.min(Math.max(value, -9), 9)\n-}\n+const { ZOOM_STORAGE_KEY, clampZoomLevel, percentToZoomLevel, zoomLevelToPercent } = require('./zoom.cjs')\n \n function setAndPersistZoomLevel(window, zoomLevel) {\n   if (!window || window.isDestroyed()) return\n   const next = clampZoomLevel(zoomLevel)\n   window.webContents.setZoomLevel(next)\n+  // Keep any open settings UI in sync, including changes made via the\n+  // keyboard shortcuts or the View menu.\n+  window.webContents.send('hermes:zoom:changed', { level: next, percent: zoomLevelToPercent(next) })\n   window.webContents\n     .executeJavaScript(\n       `try { localStorage.setItem(${JSON.stringify(ZOOM_STORAGE_KEY)}, ${JSON.stringify(String(next))}) } catch {}`","@@ -6158,6 +6156,21 @@ ipcMain.handle('hermes:window:openNewSession', async () => {\n   return { ok: true }\n })\n \n+// --- Text size (zoom) -------------------------------------------------------\n+// The settings UI drives the same clamped zoom scale as the Ctrl/Cmd\n+// shortcuts and the View menu. Reads and writes target the asking window.\n+ipcMain.handle('hermes:zoom:get', event => {\n+  const window = BrowserWindow.fromWebContents(event.sender)\n+  const level = window && !window.isDestroyed() ? window.webContents.getZoomLevel() : 0\n+\n+  return { level, percent: zoomLevelToPercent(level) }\n+})\n+ipcMain.on('hermes:zoom:set-percent', (event, percent) => {\n+  const window = BrowserWindow.fromWebContents(event.sender)\n+  if (!window || window.isDestroyed()) return\n+  setAndPersistZoomLevel(window, percentToZoomLevel(Number(percent)))\n+})\n+\n // --- Pet overlay (pop-out mascot) -----------------------------------------\n // `request` is `{ bounds, screen }`. A fresh pop-out passes viewport-space\n // bounds (screen=false): convert to screen space by adding the main window's"]},{"file":"apps/desktop/electron/preload.cjs","hunks":["@@ -78,6 +78,18 @@ contextBridge.exposeInMainWorld('hermesDesktop', {\n     setDefaultProjectDir: dir => ipcRenderer.invoke('hermes:setting:defaultProjectDir:set', dir),\n     pickDefaultProjectDir: () => ipcRenderer.invoke('hermes:setting:defaultProjectDir:pick')\n   },\n+  zoom: {\n+    // Current zoom of this window, as { level, percent }.\n+    get: () => ipcRenderer.invoke('hermes:zoom:get'),\n+    setPercent: percent => ipcRenderer.send('hermes:zoom:set-percent', percent),\n+    // Fires on every zoom change, including the Ctrl/Cmd +/-/0 shortcuts,\n+    // so the settings UI can stay in sync with the keyboard.\n+    onChanged: callback => {\n+      const listener = (_event, payload) => callback(payload)\n+      ipcRenderer.on('hermes:zoom:changed', listener)\n+      return () => ipcRenderer.removeListener('hermes:zoom:changed', listener)\n+    }\n+  },\n   revealLogs: () => ipcRenderer.invoke('hermes:logs:reveal'),\n   getRecentLogs: () => ipcRenderer.invoke('hermes:logs:recent'),\n   readDir: dirPath => ipcRenderer.invoke('hermes:fs:readDir', dirPath),"]},{"file":"apps/desktop/electron/zoom.cjs","hunks":["@@ -0,0 +1,34 @@\n+/**\n+ * Pure helpers for window zoom. The main process owns webContents.setZoomLevel,\n+ * so the menu items, the Ctrl/Cmd shortcuts, and the settings UI all funnel\n+ * through this one clamped scale. Percent is the user-facing unit (100 = the\n+ * default size); Chromium's internal unit is the zoom level, where\n+ * factor = 1.2 ^ level.\n+ */\n+\n+const ZOOM_STORAGE_KEY = 'hermes:desktop:zoomLevel'\n+\n+const ZOOM_FACTOR_BASE = 1.2\n+const MIN_ZOOM_LEVEL = -9\n+const MAX_ZOOM_LEVEL = 9\n+\n+function clampZoomLevel(value) {\n+  if (!Number.isFinite(value)) return 0\n+  return Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)\n+}\n+\n+function zoomLevelToPercent(level) {\n+  return Math.round(Math.pow(ZOOM_FACTOR_BASE, clampZoomLevel(level)) * 100)\n+}\n+\n+function percentToZoomLevel(percent) {\n+  if (!Number.isFinite(percent) || percent <= 0) return 0\n+  return clampZoomLevel(Math.log(percent / 100) / Math.log(ZOOM_FACTOR_BASE))\n+}\n+\n+module.exports = {\n+  ZOOM_STORAGE_KEY,\n+  clampZoomLevel,\n+  percentToZoomLevel,\n+  zoomLevelToPercent\n+}"]},{"file":"apps/desktop/electron/zoom.test.cjs","hunks":["@@ -0,0 +1,54 @@\n+/**\n+ * Unit tests for the pure zoom helpers: clamping garbage input, the\n+ * percent <-> zoom-level conversion the settings UI relies on, and the\n+ * roundtrip stability of the preset percentages.\n+ */\n+\n+const test = require('node:test')\n+const assert = require('node:assert/strict')\n+\n+const { ZOOM_STORAGE_KEY, clampZoomLevel, percentToZoomLevel, zoomLevelToPercent } = require('./zoom.cjs')\n+\n+test('storage key stays stable so persisted zoom survives upgrades', () => {\n+  assert.equal(ZOOM_STORAGE_KEY, 'hermes:desktop:zoomLevel')\n+})\n+\n+test('clampZoomLevel rejects garbage and enforces bounds', () => {\n+  assert.equal(clampZoomLevel(NaN), 0)\n+  assert.equal(clampZoomLevel(Infinity), 0)\n+  assert.equal(clampZoomLevel(undefined), 0)\n+  assert.equal(clampZoomLevel('2'), 0)\n+  assert.equal(clampZoomLevel(0.3), 0.3)\n+  assert.equal(clampZoomLevel(-42), -9)\n+  assert.equal(clampZoomLevel(42), 9)\n+})\n+\n+test('level 0 is exactly 100 percent', () => {\n+  assert.equal(zoomLevelToPercent(0), 100)\n+  assert.equal(percentToZoomLevel(100), 0)\n+})\n+\n+test('percentToZoomLevel rejects garbage', () => {\n+  assert.equal(percentToZoomLevel(NaN), 0)\n+  assert.equal(percentToZoomLevel(0), 0)\n+  assert.equal(percentToZoomLevel(-50), 0)\n+  assert.equal(percentToZoomLevel(undefined), 0)\n+})\n+\n+test('preset percentages roundtrip within rounding', () => {\n+  for (const percent of [90, 100, 110, 125, 150, 175]) {\n+    assert.equal(zoomLevelToPercent(percentToZoomLevel(percent)), percent)\n+  }\n+})\n+\n+test('conversion is monotonic across the preset range', () => {\n+  const levels = [90, 100, 110, 125, 150, 175].map(percentToZoomLevel)\n+  for (let i = 1; i < levels.length; i++) {\n+    assert.ok(levels[i] > levels[i - 1])\n+  }\n+})\n+\n+test('extreme percentages clamp to the level bounds', () => {\n+  assert.equal(percentToZoomLevel(1), -9)\n+  assert.equal(percentToZoomLevel(1_000_000), 9)\n+})"]},{"file":"apps/desktop/package.json","hunks":["@@ -37,7 +37,7 @@\n     \"test:desktop:nsis\": \"node scripts/test-desktop.mjs nsis\",\n     \"test:desktop:existing\": \"node scripts/test-desktop.mjs existing\",\n     \"test:desktop:fresh\": \"node scripts/test-desktop.mjs fresh\",\n-    \"test:desktop:platforms\": \"node --test electron/bootstrap-platform.test.cjs electron/hardening.test.cjs electron/backend-env.test.cjs electron/backend-probes.test.cjs electron/backend-ready.test.cjs electron/bootstrap-runner.test.cjs electron/connection-config.test.cjs electron/dashboard-token.test.cjs electron/gateway-ws-probe.test.cjs electron/oauth-net-request.test.cjs electron/desktop-uninstall.test.cjs electron/session-windows.test.cjs electron/link-title-window.test.cjs electron/workspace-cwd.test.cjs electron/fs-read-dir.test.cjs electron/git-root.test.cjs electron/git-worktree-ops.test.cjs electron/windows-child-process.test.cjs electron/update-remote.test.cjs electron/update-count.test.cjs electron/update-rebuild.test.cjs electron/update-marker.test.cjs electron/update-relaunch.test.cjs electron/windows-user-env.test.cjs electron/wsl-clipboard-image.test.cjs electron/titlebar-overlay-width.test.cjs electron/window-state.test.cjs electron/windows-hermes-resolution.test.cjs electron/oauth-session-request.test.cjs\",\n+    \"test:desktop:platforms\": \"node --test electron/bootstrap-platform.test.cjs electron/hardening.test.cjs electron/backend-env.test.cjs electron/backend-probes.test.cjs electron/backend-ready.test.cjs electron/bootstrap-runner.test.cjs electron/connection-config.test.cjs electron/dashboard-token.test.cjs electron/gateway-ws-probe.test.cjs electron/oauth-net-request.test.cjs electron/desktop-uninstall.test.cjs electron/session-windows.test.cjs electron/link-title-window.test.cjs electron/workspace-cwd.test.cjs electron/fs-read-dir.test.cjs electron/git-root.test.cjs electron/git-worktree-ops.test.cjs electron/windows-child-process.test.cjs electron/update-remote.test.cjs electron/update-count.test.cjs electron/update-rebuild.test.cjs electron/update-marker.test.cjs electron/update-relaunch.test.cjs electron/windows-user-env.test.cjs electron/wsl-clipboard-image.test.cjs electron/titlebar-overlay-width.test.cjs electron/window-state.test.cjs electron/zoom.test.cjs electron/windows-hermes-resolution.test.cjs electron/oauth-session-request.test.cjs\",\n     \"typecheck\": \"tsc -p . --noEmit\",\n     \"lint\": \"eslint src/ electron/\",\n     \"lint:fix\": \"eslint src/ electron/ --fix\","]},{"file":"apps/desktop/src/app/settings/appearance-settings.tsx","hunks":["@@ -16,6 +16,7 @@ import { $embedAllowed, $embedMode, clearEmbedAllowed, type EmbedMode, setEmbedM\n import { $activeGatewayProfile, $profiles, normalizeProfileKey } from '@/store/profile'\n import { $toolViewMode, setToolViewMode } from '@/store/tool-view'\n import { $translucency, setTranslucency } from '@/store/translucency'\n+import { $zoomPercent, setZoomPercent } from '@/store/zoom'\n import { getBaseColors, useTheme } from '@/themes/context'\n import { installVscodeThemeFromMarketplace } from '@/themes/install'\n import type { DesktopTheme } from '@/themes/types'","@@ -62,6 +63,18 @@ function ThemePreview({ name, mode }: { name: string; mode: 'light' | 'dark' })\n   )\n }\n \n+// UI scale presets, as zoom percentages. 100 is the browser-default size;\n+// the ids double as the percent values sent to the main process. A Cmd/Ctrl\n+// +/- step landing between presets highlights nothing, and the row\n+// description keeps showing the exact current percent.\n+const UI_SCALE_PRESETS = ['90', '100', '110', '125', '150', '175'] as const\n+\n+type UiScalePreset = (typeof UI_SCALE_PRESETS)[number]\n+\n+function matchUiScalePreset(percent: number): UiScalePreset | null {\n+  return UI_SCALE_PRESETS.find(preset => Number(preset) === percent) ?? null\n+}\n+\n function useDebounced<T>(value: T, delayMs: number): T {\n   const [debounced, setDebounced] = useState(value)\n ","@@ -231,6 +244,7 @@ export function AppearanceSettings() {\n   const { t, isSavingLocale } = useI18n()\n   const { themeName, mode, resolvedMode, availableThemes, setTheme, setMode } = useTheme()\n   const toolViewMode = useStore($toolViewMode)\n+  const zoomPercent = useStore($zoomPercent)\n   const embedMode = useStore($embedMode)\n   const embedAllowed = useStore($embedAllowed)\n   const translucency = useStore($translucency)","@@ -277,6 +291,10 @@ export function AppearanceSettings() {\n     { id: 'off', label: a.embedsOff }\n   ] as const satisfies readonly { id: EmbedMode; label: string }[]\n \n+  const uiScaleOptions = UI_SCALE_PRESETS.map(preset => ({ id: preset, label: `${preset}%` }))\n+\n+  const matchedScalePreset = matchUiScalePreset(zoomPercent)\n+\n   return (\n     <SettingsContent>\n       <div>","@@ -392,6 +410,21 @@ export function AppearanceSettings() {\n             wide\n           />\n \n+          <ListRow\n+            action={\n+              <SegmentedControl\n+                onChange={id => {\n+                  triggerHaptic('selection')\n+                  setZoomPercent(Number(id))\n+                }}\n+                options={uiScaleOptions}\n+                value={matchedScalePreset ?? ('' as UiScalePreset)}\n+              />\n+            }\n+            description={a.uiScaleDesc(zoomPercent)}\n+            title={a.uiScaleTitle}\n+          />\n+\n           <ListRow\n             action={\n               <div className=\"flex items-center gap-3\">"]},{"file":"apps/desktop/src/global.d.ts","hunks":["@@ -89,6 +89,11 @@ declare global {\n         pickDefaultProjectDir: () => Promise<{ canceled: boolean; dir: null | string }>\n         setDefaultProjectDir: (dir: null | string) => Promise<{ dir: null | string }>\n       }\n+      zoom?: {\n+        get: () => Promise<{ level: number; percent: number }>\n+        setPercent: (percent: number) => void\n+        onChanged: (callback: (payload: { level: number; percent: number }) => void) => () => void\n+      }\n       revealLogs: () => Promise<{ ok: boolean; path: string; error?: string }>\n       getRecentLogs: () => Promise<{ path: string; lines: string[] }>\n       readDir: (path: string) => Promise<HermesReadDirResult>"]},{"file":"apps/desktop/src/i18n/en.ts","hunks":["@@ -381,6 +381,9 @@ export const en: Translations = {\n       colorModeDesc: 'Pick a fixed mode or let Hermes follow your system setting.',\n       toolViewTitle: 'Tool Call Display',\n       toolViewDesc: 'Product hides raw tool payloads; Technical shows full input/output.',\n+      uiScaleTitle: 'UI Scale',\n+      uiScaleDesc: (percent: number) =>\n+        `Scales text and controls across the whole app. Cmd/Ctrl with +, - and 0 also works. Current: ${percent}%.`,\n       translucencyTitle: 'Window Translucency',\n       translucencyDesc: 'See your desktop through the whole window. macOS and Windows only.',\n       embedsTitle: 'Inline Embeds',"]},{"file":"apps/desktop/src/i18n/ja.ts","hunks":["@@ -287,6 +287,9 @@ export const ja = defineLocale({\n       colorModeDesc: '固定モードを選ぶか、Hermes をシステム設定に合わせます。',\n       toolViewTitle: 'ツール呼び出しの表示',\n       toolViewDesc: 'プロダクト表示は生のツールペイロードを隠し、テクニカル表示は入出力をすべて表示します。',\n+      uiScaleTitle: 'UI スケール',\n+      uiScaleDesc: (percent: number) =>\n+        `アプリ全体の文字と UI を拡大縮小します。Cmd/Ctrl と +、-、0 でも変更できます。現在: ${percent}%`,\n       translucencyTitle: 'ウィンドウの透過',\n       translucencyDesc: 'ウィンドウ全体を透過させてデスクトップを表示します。macOS と Windows のみ。',\n       embedsTitle: 'インライン埋め込み',"]},{"file":"apps/desktop/src/i18n/types.ts","hunks":["@@ -302,6 +302,8 @@ export interface Translations {\n       colorModeDesc: string\n       toolViewTitle: string\n       toolViewDesc: string\n+      uiScaleTitle: string\n+      uiScaleDesc: (percent: number) => string\n       translucencyTitle: string\n       translucencyDesc: string\n       embedsTitle: string"]},{"file":"apps/desktop/src/i18n/zh-hant.ts","hunks":["@@ -279,6 +279,8 @@ export const zhHant = defineLocale({\n       colorModeDesc: '選擇固定模式，或讓 Hermes 跟隨系統設定。',\n       toolViewTitle: '工具呼叫顯示',\n       toolViewDesc: '產品模式會隱藏原始工具 payload；技術模式會顯示完整輸入/輸出。',\n+      uiScaleTitle: '介面縮放',\n+      uiScaleDesc: (percent: number) => `縮放整個應用程式的文字與介面。也可使用 Cmd/Ctrl 加 +、- 或 0 調整。目前：${percent}%`,\n       translucencyTitle: '視窗透明',\n       translucencyDesc: '讓整個視窗透出桌面。僅支援 macOS 與 Windows。',\n       embedsTitle: '內嵌預覽',"]},{"file":"apps/desktop/src/i18n/zh.ts","hunks":["@@ -370,6 +370,8 @@ export const zh: Translations = {\n       colorModeDesc: '选择固定模式，或让 Hermes 跟随系统设置。',\n       toolViewTitle: '工具调用显示',\n       toolViewDesc: '产品模式隐藏原始工具数据；技术模式显示完整输入/输出。',\n+      uiScaleTitle: '界面缩放',\n+      uiScaleDesc: (percent: number) => `缩放整个应用的文字和界面。也可使用 Cmd/Ctrl 加 +、- 或 0 调整。当前：${percent}%`,\n       translucencyTitle: '窗口透明',\n       translucencyDesc: '让整个窗口透出桌面。仅支持 macOS 和 Windows。',\n       embedsTitle: '内嵌预览',"]},{"file":"apps/desktop/src/store/zoom.ts","hunks":["@@ -0,0 +1,22 @@\n+/**\n+ * Window text size (zoom).\n+ *\n+ * The main process owns the zoom level and persists it (see electron/zoom.cjs\n+ * for the scale). The renderer only mirrors the current percent for the\n+ * settings UI: preset clicks go to the main process over IPC, and every\n+ * change comes back through onChanged, including ones made with the\n+ * Ctrl/Cmd +/-/0 shortcuts or the View menu, so the UI never drifts.\n+ */\n+\n+import { atom } from 'nanostores'\n+\n+export const $zoomPercent = atom<number>(100)\n+\n+export function setZoomPercent(percent: number): void {\n+  window.hermesDesktop?.zoom?.setPercent(percent)\n+}\n+\n+if (typeof window !== 'undefined' && window.hermesDesktop?.zoom) {\n+  void window.hermesDesktop.zoom.get().then(({ percent }) => $zoomPercent.set(percent))\n+  window.hermesDesktop.zoom.onChanged(({ percent }) => $zoomPercent.set(percent))\n+}\n"]}]},{"kind":"pr","number":87172,"title":"fix(desktop): add Ctrl+R and F5 reload shortcut for Windows/Linux","url":null,"state":"open","close_reason":{"refs":[{"kind":"pr","value":"#79870","number":79870}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":116606,"title":"feat(desktop): add configurable chat reading width","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}