Skip to content

feat(editor): open images in the shared pan and zoom viewer - #32185

Closed
bokeeeey wants to merge 3 commits into
stablyai:mainfrom
bokeeeey:feat/image-viewer-pan-zoom-popup
Closed

bokeeeey wants to merge 3 commits into
stablyai:mainfrom
bokeeeey:feat/image-viewer-pan-zoom-popup

Conversation

@bokeeeey

@bokeeeey bokeeeey commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Clicking an image in an editor tab opened a big popup that had no zoom buttons, and you couldn't drag the picture around. Now it opens in the same viewer that chat images, Markdown images and Mermaid diagrams already use. It has zoom out, zoom in, fit and close buttons, and you can drag the image to move around it.

What Changed

Before: the editor's full-size image popup (ImageViewerPopup) showed the image and a zoom percentage. It zoomed only with Ctrl+wheel or pinch, had no zoom buttons, and moved only with the scrollbars or the wheel. Dragging did nothing.

After: the popup is PanZoomImageDialog from #30154. It has zoom out, zoom in, fit-to-screen and close buttons, drag and wheel panning (Shift+wheel pans sideways), and pinch or Ctrl+wheel zoom at the cursor. It shows the file name in the corner, and Escape and the close button both close it.

How:

  • ImageViewer renders PanZoomImageDialog with the preview data URI and file name. ImageViewerPopup.tsx is deleted, along with the popup's own zoom state, surface ref, wheel listener and resize observer.
  • The three ImageViewerPopup strings are removed from every locale catalog, because the runtime-catalog check fails once their call site is gone.
  • Cmd/Ctrl +/- still zooms the open popup, as fix(editor): let Cmd/Ctrl +/- zoom an open image #32184 made it do. The dialog listens for the same app zoom request with the popup priority fix(editor): let Cmd/Ctrl +/- zoom an open image #32184 added, so it answers before the inline image behind it. Cmd/Ctrl+0 returns it to the fitted view. It hands the shortcut back as soon as it closes, not after its fade-out. ImageViewer now takes the shortcut only for its inline preview.
  • The inline preview, its footer zoom controls, its shortcut zoom and its Ctrl+wheel zoom are unchanged.
  • The same viewer serves image previews in diffs and conflict review, so they get the new popup too.

Behavior differences, both matching the other surfaces that use this dialog:

  • The popup opens fitted to the window instead of at the inline preview's zoom, and Cmd/Ctrl+0 returns it there instead of to 100%.
  • It no longer shows a zoom percentage.
  • Other surfaces that use this dialog get the shortcut too when it reaches a viewer, which is when an editor tab is active, for example a Markdown lightbox opened over an editor tab. Before, the shortcut changed the editor font behind the dialog there. A dialog opened from native chat is not offered the request, so the shortcut there is routed as it is today.

Why

The editor's popup was the last full-size image view still on the older implementation, so the same image behaved differently depending on where it was opened. Moving it onto the shared dialog fixes the missing controls and panning with a net deletion, and keeps future viewer fixes in one place.

#9557 tried to add panning to the old popup with a separate pointer controller. Reusing the dialog the app already ships avoids a second set of gestures to maintain.

Linked Issue

Fixes #32124

This also covers the popup half of #9337. Drag panning on the inline preview is left out, because a click on the inline image opens this popup.

Visual Proof

Headless Electron e2e build, 832×624 PNG opened from the explorer and clicked to open the popup. In the second row the script clicks Zoom in three times where the button exists, then drags from the center toward the top left.

Popup on open

Before (main) After
before-1-popup-open after-1-popup-open
No zoom buttons Zoom out, zoom in, fit and close

After zooming in and dragging

Before (main) After
before-2-zoomed-and-dragged after-2-zoomed-and-dragged
No button to click, and the drag does nothing Zoomed in three steps and dragged

