Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app by DragonnZhang · Pull Request #69 · modelstudioai/openwork · GitHub
Skip to content

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app - #69

Merged
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom
Jul 19, 2026
Merged

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app#69
DragonnZhang merged 2 commits into
mainfrom
loop/interface-zoom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Member

Closes#68

What & why

Every desktop app OpenWork is benchmarked against — Claude Desktop, VS Code, Codex/ChatGPT desktop, Slack, Discord — exposes an interface zoom that scales the whole UI up or down via the platform-standard ⌘+ / ⌘- / ⌘0 (Ctrl on Windows/Linux). OpenWork had none: grep -rn "setZoomLevel\|zoomFactor\|webFrame\|zoom" apps/electron/src found nothing, and there was no such binding in the action registry. Users on high-DPI displays (or who want to fit more on screen) had no way to resize the whole app.

This adds an app-wide interface-zoom preference — frontend-only, no backend / qwen-code change.

Distinct from any per-surface "chat text size" / "conversation width" work: interface zoom scales the entire renderer (sidebar, navigator, chat, settings, dialogs, composer), not just message text.

How

  • context/ZoomContext.tsx (new) — ZoomProvider, mirroring the merged ReduceMotionProvider. Owns a discrete zoom factor from a Chrome-like step ladder (50%–200%), applies it by setting document.documentElement.style.zoom (a Chromium-native property that scales the whole tree, including portalled dialogs), and clears the inline style at 100%. Persists in localStorage (craft-zoom-level) via the existing lib/local-storage.ts helper — no IPC, no config.json.
  • actions/definitions.ts — three new View actions: view.zoomIn (mod+=), view.zoomOut (mod+-), view.zoomReset (mod+0). Registering them means they appear automatically in the Command Palette (⌘K) and the Settings → Keyboard Shortcuts reference.
  • components/ZoomHotkeys.tsx (new) — a render-null bridge mounted inside ActionRegistryProvider that wires those actions to the provider via useAction.
  • main.tsx — mounts ZoomProvider at the renderer root (inside ReduceMotionProvider).
  • pages/settings/AppearanceSettingsPage.tsx — a Zoom stepper ( / percentage / + with reset) in Appearance → Interface, for discoverability and mouse-only users.
  • lib/local-storage.ts — new zoomLevel key.
  • actions/action-i18n.ts + locales/*.json — 5 new i18n keys across all 7 locales.
  • e2e/assertions/zoom.assert.ts (new) — CDP assertion (below).

Verification (DoD)

Run in this PR's sandbox; all checks that don't require launching Electron pass with zero delta vs main:

  • bun run typecheck:all — no new errors. apps/electron reports exactly the 11 pre-existing errors (auto-update.tsowner/repo, a settings-default-thinking test tuple, two test files importing vitest) — none in files this PR touches; all other packages clean.
  • bun test — the failing-test set is byte-for-byte identical to a clean main run in the same environment: 56 failing tests on both, diff empty. This change adds zero new failures. (The raw N fail line is order-dependent when the whole suite runs unisolated; the deterministic signal is the failing-test set.)
  • bun run lint:i18n:parity — OK (1549 keys per locale, 7 locales). The 5 new keys are in every locale.
  • eslint on all changed files — 0 errors. The new files add no warnings; the pre-existing no-localstorage / exhaustive-deps warnings are unchanged.
  • Renderer build (bun run build:renderer, where this change lives) — ✅ built cleanly.
  • CDP assertion transpiles (bun build e2e/assertions/zoom.assert.ts) — ✅.

CDP e2e assertion (zoom.assert.ts)

Drives the real built app over CDP entirely in the draft/no-session state (Settings only, no backend):

  1. Open Settings → Appearance → Interface; the Zoom control renders at 100%, with no inline zoom style on <html>.
  2. Click Zoom in → the display reads 110%, document.documentElement.style.zoom === '1.1', and localStorage["craft-zoom-level"] is 1.1 — proving it applies and persists, not merely renders.
  3. Click Reset → back to 100%, the inline zoom style is cleared, and the persisted value returns to 1.

⚠️Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (GitHub release host) returns 403 from the org egress proxy — its allowlist covers npm/pypi/crates but not GitHub release hosts — so Electron can't be launched here (xvfb is present; only the binary fetch fails; the proxy README says to report such policy denials, not route around them). The assertion transpiles and is included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, lint, renderer build) passes with zero delta vs main. This is the same documented environment limitation as the merged reduce-motion PR (#51).

Part of the autonomous desktop-feature loop (loop-bot).

🤖 Generated with Claude Code


Generated by Claude Code

DragonnZhang pushed a commit that referenced this pull request Jul 19, 2026
Resolve merge conflict in docs/loop/feature-ledger.md: adopt main's canonical
ledger and prepend this PR's interface-zoom (#69) row. No code changes.
claude added 2 commits July 20, 2026 01:06
Adds an app-wide interface-zoom preference that scales the entire renderer
(sidebar, navigator, chat, settings, dialogs) up or down, matching the
standard desktop zoom in Claude Desktop, VS Code, and other apps.
- ZoomProvider (renderer context, mirrors ReduceMotionProvider): owns a
discrete zoom factor from a Chrome-like step ladder (50%-200%), applies it
via document.documentElement.style.zoom, and persists it in localStorage
(craft-zoom-level). Frontend-only; no IPC, no backend, no config.json.
- Three action-registry entries under View - view.zoomIn (mod+=),
view.zoomOut (mod+-), view.zoomReset (mod+0) - so the shortcuts appear in
the Command Palette and Keyboard Shortcuts reference for free. Wired to the
provider via a ZoomHotkeys bridge component.
- A Zoom stepper (-/percentage/+ with reset) in Settings > Appearance >
Interface for discoverability and mouse-only users.
- i18n keys added to all 7 locales.
- CDP e2e assertion (zoom.assert.ts) drives the stepper and asserts the
percentage, the inline zoom on <html>, and the persisted value.
Closes#68
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interface zoom (⌘+ / ⌘- / ⌘0) to scale the whole app up or down

2 participants

@DragonnZhang@claude