From 33f32cccf63a8faa02df4a142b220487bbd22443 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 17 Aug 2026 11:29:20 -0700 Subject: [PATCH] Leave browser zoom to the browser (#578) The web build bound Cmd/Ctrl +, - and 0 and then did nothing with them, so zoom appeared broken. Those keys are the browser's own page zoom, which scales the whole page and remembers it per site. Adds an interfaceZoom capability: true on desktop, where the host zooms the webview, false in a browser. When false the app binds nothing and the hotkeys screen drops the three rows it can't configure. --- .../components/Settings/SettingsHotkeys.tsx | 2 +- apps/yaak-client/hooks/useHotKey.ts | 34 +++++++++++++------ packages/platform/src/tauri/index.ts | 1 + packages/platform/src/types.ts | 5 +++ packages/platform/src/web/README.md | 5 ++- packages/platform/src/web/index.ts | 3 ++ 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/apps/yaak-client/components/Settings/SettingsHotkeys.tsx b/apps/yaak-client/components/Settings/SettingsHotkeys.tsx index 1d8c82f3..14709ec2 100644 --- a/apps/yaak-client/components/Settings/SettingsHotkeys.tsx +++ b/apps/yaak-client/components/Settings/SettingsHotkeys.tsx @@ -124,7 +124,7 @@ export function SettingsHotkeys() { { const newHotkeys = { ...settings.hotkeys }; diff --git a/apps/yaak-client/hooks/useHotKey.ts b/apps/yaak-client/hooks/useHotKey.ts index f0efe8d6..237786b5 100644 --- a/apps/yaak-client/hooks/useHotKey.ts +++ b/apps/yaak-client/hooks/useHotKey.ts @@ -112,9 +112,12 @@ export const hotkeysAtom = atom((get) => { // Merge default hotkeys with custom hotkeys from settings // Custom hotkeys override defaults for the same action // An empty array means the hotkey is intentionally disabled - const merged: Record = { ...defaultHotkeys }; + const merged: Partial> = {}; + for (const action of hotkeyActions) { + merged[action] = defaultHotkeys[action]; + } for (const [action, keys] of Object.entries(customHotkeys)) { - if (action in defaultHotkeys && Array.isArray(keys)) { + if (action in merged && Array.isArray(keys)) { merged[action as HotkeyAction] = keys; } } @@ -122,7 +125,7 @@ export const hotkeysAtom = atom((get) => { }); /** Helper function to get current hotkeys from the store */ -function getHotkeys(): Record { +function getHotkeys(): Partial> { return jotaiStore.get(hotkeysAtom); } @@ -165,16 +168,25 @@ const layoutInsensitiveKeys = [ "Space", ]; +/** Zoom is the browser's own on these keys, so the app has no such action there. */ +const ZOOM_ACTIONS: HotkeyAction[] = ["app.zoom_in", "app.zoom_out", "app.zoom_reset"]; + +/** + * The actions this host actually has. An action left out of here has no keys in + * `hotkeysAtom`, so it never matches and never claims the keystroke. + */ export const hotkeyActions: HotkeyAction[] = ( Object.keys(defaultHotkeys) as (keyof typeof defaultHotkeys)[] -).sort((a, b) => { - const scopeA = a.split(".")[0] || ""; - const scopeB = b.split(".")[0] || ""; - if (scopeA !== scopeB) { - return scopeA.localeCompare(scopeB); - } - return hotkeyLabels[a].localeCompare(hotkeyLabels[b]); -}); +) + .filter((a) => platform.capabilities.interfaceZoom || !ZOOM_ACTIONS.includes(a)) + .sort((a, b) => { + const scopeA = a.split(".")[0] || ""; + const scopeB = b.split(".")[0] || ""; + if (scopeA !== scopeB) { + return scopeA.localeCompare(scopeB); + } + return hotkeyLabels[a].localeCompare(hotkeyLabels[b]); + }); export type HotKeyOptions = { enable?: boolean | (() => boolean); diff --git a/packages/platform/src/tauri/index.ts b/packages/platform/src/tauri/index.ts index 271ed37f..3db70afa 100644 --- a/packages/platform/src/tauri/index.ts +++ b/packages/platform/src/tauri/index.ts @@ -59,6 +59,7 @@ const ALL_CAPABILITIES: PlatformCapabilities = { timeline: true, multiWindow: true, windowChrome: true, + interfaceZoom: true, plugins: true, encryption: true, updater: true, diff --git a/packages/platform/src/types.ts b/packages/platform/src/types.ts index de077348..2969d301 100644 --- a/packages/platform/src/types.ts +++ b/packages/platform/src/types.ts @@ -262,6 +262,11 @@ export interface PlatformCapabilities { * chrome should be reserved or drawn. */ windowChrome: boolean; + /** + * The app zooms its own interface, and so owns Cmd/Ctrl `+`, `-` and `0`. + * False in a browser, where those keys are already the browser's. + */ + interfaceZoom: boolean; /** The plugin runtime. */ plugins: boolean; /** Workspace encryption backed by a key the host keeps. */ diff --git a/packages/platform/src/web/README.md b/packages/platform/src/web/README.md index c80cd97d..5bdae5aa 100644 --- a/packages/platform/src/web/README.md +++ b/packages/platform/src/web/README.md @@ -129,7 +129,10 @@ Reported honestly, so callers gate on the question rather than on the host: | True | False | | --- | --- | -| `cookieJar` (the jar stores and edits here; only filling it needs the sender) | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `timeline`, `multiWindow`, `windowChrome`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` | +| `cookieJar` (the jar stores and edits here; only filling it needs the sender) | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `timeline`, `multiWindow`, `windowChrome`, `interfaceZoom`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` | + +`interfaceZoom: false` leaves Cmd/Ctrl `+`, `-` and `0` to the browser instead +of swallowing them, and drops those three rows from the hotkeys screen. `multiWindow: false` means the host cannot open a *second window* on demand — what `cmd_new_child_window` does for Settings and workspace switching. It is not diff --git a/packages/platform/src/web/index.ts b/packages/platform/src/web/index.ts index 530ae4e4..77cac0e1 100644 --- a/packages/platform/src/web/index.ts +++ b/packages/platform/src/web/index.ts @@ -52,6 +52,9 @@ function capabilitiesFor(): PlatformCapabilities { // The browser draws the frame around the page. There are no traffic lights // to leave room for and no window controls to draw. windowChrome: false, + // The browser already zooms the page, on the same keys, and remembers it + // per site. The app stays out of the way. + interfaceZoom: false, plugins: false, encryption: false, updater: false,