Skip to content

fix(editor): send Cmd/Ctrl +/-/0 to an open image viewer outside editor tabs - #37521

Merged
AmoabaKelvin merged 1 commit into
stablyai:mainfrom
bokeeeey:fix/image-popup-zoom-shortcut
Nov 2, 2026
Merged

AmoabaKelvin merged 1 commit into
stablyai:mainfrom
bokeeeey:fix/image-popup-zoom-shortcut

Conversation

@bokeeeey

@bokeeeey bokeeeey commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

ELI5

With an image or diagram viewer open over a chat tab (and, from the code, terminal and browser tabs), ⌘= / ⌘- now zoom the image and ⌘0 fits it again. Before, they changed Orca's UI Zoom for the whole window and saved it, and ⌘0 threw away a custom UI Zoom.

What Changed

  • Before: only the editor branch of the zoom bridge offered the command to an image viewer. Viewer dialogs are portaled outside every pane, so with the viewer open over any other tab or page the command fell through to UI Zoom. View → Zoom In / Zoom Out / Reset Size take the same path.
  • After: right before that UI Zoom fallthrough, the bridge offers the command to a viewer in the frontmost open dialog, which is the last open [role="dialog"] / [role="alertdialog"] in the document (requestFrontDialogViewerZoom). If that dialog has no viewer, UI Zoom changes as before. The chat, terminal, browser and editor branches are untouched.
  • viewer-zoom-request.ts: a request can now be sent into one element, and a listener takes it only when its viewer is inside that element. Requests sent from document, which the editor branch uses, behave as before.

Why

The viewer already listens for this request with popup priority (#32184, #36576); it just never received it outside the editor branch. Every entry point renders the same PanZoomImageDialog (chat attachments and transcript, Markdown and Mermaid images in comments and chat, Jira attachments, browser annotation images), so one call covers them. I exercised the chat one.

The viewer only gets the command while it is the front dialog. When another dialog is drawn over it, such as Quick Open or the workspace board sheet, the command keeps going to UI Zoom as it does today. The editor branch is unchanged, so there the viewer keeps taking the command even behind Quick Open, as on main.

Alternatives I ruled out:

  • Calling the existing requestViewerZoom for every target. A viewer behind Quick Open or the board would still take the command, and editor-pane viewers, which only the editor branch reaches today, would be offered commands from the other branches too.
  • A viewer target in resolveZoomTarget keyed on focus. Clicking Fit to screen disables that button and drops focus to the body, so the next ⌘= would change UI Zoom again.

Linked Issue

Fixes #37489

Visual Proof

macOS, hidden dev build. A 5200×2600 test image pasted into a Claude chat tab's message box and opened in the viewer, with UI Zoom set to 83% first. Commands were sent as the terminal:zoom IPC that the main-process shortcut handler sends for ⌘= (synthetic CDP keys bypass before-input-event). "main" is this branch with its two source files reverted to main.

Claude Chat tab with the pasted test image in the message box
After one zoom-in (the ⌘= path) main this PR
Viewer main: after zoom-in the image is still fitted This PR: after zoom-in the image is zoomed
Toolbar main: Fit to screen disabled Fit to screen disabled: still fitted This PR: Fit to screen enabled Fit to screen enabled: zoomed
Settings → UI Zoom after closing main: UI Zoom 91% 91%, saved This PR: UI Zoom 83% still 83%

Also checked in the same build:

  • After clicking Fit to screen (focus on the body), the next zoom-in still zooms the image.
  • With the workspace board opened over the viewer (ui:openWorkspaceBoard, what its shortcut sends), zoom-in changes UI Zoom, as on main. The board covers most of the viewer.
  • With Quick Open (ui:openQuickOpen) open over the viewer, zoom-in changes UI Zoom, as on main.

#37489 has screenshots of the bug taken with real key presses.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Manual, macOS: real key presses on a visible dev build of this branch, viewer open over a Claude chat tab, UI Zoom at 83%. ⌘= ⌘= ⌘- ⌘0 took the image 2976 → 3720 → 4650 → 3720 px and back to fitted, and UI Zoom stayed at 83%, saved value included. After clicking Fit to screen, ⌘= still zoomed the image. With the viewer closed, ⌘0 reset UI Zoom to 100% as before. The checks under Visual Proof used the IPC instead, and the bug on main was reproduced with real keys (#37489).

Automated: a new case in PanZoomImageDialog.test.tsx renders the real viewer and Radix dialogs and drives the real zoom bridge and store. ResizeObserver, layout sizes, image size and window.api are stubbed, and the workspace board is a stand-in non-modal Radix dialog.

  • Over a chat tab, zoom-in, reset (⌘0) and zoom-in with focus on the body move the viewer and leave UI Zoom alone.
  • Over an editor tab, the viewer still zooms and the editor font does not.
  • With a non-modal dialog drawn over the viewer, UI Zoom changes as on main.
  • The case fails on main, and again when any one part is removed: the bridge call, the scope check in the listener, or picking the last open dialog (the first one, or every one, fails).

Related suites pass: PanZoomImageDialog, zoom-routing, viewer-zoom-request, image and PDF zoom ownership (5 files, 21 tests). pnpm check:changed passes on the final commit. zoom-routing.test.ts mocks the request module, so it only gains the new export there.

Not tested: Windows and Linux (same renderer path, Ctrl instead of ⌘), and by hand the terminal, browser and simulator tabs, the Tasks page and the View menu items. They all reach the same fallthrough.

Risk

Level: Low

Renderer only, and only where the command would otherwise change UI Zoom.

  • The rule assumes the last open dialog in the document is the one drawn on top, which holds while dialogs portal to the end of <body> as they open. A layer that breaks this would send the command to the wrong place: UI Zoom instead of the viewer, or the reverse.
  • While a dialog over the viewer is fading out it is already data-state="closed", so the viewer gets the command again.
  • When the image fails to load, the viewer still takes the command and nothing visible happens. The editor branch already behaves this way; on main this case changed UI Zoom.

Only PanZoomImageDialog listens inside a dialog today; editor-pane viewers need EditorPanel's command-owner context, which no dialog provides. Nothing new is persisted or sent over IPC or RPC, so a plain revert backs it out.

AI Disclosure

Claude Code (Claude Opus 5.5) helped investigate, write the code, tests and this description, and reviewed the diff.

Review

Claude Code review of the final diff:

  • Cross-platform: renderer only, no platform branches. Ctrl arrives as the same terminal:zoom IPC.
  • SSH / remote: the viewer zooms local view state; no RPC.
  • Mobile and the web client: onTerminalZoom is a no-op there, so they are unaffected and the browser's own zoom applies.
  • Integrations: Markdown images and Mermaid diagrams in comments, and Jira attachments, open the same viewer and get the fix; no provider code changes. Agents are not affected.
  • UI: no visual change apart from where the command goes.
  • Performance: one document-wide querySelectorAll for open dialogs, only on the UI Zoom path.
  • Security: no new inputs, IPC or persisted data.
  • Changed during review: an earlier version ran ahead of every branch and tried every open dialog. That let a viewer covered by the workspace board take the command with nothing visible happening. It now runs only at the UI Zoom fallthrough and only for the front dialog.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • I stated the risk: what could break, for whom, and how to back it out
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred). Not run in full locally; pnpm check:changed and the focused suites pass, and CI covers the rest.