Testing

  • I manually tested these changes locally (macOS, headless Electron e2e build, scenario above on main and on this branch)
  • Automated tests added/updated
    • ImageViewer.test.tsx now checks that clicking the preview opens PanZoomImageDialog on the same data URI and file name, and that closing it works.
    • The pre-load layout test now covers the one remaining scroll surface, since the dialog sizes its own image.
    • PanZoomImageDialog.test.tsx checks that the zoom request zooms the dialog's image ahead of a pane listener registered before it, that reset returns to the fitted view, and that a closing dialog still fading out leaves the request to the pane (this last check fails without the open guard).
    • The fix(editor): let Cmd/Ctrl +/- zoom an open image #32184 tests in image-viewer-zoom-ownership.test.tsx that read the old popup's percentage now check that the open popup takes the request and the inline image behind it stays at its zoom. Making the dialog's listener never claim fails 5 tests, and giving it the pane priority fails 4.
    • The dialog's fitting, zoom, pan limits and load-failure fallback are already covered by PanZoomImageDialog.test.tsx and pan-zoom-view.test.ts.
  • Rebased on main again (Oct 26). The only conflicts came from the repo-wide comment and lint cleanups ([comment audit] Complete repository-wide comment cleanup #33249, chore(lint): fix suppressed lint violations instead of ignoring them #33294): they edited ImageViewerPopup.tsx, which this PR deletes, and a test comment in ImageViewer.test.tsx. pnpm lint and pnpm typecheck pass locally. The editor, hooks, native-chat and sidebar suites pass (11,955 tests). One unhandled teardown error comes from terminal-presentation-pane-title.test.ts, which imports nothing this PR changes; it does the same when run alone.
  • I did not run the full pnpm test or pnpm build locally, because the full suite ran out of memory on my machine. The electron-vite e2e build used for the screenshots succeeded before this rebase, and the screenshots predate it. I'm relying on CI for the full suite.

AI Disclosure

I used Claude Code (Claude Fable 5.1 and Claude Opus 5.5) to research the existing viewers and related issues and PRs, write the change and tests, and capture the before/after screenshots.

Review

  • Cross-platform: renderer-only. The dialog already ships on all three platforms for chat and Markdown images.
  • SSH / remote / paired web: no IPC, RPC or persisted state changes. Remote and SSH images reach the viewer as the same data URI.
  • Mobile: not affected. The mobile file preview does not use this viewer.
  • Performance: less work than before. The popup no longer keeps its own resize observer and wheel listener, and the dialog's canvas mounts only while it is open.
  • Security: none. The dialog gets the same data URI the inline preview already renders.

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)
  • 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). Lint and typecheck pass locally; see Testing for the rest.

@coderabbitai

coderabbitai Bot commented Oct 23, 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: 5a6f53dd-daa1-4574-a690-1cf6d0a15e96
Commits

Reviewing files that changed from the base of the PR and between 0a5d9f4 and aeaeed0.

Files selected for processing: 11
  • src/renderer/src/components/editor/ImageViewer.test.tsx
  • src/renderer/src/components/editor/ImageViewer.tsx
  • src/renderer/src/components/editor/ImageViewerPopup.tsx
  • src/renderer/src/components/editor/PanZoomImageDialog.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/pt.json
  • src/renderer/src/i18n/locales/zh.json
Files with no reviewable changes: 8
  • src/renderer/src/components/editor/ImageViewerPopup.tsx
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/pt.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/en.json

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

ImageViewer now opens editor image previews in PanZoomImageDialog. The inline preview and its zoom handling remain. Popup-specific zoom state, sizing, and wheel handling were removed along with ImageViewerPopup and its locale entries. PanZoomImageDialog handles viewer zoom requests. Tests cover dialog behavior and zoom handling.

Priority: Normal

Severity of issue fixed: Medium

Merge Risk: Minimal · up to aeaee

The image preview now opens in the shared viewer. No actionable merge-blocking issue is established; normal CI checks remain appropriate.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 5 files. 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: replacing editor image popups with the shared pan and zoom viewer.
Description check Passed The description is complete and directly related to the change. It covers the user impact, implementation, rationale, linked issue, visual proof, testing, platform considerations, AI disclosure, and c…
Linked Issues check Passed Direct linked issue: #32124. Objective 1, open the editor image popup in the shared dialog: at the reviewed head, ImageViewer.tsx imports PanZoomImageDialog and renders it with open={isPopupOpen}, src…
Out of Scope Changes check Passed The changes connect to #32124. They replace the editor popup with the shared dialog, delete ImageViewerPopup and its locale keys, and update tests for the new wiring. Zoom-ownership changes are needed…
Full details: Description check

Explanation

The description is complete and directly related to the change. It covers the user impact, implementation, rationale, linked issue, visual proof, testing, platform considerations, AI disclosure, and checklist. It also clearly documents that the full test suite and build were not run locally and that CI is expected to cover them.

Full details: Linked Issues check

Explanation

