Skip to content

fix(er-diagram): zoom the ER and query plan diagrams on Cmd+scroll - #2840

Merged
datlechin merged 2 commits into
mainfrom
fix/diagram-cmd-scroll-zoom
Sep 14, 2026
Merged

datlechin merged 2 commits into
mainfrom
fix/diagram-cmd-scroll-zoom

Conversation

@datlechin

Copy link
Copy Markdown
Member

Problem

Holding Command and scrolling over the ER diagram or the EXPLAIN plan diagram scrolled the canvas instead of zooming it. The docs (docs/features/er-diagram.mdx, docs/features/explain-visualization.mdx) and the changelog both promise Cmd+scroll zoom.

Root cause

Both diagrams sit in MagnifiableCanvasView, a plain NSScrollView with allowsMagnification. That property wires magnify(with:) and smartMagnify(with:) only. Measured with a swiftc probe on macOS 27: a scroll-wheel event carrying Command, Option or Control, in line or pixel units, only scrolls. The view's header comment said Cmd+scroll came from AppKit.

#2117 moved both diagrams onto that component and deleted ERDiagramCanvasContainer, the scrollWheel(with:) override that used to turn a Command scroll into zoom (#1810). Nothing replaced it.

Every other surface was checked: the result map uses MapKit's own controls, and the cell image preview, charts, SQL editor, data grid and the iOS app have no canvas zoom, so none of them has this bug.

Fix

  • DiagramScrollView, an NSScrollView subclass the canvas now creates, overrides scrollWheel(with:). The scroll view already owns magnification, so it owns this zoom too.
  • DiagramScrollZoom holds the decision as a pure type:
    • Only Command alone zooms. Caps Lock, Fn and the keypad flag are ignored; Shift, Option and Control chords scroll.
    • A trackpad gesture decides zoom or scroll when it begins, so pressing or releasing Command mid-swipe changes nothing. Momentum after a zoom swipe is swallowed.
    • The factor is exp(points * 0.01): one wheel notch (10 points, NSScrollView's line scroll) is about 10.5%, a notch in and a notch out cancel exactly, and one event is capped at 30 points so an accelerated wheel spin cannot jump across the whole range.
    • Moving the wheel or fingers away from you zooms in, whatever the natural scrolling setting (isDirectionInvertedFromDevice).
  • The zoom is anchored at the pointer with setMagnification(_:centeredAt:), taking the point in clip-view space as NSScrollView.h specifies.
  • The subclass opts back into responsive scrolling, which overriding scrollWheel(with:) turns off; every scroll it does not zoom still goes to super.
  • The Reset Zoom button now carries the zoom level as its accessibility value, so VoiceOver reads it and the UI test can observe it.

Tests

  • DiagramScrollZoomTests: factor per notch and per point, natural scrolling, cancellation, the per-event cap, every modifier chord, the gesture latch, momentum, degenerate deltas.
  • DiagramScrollViewTests: real NSEvent(cgEvent:) wheel events into a real scroll view: Command zooms, other chords do not, zooming back returns to the start, the document point under the pointer stays within 0.5pt at 50%, 100% and 200%, the bounds hold, responsive scrolling stays on.
  • DiagramZoomUITests: opens the ER diagram on the sample database and checks a Command-modified scroll changes the zoom level.

Verification

  • DiagramScrollZoomTests, DiagramScrollViewTests, DiagramViewportControllerTests, DiagramZoomTests, DiagramPaintCoverageTests: 61 cases, all pass locally.
  • SwiftLint --strict on every changed file: 0 violations.
  • DiagramZoomUITests did not run locally: macOS held an unanswered "Enable UI Automation" prompt, so the runner timed out enabling automation mode with zero cases executed. CI runs it.
  • Codex could not review (usage limit); the diff was reviewed with /code-review high, which found no defects.
  • Not measured: whether AppKit keeps delivering each swipe's first event to the scrollWheel(with:) override with responsive scrolling on. Synthesized events cannot exercise a real trackpad gesture.

Before / After

This is an input change with no static visual difference: before, the diagram scrolled under Cmd+scroll; after, it zooms around the pointer. The UI test covers it in the running app.

Part of a stack: the next PR moves pointer input in both diagrams to AppKit, because SwiftUI reports clicks and drags at the wrong position once a diagram is zoomed.

https://claude.ai/code/session_015m6g84UEwS6v3m2twr9Taw

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.

1 participant