Skip to content

feat: app-wide zoom shortcuts (⌘/Ctrl +, −, 0), persisted - #123

Merged
mattenarle10 merged 1 commit into
mattenarle10:mainfrom
sashamerzliakov:feat/app-wide-zoom
Jul 27, 2026
Merged

feat: app-wide zoom shortcuts (⌘/Ctrl +, −, 0), persisted#123
mattenarle10 merged 1 commit into
mattenarle10:mainfrom
sashamerzliakov:feat/app-wide-zoom

Conversation

@sashamerzliakov

Copy link
Copy Markdown
Contributor

Transparency up front: this feature was built with AI assistance (Claude — see the commit trailer) in my fork. I've verified the behavior on my own machine (macOS, Apple Silicon); platform notes below state exactly what was and wasn't tested.

Motivation — the gap #89 doesn't cover

#84 asked for Cmd+/− zoom for presenting, and #89 answered it with reading-size controls plus quick zoom buttons in reading mode — which solve it well for prose. This PR is about the surface #89 doesn't reach: the sidebar, tabs, status bar, dialogs, and the editor pane stay at fixed size, and outside reading mode there's no zoom at all. Whole-UI scaling is the browser-standard behavior for ⌘/Ctrl + 0 (which are currently unbound), and it matters for the same presenting/eyesight scenarios as #84 — just for the whole app rather than the article body.

The two axes stay complementary and independent: the reading-size controls keep governing prose type; these shortcuts scale the entire UI around it. (⌘0 resets the UI scale only — it deliberately leaves your reading-size preference alone.)

Why not zoomHotkeysEnabled?

Tauri ships built-in zoom hotkeys behind a one-line config flag, and I considered it first. The built-in polyfill resets to 100% on every launch (no persistence), steps 20% at a time over a 20%–1000% range, and isn't integrated with the app (no help-overlay/README documentation surface, no way to reason about interactions like reading mode). This implementation persists across restarts, steps 10% over a saner 50%–300%, and lives in the same shortcut system as everything else. Happy to switch to the flag if you'd rather have the one-liner — but I think the persistence alone is worth the ~70 lines.

What it does

  • ⌘= (and ⌘⇧=, i.e. ⌘+, plus numpad +) zooms in 10%, ⌘− (and numpad ) zooms out 10%, ⌘0 resets — clamped to 50%–300%. On Windows/Linux the same bindings arrive as Ctrl via the existing mod handling.
  • Zoom is applied at the webview level (getCurrentWebview().setZoom()), so editor, preview, sidebar, and chrome scale together.
  • The level persists across restarts (new mdview.zoomLevel storage key), and persisted values are normalized on load (finite, one decimal, clamped) so corrupted localStorage can never drive the webview outside the supported range.
  • The detached preview window zooms too: the shared hook runs in both window roots — each window applies the persisted level when it opens, and the shortcuts act on whichever window has focus.
  • The macOS titlebar's traffic-light inset is counter-scaled via a --mdv-zoom CSS variable — the native buttons keep their physical size under page zoom, so a fixed 84px inset would otherwise slide content beneath them below 100%.

Implementation

  • src/hooks/use-app-zoom.ts — new hook: persisted + normalized zoom state, the setZoom effect (a rejected call logs a console.error rather than leaving an unhandled rejection), the --mdv-zoom CSS variable, and the four shortcut bindings
  • src/app.tsx, src/components/editor/preview-window.tsx — one useAppZoom() call in each window root
  • src/hooks/use-shortcuts.ts — the shifted-symbol code map now accepts multiple codes per key, adding NumpadAdd/NumpadSubtract so keypad zoom works (numpad + reports e.key === "+", which can't be expressed in the combo syntax and would otherwise never match)
  • src/styles/chrome/titlebar.css — traffic-light inset counter-scaled by --mdv-zoom
  • src/lib/storage.ts — one storage key; src-tauri/capabilities/default.json — adds core:webview:allow-set-webview-zoom (already granted to both windows)
  • tests/app-zoom.test.ts — unit tests for the normalize/clamp helper: bounds, corrupt hydration values, rounding/drift over full-range stepping
  • README keyboard rows + help overlay entries (zoom in / zoom out / reset as separate rows) in all ten locales

No new dependencies. mod+=, mod+shift+=, mod+-, and mod+0 are unbound today (verified against the current shortcut map and against CodeMirror's default/search/history/vim keymaps, none of which bind these keys — events bubble to the window listener).

Platform notes

Tauri's Webview.setZoom (wry 0.55, per the lockfile) maps to WKWebView setPageZoom on macOS, Controller.SetZoomFactor on Windows WebView2, and set_zoom_level on webkitgtk on Linux. setPageZoom is macOS 11+ (per wry's wkwebview module and Tauri's rustdoc). One thing worth your call: the README advertises macOS 13+, but tauri.conf.json sets no minimumSystemVersion, so the built binary's deployment target is Tauri's default 10.13 — on 10.13–10.15 the zoom call would be a no-op at best. If 13+ is the intended floor, pinning minimumSystemVersion would close that gap independently of this PR.

Verified manually on macOS (Apple Silicon): stepping, clamping at both ends, reset, persistence across restart, the detached preview window, the help overlay rows, titlebar/traffic-light alignment at 50%/100%/300%, and interplay with the reading-size settings. I have not tested Windows or Linux — happy to adjust if either behaves differently.

Testing

  • bun test — 50 pass (3 new zoom tests); bunx tsc --noEmit clean.
  • Manual (macOS): as listed under platform notes, including while focus is in the CodeMirror editor.

Webview-level zoom so editor, preview, sidebar and chrome scale together,
complementing the reading-size controls (mattenarle10#84/mattenarle10#89), which keep governing
prose type. 10% steps clamped to 50%-300%, normalized on load so corrupted
storage can't escape the range, persisted across restarts. Shared
use-app-zoom hook runs in both window roots (main + detached preview);
numpad +/- supported via the shortcut matcher's code map; the macOS
traffic-light inset is counter-scaled so native chrome stays aligned at
any zoom. Unit tests for the normalize/clamp helper. Adds the
set-webview-zoom capability, README keyboard rows, and help-overlay
entries in all ten locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mattenarle10
mattenarle10 merged commit 50c9f76 into mattenarle10:main Jul 27, 2026
2 checks passed
sashamerzliakov added a commit to sashamerzliakov/markamd that referenced this pull request Jul 27, 2026
sashamerzliakov added a commit to sashamerzliakov/markamd that referenced this pull request Jul 29, 2026
- pr-plan.md: fork track marked merged (#1 / d312b49), the three-way split
  replaced with what actually landed and why it landed as one PR, order of
  operations rewritten around what's done and what's next.
- CUSTOM.md: files-touched list was three features out of date — it predates
  the watcher rewrite, the minimal-diff dispatch, the tsv work, the tests and
  the checklist.
- Removed docs/upstream-submission-drafts.md and docs/upstream-zoom-pr-draft.md.
  Both described PRs that merged upstream as mattenarle10#122 and mattenarle10#123 in July; leaving
  them reads as pending work.

The release gap is now stated in the plan rather than implied: with no fork
release, this repo's install link and badges point at upstream's builds, so a
visitor downloading from the front page gets an app without any of the
features listed above the link.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants