diff --git a/apps/electron/src/renderer/App.tsx b/apps/electron/src/renderer/App.tsx
index 8392407f8..cfc83116b 100644
--- a/apps/electron/src/renderer/App.tsx
+++ b/apps/electron/src/renderer/App.tsx
@@ -17,6 +17,7 @@ import { OnboardingWizard, ReauthScreen } from '@/components/onboarding'
import { WorkspacePicker } from '@/components/workspace'
import { ResetConfirmationDialog } from '@/components/ResetConfirmationDialog'
import { CommandPalette } from '@/components/CommandPalette'
+import { ZoomHotkeys } from '@/components/ZoomHotkeys'
import { SplashScreen } from '@/components/SplashScreen'
import { TooltipProvider } from '@craft-agent/ui'
import { FocusProvider } from '@/context/FocusContext'
@@ -2500,6 +2501,9 @@ export default function App() {
{/* Global command palette (⌘K / Ctrl+K) — search and run any action */}
+ {/* Interface zoom shortcuts (⌘+ / ⌘- / ⌘0) wired to the action registry */}
+
+
{/* Splash screen overlay - fades out when fully ready */}
{showSplash && (
> = {
'nav.goForwardAlt': 'shortcuts.action.goForward',
'view.toggleSidebar': 'shortcuts.action.toggleSidebar',
'view.toggleFocusMode': 'shortcuts.action.toggleFocusMode',
+ 'view.zoomIn': 'shortcuts.action.zoomIn',
+ 'view.zoomOut': 'shortcuts.action.zoomOut',
+ 'view.zoomReset': 'shortcuts.action.zoomReset',
'navigator.selectAll': 'shortcuts.action.selectAll',
'navigator.clearSelection': 'shortcuts.action.clearSelection',
'panel.focusNext': 'shortcuts.action.focusNextPanel',
diff --git a/apps/electron/src/renderer/actions/definitions.ts b/apps/electron/src/renderer/actions/definitions.ts
index 82ecf3521..8f095fdc9 100644
--- a/apps/electron/src/renderer/actions/definitions.ts
+++ b/apps/electron/src/renderer/actions/definitions.ts
@@ -143,6 +143,27 @@ export const actions = {
defaultHotkey: 'mod+.',
category: 'View',
},
+ 'view.zoomIn': {
+ id: 'view.zoomIn',
+ label: 'Zoom In',
+ description: 'Scale the whole interface up',
+ defaultHotkey: 'mod+=',
+ category: 'View',
+ },
+ 'view.zoomOut': {
+ id: 'view.zoomOut',
+ label: 'Zoom Out',
+ description: 'Scale the whole interface down',
+ defaultHotkey: 'mod+-',
+ category: 'View',
+ },
+ 'view.zoomReset': {
+ id: 'view.zoomReset',
+ label: 'Reset Zoom',
+ description: 'Reset the interface zoom to 100%',
+ defaultHotkey: 'mod+0',
+ category: 'View',
+ },
// ═══════════════════════════════════════════
// Navigator (scoped — active entity list in middle panel)
diff --git a/apps/electron/src/renderer/components/ZoomHotkeys.tsx b/apps/electron/src/renderer/components/ZoomHotkeys.tsx
new file mode 100644
index 000000000..27121fe60
--- /dev/null
+++ b/apps/electron/src/renderer/components/ZoomHotkeys.tsx
@@ -0,0 +1,23 @@
+/**
+ * ZoomHotkeys
+ *
+ * Bridges the interface-zoom preference to the action registry so the standard
+ * desktop zoom shortcuts (⌘+ / ⌘- / ⌘0) and their Command Palette entries drive
+ * `ZoomProvider`. Renders nothing — it only registers handlers.
+ *
+ * Must be mounted inside both `ZoomProvider` (for `useZoom`) and
+ * `ActionRegistryProvider` (for `useAction`).
+ */
+
+import { useZoom } from '@/context/ZoomContext'
+import { useAction } from '@/actions'
+
+export function ZoomHotkeys() {
+ const { zoomIn, zoomOut, resetZoom } = useZoom()
+
+ useAction('view.zoomIn', zoomIn)
+ useAction('view.zoomOut', zoomOut)
+ useAction('view.zoomReset', resetZoom)
+
+ return null
+}
diff --git a/apps/electron/src/renderer/context/ZoomContext.tsx b/apps/electron/src/renderer/context/ZoomContext.tsx
new file mode 100644
index 000000000..9b04ec19a
--- /dev/null
+++ b/apps/electron/src/renderer/context/ZoomContext.tsx
@@ -0,0 +1,140 @@
+/**
+ * ZoomContext
+ *
+ * App-wide "interface zoom" preference — scales the whole renderer (sidebar,
+ * navigator, chat, settings, dialogs) up or down, like the ⌘+ / ⌘- / ⌘0 zoom
+ * in Claude Desktop, VS Code, and other desktop apps.
+ *
+ * The zoom factor is one of a fixed set of Chrome-like steps and is applied by
+ * setting `document.documentElement.style.zoom` — a Chromium-native property
+ * that scales the entire tree, including portalled dialogs rendered into
+ * ``. At 100% the inline style is cleared so the DOM stays clean.
+ *
+ * The preference is persisted in `localStorage` (renderer-only, no backend),
+ * mirroring the other lightweight UI prefs in `lib/local-storage.ts` and the
+ * merged `ReduceMotionProvider`.
+ */
+
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useEffect,
+ useCallback,
+ useMemo,
+ type ReactNode,
+} from 'react'
+import * as storage from '@/lib/local-storage'
+
+/** Discrete zoom steps (factors), mirroring a browser's zoom ladder. */
+export const ZOOM_STEPS: readonly number[] = [0.5, 0.67, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2]
+
+/** The neutral / "actual size" factor. */
+export const DEFAULT_ZOOM = 1
+
+interface ZoomContextType {
+ /** Current zoom factor (e.g. 1 = 100%, 1.1 = 110%). */
+ zoom: number
+ /** Whole-number percentage for display (e.g. 110). */
+ zoomPercent: number
+ /** Step up to the next larger factor (no-op at the max). */
+ zoomIn: () => void
+ /** Step down to the next smaller factor (no-op at the min). */
+ zoomOut: () => void
+ /** Return to 100%. */
+ resetZoom: () => void
+ /** Whether a larger step is available. */
+ canZoomIn: boolean
+ /** Whether a smaller step is available. */
+ canZoomOut: boolean
+}
+
+const ZoomContext = createContext(null)
+
+/** Clamp an arbitrary stored value onto the nearest valid step. */
+function normalizeZoom(value: number): number {
+ if (!Number.isFinite(value)) return DEFAULT_ZOOM
+ let nearest = ZOOM_STEPS[0]
+ let bestDelta = Math.abs(value - nearest)
+ for (const step of ZOOM_STEPS) {
+ const delta = Math.abs(value - step)
+ if (delta < bestDelta) {
+ nearest = step
+ bestDelta = delta
+ }
+ }
+ return nearest
+}
+
+/** Reflect the factor onto so the whole renderer scales. */
+function applyZoom(factor: number): void {
+ const root = document.documentElement
+ if (factor === DEFAULT_ZOOM) {
+ root.style.removeProperty('zoom')
+ } else {
+ root.style.zoom = String(factor)
+ }
+}
+
+export function ZoomProvider({ children }: { children: ReactNode }) {
+ const [zoom, setZoomState] = useState(() =>
+ normalizeZoom(storage.get(storage.KEYS.zoomLevel, DEFAULT_ZOOM)),
+ )
+
+ // Keep the DOM in sync (also covers the initial value on mount).
+ useEffect(() => {
+ applyZoom(zoom)
+ }, [zoom])
+
+ const setZoom = useCallback((factor: number) => {
+ const next = normalizeZoom(factor)
+ setZoomState(next)
+ storage.set(storage.KEYS.zoomLevel, next)
+ applyZoom(next)
+ }, [])
+
+ const zoomIn = useCallback(() => {
+ setZoomState((current) => {
+ const idx = ZOOM_STEPS.indexOf(normalizeZoom(current))
+ const next = ZOOM_STEPS[Math.min(idx + 1, ZOOM_STEPS.length - 1)]
+ storage.set(storage.KEYS.zoomLevel, next)
+ applyZoom(next)
+ return next
+ })
+ }, [])
+
+ const zoomOut = useCallback(() => {
+ setZoomState((current) => {
+ const idx = ZOOM_STEPS.indexOf(normalizeZoom(current))
+ const next = ZOOM_STEPS[Math.max(idx - 1, 0)]
+ storage.set(storage.KEYS.zoomLevel, next)
+ applyZoom(next)
+ return next
+ })
+ }, [])
+
+ const resetZoom = useCallback(() => setZoom(DEFAULT_ZOOM), [setZoom])
+
+ const value = useMemo(() => {
+ const idx = ZOOM_STEPS.indexOf(zoom)
+ return {
+ zoom,
+ zoomPercent: Math.round(zoom * 100),
+ zoomIn,
+ zoomOut,
+ resetZoom,
+ canZoomIn: idx < ZOOM_STEPS.length - 1,
+ canZoomOut: idx > 0,
+ }
+ }, [zoom, zoomIn, zoomOut, resetZoom])
+
+ return {children}
+}
+
+export function useZoom(): ZoomContextType {
+ const ctx = useContext(ZoomContext)
+ if (!ctx) {
+ throw new Error('useZoom must be used within a ZoomProvider')
+ }
+ return ctx
+}
diff --git a/apps/electron/src/renderer/lib/local-storage.ts b/apps/electron/src/renderer/lib/local-storage.ts
index 4d6446afd..150578608 100644
--- a/apps/electron/src/renderer/lib/local-storage.ts
+++ b/apps/electron/src/renderer/lib/local-storage.ts
@@ -53,6 +53,7 @@ export const KEYS = {
// Appearance
showConnectionIcons: 'show-connection-icons',
reduceMotion: 'reduce-motion', // Minimize animations/transitions app-wide
+ zoomLevel: 'zoom-level', // Interface zoom factor (⌘+/⌘-/⌘0), scales the whole renderer
// What's New
whatsNewLastSeenVersion: 'whats-new-last-seen-version',
diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx
index 14729366d..79d8bd525 100644
--- a/apps/electron/src/renderer/main.tsx
+++ b/apps/electron/src/renderer/main.tsx
@@ -7,6 +7,7 @@ import { Provider as JotaiProvider, useAtomValue } from 'jotai'
import App from './App'
import { ThemeProvider } from './context/ThemeContext'
import { ReduceMotionProvider } from './context/ReduceMotionContext'
+import { ZoomProvider } from './context/ZoomContext'
import { windowWorkspaceIdAtom } from './atoms/sessions'
import { Toaster } from '@/components/ui/sonner'
import { PetWindowController } from '@/components/pet/PetWindowController'
@@ -108,9 +109,11 @@ function Root() {
return (
-
-
-
+
+
+
+
+
)
diff --git a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx
index 4da9ba24c..193104053 100644
--- a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx
+++ b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx
@@ -15,9 +15,11 @@ import { HeaderMenu } from '@/components/ui/HeaderMenu'
import { EditPopover, EditButton, getEditConfig } from '@/components/ui/EditPopover'
import { useTheme } from '@/context/ThemeContext'
import { useReduceMotion } from '@/context/ReduceMotionContext'
+import { useZoom } from '@/context/ZoomContext'
+import { Button } from '@/components/ui/button'
import { useAppShellContext } from '@/context/AppShellContext'
import { routes } from '@/lib/navigate'
-import { FolderOpen, Monitor, RefreshCw, Sun, Moon } from 'lucide-react'
+import { FolderOpen, Monitor, RefreshCw, Sun, Moon, Minus, Plus, RotateCcw } from 'lucide-react'
import type { DetailsPageMeta } from '@/lib/navigation-registry'
import type { ToolIconMapping } from '../../../shared/types'
@@ -142,6 +144,7 @@ export default function AppearanceSettingsPage() {
// Reduce motion toggle (renderer-only preference, persisted in localStorage)
const { reduceMotion, setReduceMotion } = useReduceMotion()
+ const { zoomPercent, zoomIn, zoomOut, resetZoom, canZoomIn, canZoomOut } = useZoom()
// Pet companion settings + custom pets (synced via shared Jotai atoms)
const {
@@ -387,6 +390,58 @@ export default function AppearanceSettingsPage() {
onCheckedChange={setReduceMotion}
testId="reduce-motion-toggle"
/>
+
+
+
+
+ {zoomPercent}%
+
+
+
+
+
diff --git a/docs/loop/feature-ledger.md b/docs/loop/feature-ledger.md
index 51235562a..c40101c45 100644
--- a/docs/loop/feature-ledger.md
+++ b/docs/loop/feature-ledger.md
@@ -32,7 +32,16 @@ log, not the system of record.
| slug | title | source | feasibility | status | issue | pr | branch | updated | notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | pr-open | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-03 | Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). New `ReduceMotionProvider` in `main.tsx`; toggle in Appearance→Interface; 2 new i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; i18n parity ✅. CDP assertion included; **could not run locally** (egress 403s Electron binary download). |
+| interface-zoom | Interface zoom (⌘+/⌘-/⌘0) to scale the whole app | Claude Desktop / VS Code / Codex desktop View→Zoom In/Out/Actual Size | frontend-only | pr-open | [#68](https://github.com/modelstudioai/openwork/issues/68) | [#69](https://github.com/modelstudioai/openwork/pull/69) | loop/interface-zoom | 2026-07-08 | New `ZoomProvider` (mirrors `ReduceMotionProvider`) scales whole renderer via `document.documentElement.style.zoom`, discrete 50–200% steps, persisted in localStorage (`craft-zoom-level`). 3 View actions `view.zoomIn/Out/Reset` (`mod+=`/`mod+-`/`mod+0`) → auto in Command Palette + Shortcuts ref, wired via `ZoomHotkeys` bridge. Stepper in Appearance→Interface. 5 i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; lint 0 errors; i18n parity ✅ (1549 keys). CDP assertion included; **could not run locally** (org egress 403 on Electron binary download — same block as #51). Distinct from chat-text-size (#64)/conversation-width (#62): scales entire UI, not just chat text. |
+| increase-contrast | "Increase contrast" accessibility setting in Appearance | Claude / macOS / Windows increase-contrast | frontend-only | pr-open | [#66](https://github.com/modelstudioai/openwork/issues/66) | [#67](https://github.com/modelstudioai/openwork/pull/67) | — | 2026-07-08 | Opened by a prior run (not this ledger's author); reconciled from GitHub. Awaiting review. |
+| chat-text-size | "Chat text size" (Small/Default/Large) in Appearance | Claude / ChatGPT desktop message text size | frontend-only | pr-open | [#64](https://github.com/modelstudioai/openwork/issues/64) | [#65](https://github.com/modelstudioai/openwork/pull/65) | — | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| conversation-width | "Conversation width" (Comfortable/Wide/Full) in Appearance | Claude / ChatGPT desktop content width | frontend-only | pr-open | [#62](https://github.com/modelstudioai/openwork/issues/62) | [#63](https://github.com/modelstudioai/openwork/pull/63) | — | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| composer-word-count | Live word / character count indicator in the composer | Codex / ChatGPT desktop composer counter | frontend-only | pr-open | [#60](https://github.com/modelstudioai/openwork/issues/60) | [#61](https://github.com/modelstudioai/openwork/pull/61) | — | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| shortcuts-search | Search box on Settings → Keyboard Shortcuts page | Claude / VS Code shortcuts search | frontend-only | pr-open | [#58](https://github.com/modelstudioai/openwork/issues/58) | [#59](https://github.com/modelstudioai/openwork/pull/59) | — | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| palette-recent | Surface recently-used commands in Command Palette (⌘K) | VS Code / Linear recent commands | frontend-only | pr-open | [#56](https://github.com/modelstudioai/openwork/issues/56) | [#57](https://github.com/modelstudioai/openwork/pull/57) | — | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| thinking-menu-shortcut | Keyboard shortcut (⌘⇧E) to open composer thinking menu | Claude Code Desktop ⌘⇧E | frontend-only | pr-open | [#54](https://github.com/modelstudioai/openwork/issues/54) | [#55](https://github.com/modelstudioai/openwork/pull/55) | loop/thinking-menu-shortcut | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| recall-prompts | Recall previously-sent prompts with Up/Down in composer | Claude Code / ChatGPT / shell history recall | frontend-only | pr-open | [#52](https://github.com/modelstudioai/openwork/issues/52) | [#53](https://github.com/modelstudioai/openwork/pull/53) | — | 2026-07-08 | Prior run; reconciled from GitHub. Awaiting review. |
+| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | merged | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-06 | **Merged** into `main` (2026-07-06). Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. New `ReduceMotionProvider` in `main.tsx`; toggle in Appearance→Interface; 2 new i18n keys ×7 locales. Provider pattern reused by interface-zoom (#69). |
| composer-expand | Expand / collapse (maximize) toggle for the chat composer | Claude/ChatGPT/Codex desktop composer maximize | frontend-only | pr-open | [#48](https://github.com/modelstudioai/openwork/issues/48) | [#49](https://github.com/modelstudioai/openwork/pull/49) | loop/composer-expand | 2026-07-03 | Opened by a prior run. Adds `isComposerExpanded` toggle in `FreeFormInput`; 2 new i18n keys. Awaiting review. |
| scroll-to-bottom | "Jump to latest" (scroll-to-bottom) button in the chat transcript | Claude Code / ChatGPT / Codex desktop | frontend-only | pr-open | [#46](https://github.com/modelstudioai/openwork/issues/46) | [#47](https://github.com/modelstudioai/openwork/pull/47) | loop/scroll-to-bottom | 2026-07-02 | Opened by a prior run. Floating jump button in `ChatDisplay` + `seed()` harness hook. Awaiting review. |
| thinking-level-picker | Thinking-level (reasoning effort) picker in the chat composer | Claude Code Desktop effort menu (⌘⇧E) + OpenWork's own model picker | frontend-only | merged | [#44](https://github.com/modelstudioai/openwork/issues/44) | [#45](https://github.com/modelstudioai/openwork/pull/45) | loop/thinking-level-picker | 2026-07-03 | **Merged** into `main` (2026-07-02). `thinkingLevel`/`onThinkingLevelChange` already plumbed to `FreeFormInput`; only the UI trigger was missing. Reuses `thinking.*` + `settings.ai.thinking` i18n keys (zero new keys). |
diff --git a/e2e/assertions/zoom.assert.ts b/e2e/assertions/zoom.assert.ts
new file mode 100644
index 000000000..9abed1b5e
--- /dev/null
+++ b/e2e/assertions/zoom.assert.ts
@@ -0,0 +1,104 @@
+/**
+ * Feature assertion: the interface-zoom control in Settings → Appearance
+ * actually scales the whole renderer and persists the preference.
+ *
+ * Drives the real UI over CDP entirely in the draft/no-session state (no seeded
+ * conversation, no backend): opens Settings → Appearance → Interface, uses the
+ * Zoom stepper, and asserts the observable effects — the displayed percentage,
+ * the inline `zoom` style on (which is what scales the app), and the
+ * persisted localStorage value. Zooming in then resetting proves it both
+ * applies and reverts, not merely renders.
+ */
+
+import type { Assertion } from '../runner';
+
+const SETTINGS_NAV = '[data-testid="nav:settings"]';
+const APPEARANCE_NAV = '[data-testid="settings-nav-appearance"]';
+const ZOOM_CONTROL = '[data-testid="zoom-control"]';
+const ZOOM_IN = '[data-testid="zoom-in"]';
+const ZOOM_RESET = '[data-testid="zoom-reset"]';
+const ZOOM_VALUE = '[data-testid="zoom-value"]';
+const STORAGE_KEY = 'craft-zoom-level';
+
+/** The stepper's displayed percentage text (e.g. "100%"). */
+function valueTextExpr(): string {
+ return `(() => {
+ const el = document.querySelector(${JSON.stringify(ZOOM_VALUE)});
+ return el ? el.textContent : null;
+ })()`;
+}
+
+/** The inline zoom factor applied to ("" when unset). */
+function htmlZoomExpr(): string {
+ return `document.documentElement.style.zoom`;
+}
+
+/** The persisted localStorage value for the preference. */
+function storedValueExpr(): string {
+ return `window.localStorage.getItem(${JSON.stringify(STORAGE_KEY)})`;
+}
+
+const assertion: Assertion = {
+ name: 'interface zoom scales and persists the preference',
+ async run(app) {
+ const { session } = app;
+
+ // App fully mounted.
+ await session.waitForFunction(
+ '!document.getElementById("_loader") && (document.getElementById("root")?.childElementCount ?? 0) > 0',
+ { timeoutMs: 30000, message: 'app did not mount' },
+ );
+
+ // Open Settings → Appearance (real user path).
+ await session.click(SETTINGS_NAV, { timeoutMs: 15000 });
+ await session.click(APPEARANCE_NAV, { timeoutMs: 15000 });
+
+ // The zoom control is the feature under test — its presence is the first signal.
+ await session.waitForSelector(ZOOM_CONTROL, {
+ timeoutMs: 15000,
+ message: 'zoom control did not render',
+ });
+
+ // Initial state: 100%, no inline zoom on .
+ const initialText = await session.evaluate(valueTextExpr());
+ if (initialText !== '100%') {
+ throw new Error(`expected "100%" initially, saw ${JSON.stringify(initialText)}`);
+ }
+ const initialZoom = await session.evaluate(htmlZoomExpr());
+ if (initialZoom !== '') {
+ throw new Error(`expected no inline zoom on initially, saw ${JSON.stringify(initialZoom)}`);
+ }
+
+ // Zoom in → percentage rises, scaled, persisted.
+ await session.click(ZOOM_IN);
+ await session.waitForFunction(
+ `${valueTextExpr()} === '110%'`,
+ { timeoutMs: 5000, message: 'zoom-in did not raise the displayed percentage to 110%' },
+ );
+ await session.waitForFunction(
+ `${htmlZoomExpr()} === '1.1'`,
+ { timeoutMs: 5000, message: 'zoom-in did not apply zoom:1.1 to ' },
+ );
+ const storedOn = await session.evaluate(storedValueExpr());
+ if (storedOn !== '1.1') {
+ throw new Error(`expected persisted "1.1" after zoom-in, saw ${JSON.stringify(storedOn)}`);
+ }
+
+ // Reset → back to 100%, inline zoom cleared, persisted default restored.
+ await session.click(ZOOM_RESET);
+ await session.waitForFunction(
+ `${valueTextExpr()} === '100%'`,
+ { timeoutMs: 5000, message: 'reset did not return the displayed percentage to 100%' },
+ );
+ await session.waitForFunction(
+ `${htmlZoomExpr()} === ''`,
+ { timeoutMs: 5000, message: 'reset did not clear the inline zoom on ' },
+ );
+ const storedOff = await session.evaluate(storedValueExpr());
+ if (storedOff !== '1') {
+ throw new Error(`expected persisted "1" after reset, saw ${JSON.stringify(storedOff)}`);
+ }
+ },
+};
+
+export default assertion;
diff --git a/packages/shared/src/i18n/locales/de.json b/packages/shared/src/i18n/locales/de.json
index d407e5dbb..b6425a23c 100644
--- a/packages/shared/src/i18n/locales/de.json
+++ b/packages/shared/src/i18n/locales/de.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "Ausführliche Werkzeugbeschreibungen",
"settings.appearance.reduceMotion": "Bewegung reduzieren",
"settings.appearance.reduceMotionDesc": "Animationen und Übergänge in der gesamten App minimieren.",
+ "settings.appearance.zoom": "Zoom",
+ "settings.appearance.zoomDesc": "Die gesamte Benutzeroberfläche vergrößern oder verkleinern.",
"settings.appearance.pet": "Begleiter",
"settings.appearance.petDesc": "Ein Begleiter, der auf die Aktivität des Agents reagiert.",
"settings.appearance.petEnabled": "Begleiter anzeigen",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "Verarbeitung stoppen",
"shortcuts.action.toggleFocusMode": "Fokusmodus umschalten",
"shortcuts.action.toggleSidebar": "Seitenleiste umschalten",
+ "shortcuts.action.zoomIn": "Vergrößern",
+ "shortcuts.action.zoomOut": "Verkleinern",
+ "shortcuts.action.zoomReset": "Zoom zurücksetzen",
"shortcuts.action.toggleTheme": "Design umschalten",
"shortcuts.addFilterExcluded": "Filter als ausgeschlossen hinzufügen",
"shortcuts.agentTree": "Agentenbaum",
diff --git a/packages/shared/src/i18n/locales/en.json b/packages/shared/src/i18n/locales/en.json
index 6a06e456e..267f0e5f1 100644
--- a/packages/shared/src/i18n/locales/en.json
+++ b/packages/shared/src/i18n/locales/en.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "Rich tool descriptions",
"settings.appearance.reduceMotion": "Reduce motion",
"settings.appearance.reduceMotionDesc": "Minimize animations and transitions throughout the app.",
+ "settings.appearance.zoom": "Zoom",
+ "settings.appearance.zoomDesc": "Scale the entire interface up or down.",
"settings.appearance.pet": "Pet",
"settings.appearance.petDesc": "A companion that reacts to what the agent is doing.",
"settings.appearance.petEnabled": "Show pet companion",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "Stop Processing",
"shortcuts.action.toggleFocusMode": "Toggle Focus Mode",
"shortcuts.action.toggleSidebar": "Toggle Sidebar",
+ "shortcuts.action.zoomIn": "Zoom In",
+ "shortcuts.action.zoomOut": "Zoom Out",
+ "shortcuts.action.zoomReset": "Reset Zoom",
"shortcuts.action.toggleTheme": "Toggle Theme",
"shortcuts.addFilterExcluded": "Add filter as excluded",
"shortcuts.agentTree": "Agent Tree",
diff --git a/packages/shared/src/i18n/locales/es.json b/packages/shared/src/i18n/locales/es.json
index 6461320a6..60a754165 100644
--- a/packages/shared/src/i18n/locales/es.json
+++ b/packages/shared/src/i18n/locales/es.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "Descripciones detalladas de herramientas",
"settings.appearance.reduceMotion": "Reducir movimiento",
"settings.appearance.reduceMotionDesc": "Minimiza las animaciones y transiciones en toda la aplicación.",
+ "settings.appearance.zoom": "Zoom",
+ "settings.appearance.zoomDesc": "Escala toda la interfaz para hacerla más grande o más pequeña.",
"settings.appearance.pet": "Mascota",
"settings.appearance.petDesc": "Un compañero que reacciona a lo que hace el agente.",
"settings.appearance.petEnabled": "Mostrar mascota",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "Detener procesamiento",
"shortcuts.action.toggleFocusMode": "Alternar modo enfoque",
"shortcuts.action.toggleSidebar": "Alternar barra lateral",
+ "shortcuts.action.zoomIn": "Acercar",
+ "shortcuts.action.zoomOut": "Alejar",
+ "shortcuts.action.zoomReset": "Restablecer zoom",
"shortcuts.action.toggleTheme": "Cambiar tema",
"shortcuts.addFilterExcluded": "Agregar filtro como excluido",
"shortcuts.agentTree": "Árbol de agentes",
diff --git a/packages/shared/src/i18n/locales/hu.json b/packages/shared/src/i18n/locales/hu.json
index 641c6d16f..be4236aa2 100644
--- a/packages/shared/src/i18n/locales/hu.json
+++ b/packages/shared/src/i18n/locales/hu.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "Részletes eszközleírások",
"settings.appearance.reduceMotion": "Mozgás csökkentése",
"settings.appearance.reduceMotionDesc": "Az animációk és átmenetek minimalizálása az egész alkalmazásban.",
+ "settings.appearance.zoom": "Nagyítás",
+ "settings.appearance.zoomDesc": "A teljes felület nagyítása vagy kicsinyítése.",
"settings.appearance.pet": "Kabala",
"settings.appearance.petDesc": "Egy társ, aki reagál arra, amit az ügynök csinál.",
"settings.appearance.petEnabled": "Kabala megjelenítése",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "Feldolgozás leállítása",
"shortcuts.action.toggleFocusMode": "Fókusz mód be/ki",
"shortcuts.action.toggleSidebar": "Oldalsáv be/ki",
+ "shortcuts.action.zoomIn": "Nagyítás",
+ "shortcuts.action.zoomOut": "Kicsinyítés",
+ "shortcuts.action.zoomReset": "Nagyítás visszaállítása",
"shortcuts.action.toggleTheme": "Téma váltása",
"shortcuts.addFilterExcluded": "Szűrő hozzáadása kizártként",
"shortcuts.agentTree": "Ügynökfa",
diff --git a/packages/shared/src/i18n/locales/ja.json b/packages/shared/src/i18n/locales/ja.json
index 5e2191990..5b94b3d4c 100644
--- a/packages/shared/src/i18n/locales/ja.json
+++ b/packages/shared/src/i18n/locales/ja.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "リッチなツール説明",
"settings.appearance.reduceMotion": "モーションを減らす",
"settings.appearance.reduceMotionDesc": "アプリ全体のアニメーションとトランジションを最小限にします。",
+ "settings.appearance.zoom": "ズーム",
+ "settings.appearance.zoomDesc": "インターフェース全体を拡大または縮小します。",
"settings.appearance.pet": "ペット",
"settings.appearance.petDesc": "エージェントの動きに反応するコンパニオン。",
"settings.appearance.petEnabled": "ペットを表示",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "処理を停止",
"shortcuts.action.toggleFocusMode": "フォーカスモードの切り替え",
"shortcuts.action.toggleSidebar": "サイドバーの切り替え",
+ "shortcuts.action.zoomIn": "拡大",
+ "shortcuts.action.zoomOut": "縮小",
+ "shortcuts.action.zoomReset": "ズームをリセット",
"shortcuts.action.toggleTheme": "テーマの切り替え",
"shortcuts.addFilterExcluded": "除外フィルターとして追加",
"shortcuts.agentTree": "エージェントツリー",
diff --git a/packages/shared/src/i18n/locales/pl.json b/packages/shared/src/i18n/locales/pl.json
index f85aa9c9f..7bead0703 100644
--- a/packages/shared/src/i18n/locales/pl.json
+++ b/packages/shared/src/i18n/locales/pl.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "Rozbudowane opisy narzędzi",
"settings.appearance.reduceMotion": "Ogranicz ruch",
"settings.appearance.reduceMotionDesc": "Ogranicz animacje i przejścia w całej aplikacji.",
+ "settings.appearance.zoom": "Powiększenie",
+ "settings.appearance.zoomDesc": "Skaluj cały interfejs w górę lub w dół.",
"settings.appearance.pet": "Maskotka",
"settings.appearance.petDesc": "Towarzysz reagujący na to, co robi agent.",
"settings.appearance.petEnabled": "Pokaż maskotkę",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "Zatrzymaj przetwarzanie",
"shortcuts.action.toggleFocusMode": "Przełącz tryb skupienia",
"shortcuts.action.toggleSidebar": "Przełącz pasek boczny",
+ "shortcuts.action.zoomIn": "Powiększ",
+ "shortcuts.action.zoomOut": "Pomniejsz",
+ "shortcuts.action.zoomReset": "Resetuj powiększenie",
"shortcuts.action.toggleTheme": "Przełącz motyw",
"shortcuts.addFilterExcluded": "Dodaj filtr jako wykluczony",
"shortcuts.agentTree": "Drzewo agentów",
diff --git a/packages/shared/src/i18n/locales/zh-Hans.json b/packages/shared/src/i18n/locales/zh-Hans.json
index 845231913..c6e680eff 100644
--- a/packages/shared/src/i18n/locales/zh-Hans.json
+++ b/packages/shared/src/i18n/locales/zh-Hans.json
@@ -787,6 +787,8 @@
"settings.appearance.richToolDescriptions": "丰富的工具描述",
"settings.appearance.reduceMotion": "减少动态效果",
"settings.appearance.reduceMotionDesc": "在整个应用中尽量减少动画和过渡效果。",
+ "settings.appearance.zoom": "缩放",
+ "settings.appearance.zoomDesc": "放大或缩小整个界面。",
"settings.appearance.pet": "宠物",
"settings.appearance.petDesc": "一个会根据 agent 当前状态做出反应的小伙伴。",
"settings.appearance.petEnabled": "显示宠物伙伴",
@@ -1172,6 +1174,9 @@
"shortcuts.action.stopProcessing": "停止处理",
"shortcuts.action.toggleFocusMode": "切换专注模式",
"shortcuts.action.toggleSidebar": "切换侧栏",
+ "shortcuts.action.zoomIn": "放大",
+ "shortcuts.action.zoomOut": "缩小",
+ "shortcuts.action.zoomReset": "重置缩放",
"shortcuts.action.toggleTheme": "切换主题",
"shortcuts.addFilterExcluded": "添加为排除筛选",
"shortcuts.agentTree": "智能体树",