Ctrl and the wheel over a PDF

The same gesture Aether has, whose comment already claimed InkNote had it.
Registered by hand rather than through onWheel, because React attaches wheel
listeners passively and a passive listener may not call preventDefault —
without which the browser zooms the whole application instead of the
document.

What stays still depends on how the zoom was asked for, and the two answers
differ: the wheel holds the point under the pointer, the buttons hold the
page being read. The buttons held nothing at all before, so three presses of
+ left you somewhere else in the file; that came out of building this and is
fixed with it.

The checks are the step, that the default really was prevented, and that a
point measured on the page is still under the pointer afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-25 15:00:54 +02:00
1 parent 8f8fecfb96
commit 580eed8992
4 files changed
+211 -7

No files matched your search

+16 -1
View File
@@ -618,7 +618,7 @@ docker compose -f docker-compose.dev.yml up -d --build # must be running
| `search-attachments` | 12 | body-only matches, ranking, umlauts; images as blob URLs, not inline bytes |
| `history` | 10 | a large deletion is snapshotted, restoring works, restoring is itself undoable |
| `drawing` | 22 | strokes survive the poller, match across four viewport widths, and stay on the page they were drawn on |
| `pdf` | 91 | import, page proportions, lazy loading, and reading with the network off |
| `pdf` | 96 | import, page proportions, lazy loading, and reading with the network off |
| `attachments-lifecycle` | 10 | a picture is kept while any version shows it, and freed once none does |
| `conflict` | 10 | a late write wins but the state it buries survives as a version |
| `export` | 23 | the downloaded bytes are real Markdown, ZIP and PNG, unpacked and read back |
@@ -880,6 +880,21 @@ page is expensive to draw and cheap to hold, so scrolling back up repaints
nothing. Canvases are sized in device pixels and scaled down by CSS, which is
what keeps text sharp on a high-density screen.
**Zooming holds something still.** A zoom changes how tall the document is, so
the scroll position means something different afterwards — left alone, every step
drifts and three presses of + leave you somewhere else in the file. What should
stay put depends on how the zoom was asked for, and the two answers are
different: from the buttons it is **the page you are reading**, because the
request is "show me this page larger"; from **Ctrl + the wheel** it is the point
under the pointer, because you are pointing at a word and asking for that to
grow. The wheel listener is registered by hand rather than through `onWheel`:
React attaches wheel listeners passively, a passive listener may not call
`preventDefault`, and without that call the *browser* zooms the whole application
— toolbar, sidebar and all. A trackpad pinch arrives as the same event and needs
nothing further. The suite checks all three: the step, that the default really
was prevented, and that a point picked on the page is still under the pointer
afterwards.
**pdf.js is not in the main bundle.** It is larger than the rest of the
application put together, so it sits behind a dynamic import: a session that
never opens a PDF never downloads it. The worker is bundled too, not fetched