Direct linked issue: #32124. Objective 1, open the editor image popup in the shared dialog: at the reviewed head, ImageViewer.tsx imports PanZoomImageDialog and renders it with open={isPopupOpen}, src={previewSrc}, title={filename}, showTitle. The inline surface onClick sets isPopupOpen to true. Met. Objective 2, remove ImageViewerPopup and its popup-only zoom state: the head ImageViewer.tsx has no popup zoom state, popup surface ref, popup wheel listener, or popup resize observer, and does not import ImageViewerPopup. The change summary reports ImageViewerPopup.tsx deleted and its three locale keys removed from all locale files. The file deletion and locale removal rest on the change summary, because the file-level diff could not be read. Objective 3, diff and conflict-review previews: they render ImageViewer, so they receive the dialog. Met. Objective 4, inline preview and footer zoom controls unchanged: the head ImageViewer.tsx keeps the footer zoom out, reset, zoom in and percentage controls, the inline wheel listener, and the inline image click. Met. Drag panning on the inline preview was not added, which matches the stated exclusion. Automated tests: the change summary lists updated ImageViewer.test.tsx, PanZoomImageDialog.test.tsx and image-viewer-zoom-ownership.test.tsx. Test bodies were not read directly. Full pnpm test was not run locally, so CI results are unverified.

Full details: Out of Scope Changes check

Explanation

The changes connect to #32124. They replace the editor popup with the shared dialog, delete ImageViewerPopup and its locale keys, and update tests for the new wiring. Zoom-ownership changes are needed so the open dialog takes Cmd/Ctrl +/- before the inline image behind it. In PanZoomImageDialog.tsx, the zoom listener uses popup priority and runs only while open. This keeps the keyboard zoom from #32184 working for the popup. Two side effects are connected to this zoom ownership work and are not unrelated. (1) The shared dialog's zoom steps, including toolbar zoom, now anchor at the canvas center, including when no manual view exists. This also affects chat, Markdown and Mermaid surfaces. The PR description does not list this. (2) In ImageViewer.tsx, the inline zoom listener returns early for intrinsic-layout images, as it does for PDFs and non-owners. The change summary says intrinsic-layout images no longer receive these requests. The PR description states that the inline shortcut zoom is unchanged. The code and the description disagree for this case. The base file could not be read, so the prior inline behavior for intrinsic-layout images is not confirmed. The description should be corrected or the gating reviewed. No IPC, persisted state, or platform-specific changes appear in the reviewed evidence.

  • 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

Comment @coderabbitai help to get the list of available commands.

@bokeeeey
bokeeeey force-pushed the feat/image-viewer-pan-zoom-popup branch from 0366080 to 726a01f Compare October 24, 2026 03:44
@DannyGelf

Copy link
Copy Markdown

+1, I hit this today on 1.4.240 with a tall diagram PNG opened from the file explorer. The popup has no zoom buttons and no drag-to-pan, so it's hard to read the diagram. I checked this PR's approach (reusing PanZoomImageDialog) against current main and it's what I'd want. It currently shows merge conflicts with main. Would be great to get it rebased and reviewed.

bokeeeey and others added 3 commits October 26, 2026 22:34
Clicking an image in an editor tab opened a popup with no zoom
controls and no drag panning. It now opens PanZoomImageDialog, the
viewer chat images, Markdown images and Mermaid diagrams already use,
with zoom in/out/fit buttons, drag and wheel panning, and pinch or
Ctrl+wheel zoom at the cursor. The old ImageViewerPopup and its
popup-only zoom state are removed. The inline preview is unchanged.

Cmd/Ctrl +/- keeps zooming the open popup, as stablyai#32184 made it do: the
dialog now listens for the app zoom request with the popup priority,
so it answers before the inline image behind it, and Cmd/Ctrl+0
returns it to the fitted view it opened at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ImageViewerPopup is gone, so its three catalog entries no longer have a
call site and fail the runtime-catalog check.

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

A closing dialog stays mounted while it fades out, and its popup-priority listener
kept taking Cmd/Ctrl +/- for those 200ms, so the pane's image missed the key. The
listener now follows `open`. The test also releases its pane listener when it
fails, so one failure doesn't leak into later tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bokeeeey
bokeeeey force-pushed the feat/image-viewer-pan-zoom-popup branch from 0a5d9f4 to aeaeed0 Compare October 26, 2026 13:40
@nwparker

Copy link
Copy Markdown
Contributor

Heads-up: #36576 replaces ImageViewerPopup and moves every editor image onto the shared pan and zoom engine (drag to pan, wheel/pinch zoom, diff images expand into the same dialog as chat images). Once it lands this will be superseded; I'll close it then.

@nwparker

Copy link
Copy Markdown
Contributor

Thanks @bokeeeey — this landed via #36576, which opens editor images (and SVGs) in the shared pan-and-zoom viewer with drag-to-pan, and builds on the direction here. Closing as superseded.

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.

[Feature]: Open editor images in the same pan and zoom viewer as chat images

3 participants