From 5317566154f39ecf59ac10a58101c7176c0e177f Mon Sep 17 00:00:00 2001 From: broisnischal Date: Fri, 25 Sep 2026 10:16:30 +0545 Subject: [PATCH] Grant core:window:allow-destroy so the window can close Closing Stroke did nothing after 2.1.0. Clicking the title bar X left the window on screen and the process alive, and since that release ships no tray and no native decorations, there was no other way to quit the app. The capability file granted `core:window:allow-close` but never `core:window:allow-destroy`. That gap was invisible until now: the tray build intercepted CloseRequested and hid the window instead, so `close()` never ran through to `destroy`. Removing the tray in 3ea0c80 let it through for the first time, the ACL refused the call, and the rejection went nowhere: window.destroy not allowed. Permissions associated with this command: core:window:allow-destroy The capability also only covered the window labelled `main`, so every control on a second window (Ctrl/Cmd+Shift+N, labelled `main-2` onward) was denied the same way: close, minimize, maximize and dragging the bar. Widened to `main-*`. Window controls now await their core call and log a failure instead of discarding the promise, so the next gap in the capability file shows up somewhere rather than nowhere. --- .changeset/window-close.md | 3 +++ src-tauri/capabilities/default.json | 4 +++- src/lib/components/WindowControls.svelte | 29 +++++++++++++++--------- 3 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 .changeset/window-close.md diff --git a/.changeset/window-close.md b/.changeset/window-close.md new file mode 100644 index 00000000..3eaa5dd0 --- /dev/null +++ b/.changeset/window-close.md @@ -0,0 +1,3 @@ +### Bug Fixes +- Closing the window quits Stroke again. 2.1.0 dropped the tray and the hide-on-close handler that went with it, which let close run through to `destroy` for the first time. `destroy` was never in the capability file, so the ACL refused it and the window stayed put. With no tray and no window decorations, that left no way to quit the app at all. +- Second windows (`Ctrl/Cmd+Shift+N`) respond to their title bar again. The capability only covered the window labelled `main`, so every control on a second window (close, minimize, maximize, and dragging the bar) was denied. diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index ff505f89..29baa69f 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -3,7 +3,8 @@ "identifier": "default", "description": "enables the default permissions", "windows": [ - "main" + "main", + "main-*" ], "permissions": [ "core:default", @@ -24,6 +25,7 @@ "core:window:allow-set-fullscreen", "core:window:allow-is-fullscreen", "core:window:allow-close", + "core:window:allow-destroy", "core:window:allow-minimize", "core:window:allow-maximize", "core:window:allow-unmaximize", diff --git a/src/lib/components/WindowControls.svelte b/src/lib/components/WindowControls.svelte index 4e741f00..31240c19 100644 --- a/src/lib/components/WindowControls.svelte +++ b/src/lib/components/WindowControls.svelte @@ -27,19 +27,26 @@ return () => unlisten() }) - async function minimizeWindow() { - const { getCurrentWindow } = await import('@tauri-apps/api/window') - getCurrentWindow().minimize() - } - async function toggleMaximizeWindow() { - const { getCurrentWindow } = await import('@tauri-apps/api/window') - getCurrentWindow().toggleMaximize() - } - async function closeWindow() { - const { getCurrentWindow } = await import('@tauri-apps/api/window') - getCurrentWindow().close() + // Every one of these is an ACL-gated core command, and the window is frameless, + // so these buttons are the only chrome there is - a denied call leaves the user + // with a window they cannot move, resize or close. That is how the missing + // `core:window:allow-destroy` shipped in 2.1.0: `close()` runs through to + // `destroy`, the ACL refused it, and the rejection went nowhere. Await and log + // so the next gap in src-tauri/capabilities/default.json is visible. + /** @param {string} name @param {(w: import('@tauri-apps/api/window').Window) => Promise} fn */ + async function windowAction(name, fn) { + try { + const { getCurrentWindow } = await import('@tauri-apps/api/window') + await fn(getCurrentWindow()) + } catch (err) { + console.error(`window ${name} failed:`, err) + } } + const minimizeWindow = () => windowAction('minimize', (w) => w.minimize()) + const toggleMaximizeWindow = () => windowAction('toggleMaximize', (w) => w.toggleMaximize()) + const closeWindow = () => windowAction('close', (w) => w.close()) + const winBtn = 'inline-flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors duration-150 hover:bg-foreground/[0.08] hover:text-foreground'