🤖 Generated with Claude Code

…or tabs

Image and diagram viewer dialogs are portaled outside every pane, so the
zoom bridge never offered them the command unless an editor tab was
active. Over chat, terminal and other tabs the shortcut changed and saved
the app's UI Zoom instead. Where the command would otherwise change UI
Zoom, offer it first to a viewer in the frontmost open dialog.

Fixes stablyai#37489

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7299acd6-6c59-473b-b763-d1f9d4799ac3
Commits

Reviewing files that changed from the base of the PR and between d616c1b and a6f3c10.

Files selected for processing: 4
  • src/renderer/src/components/editor/PanZoomImageDialog.test.tsx
  • src/renderer/src/components/editor/viewer-zoom-request.ts
  • src/renderer/src/hooks/ipc-events/zoom-ipc-bridge.ts
  • src/renderer/src/hooks/zoom-routing.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The zoom bridge now offers zoom requests to the frontmost dialog viewer after the editor route. The viewer listener accepts requests only when the event target contains the viewer root. If no dialog viewer handles the request, processing continues to UI zoom. Tests cover viewer zoom, editor zoom, and UI zoom behavior when another dialog overlays the viewer.

Severity of issue fixed: Medium

Merge Risk: Minimal · up to a6f3c

No merge-blocking issue was identified in the zoom routing change.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: routing Cmd/Ctrl zoom commands to image viewers outside editor tabs.
Description check Passed The description is complete and relevant. It covers the user impact, implementation, rationale, linked issue, visual proof, testing, risks, compatibility considerations, AI disclosure, and checklist s…
Linked Issues check Passed [ #37489 ] is directly linked and active. zoom-ipc-bridge.ts now calls requestFrontDialogViewerZoom before UI Zoom fallback. viewer-zoom-request.ts selects the last open dialog or alert dialog a…
Out of Scope Changes check Passed The changed files support [ #37489 ]. The request helper adds the required front-dialog routing. The bridge integrates that routing at the UI Zoom fallback. The test validates the new behavior and reg…
Full details: Description check

Explanation

The description is complete and relevant. It covers the user impact, implementation, rationale, linked issue, visual proof, testing, risks, compatibility considerations, AI disclosure, and checklist status.

Full details: Linked Issues check

Explanation

[ #37489 ] is directly linked and active. zoom-ipc-bridge.ts now calls requestFrontDialogViewerZoom before UI Zoom fallback. viewer-zoom-request.ts selects the last open dialog or alert dialog and scopes the request to that dialog. Existing document-scoped requests remain unchanged. The added PanZoomImageDialog.test.tsx case covers zoom in, reset, focus loss, editor behavior, and UI Zoom fallback when another dialog overlays the viewer. These changes implement the requested viewer zoom behavior and preserve the saved UI Zoom setting.

Full details: Out of Scope Changes check

Explanation

The changed files support [ #37489 ]. The request helper adds the required front-dialog routing. The bridge integrates that routing at the UI Zoom fallback. The test validates the new behavior and regression cases. No unrelated product behavior or unrelated files are changed.

Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 4 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@AmoabaKelvin
AmoabaKelvin merged commit 52d65e9 into stablyai:main Nov 2, 2026
41 checks passed
@bokeeeey
bokeeeey deleted the fix/image-popup-zoom-shortcut branch November 3, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: ⌘= / ⌘- / ⌘0 in a chat image or diagram viewer change the saved UI Zoom instead of zooming the image

2 participants