From 78a4c888d20940f38340a0ca6c1ee3e988a43775 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Thu, 10 Sep 2026 00:37:26 +0000 Subject: [PATCH 1/2] fix(desktop): hold-to-quit no longer strands the quit Once a hold completed, a Q auto-repeat that arrived after the modifier was released cleared the pending quit timer and left the handler waiting for a Q keyUp that macOS does not always deliver. The window stayed concealed, the app never quit, and the next lone Cmd release revived the stale request and quit instantly. Q repeats now push the quiet period back instead of disarming it, so the quit lands when the repeats stop. Co-Authored-By: Claude Opus 5 (1M context) --- apps/desktop/src/window/QuitHold.test.ts | 27 +++++++++++++++++++++--- apps/desktop/src/window/QuitHold.ts | 13 ++++++------ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/window/QuitHold.test.ts b/apps/desktop/src/window/QuitHold.test.ts index 58809d8eb14d..47f3ec5298ec 100644 --- a/apps/desktop/src/window/QuitHold.test.ts +++ b/apps/desktop/src/window/QuitHold.test.ts @@ -178,14 +178,35 @@ describe("makeQuitShortcutHandler", () => { await harness.holdFor(QUIT_HOLD_DURATION_MS + 200); await harness.send(makeInput({ type: "keyUp", key: "Meta", meta: false })); harness.preventDefault.mockClear(); - await harness.send(makeInput({ meta: false, isAutoRepeat: true })); - expect(harness.preventDefault).toHaveBeenCalledTimes(1); - vi.advanceTimersByTime(QUIT_HOLD_RELEASE_GRACE_MS * 2); + // Repeats without the modifier prove Q is still down, so they hold the + // quit back for as long as they keep arriving. + await harness.holdFor(QUIT_HOLD_RELEASE_GRACE_MS * 2, { meta: false }); + expect(harness.preventDefault).toHaveBeenCalled(); expect(harness.quit).not.toHaveBeenCalled(); await harness.send(makeInput({ type: "keyUp", meta: false })); expect(harness.quit).toHaveBeenCalledTimes(1); }); + it("commits a concealed hold when the last Q repeat is never released", async () => { + // macOS can drop the final Q keyUp. The quit must land on its own once + // repeats stop, rather than sitting armed until an unrelated key arrives. + const harness = makeHarness(); + await harness.send(makeInput({})); + await harness.holdFor(QUIT_HOLD_DURATION_MS + 200); + await harness.send(makeInput({ type: "keyUp", key: "Meta", meta: false })); + await harness.send(makeInput({ meta: false, isAutoRepeat: true })); + + vi.advanceTimersByTime(QUIT_HOLD_RELEASE_GRACE_MS); + expect(harness.quit).toHaveBeenCalledTimes(1); + + // A lone Cmd tap afterwards must not quit a second time. + harness.quit.mockClear(); + await harness.send(makeInput({ key: "Meta" })); + await harness.send(makeInput({ type: "keyUp", key: "Meta", meta: false })); + vi.advanceTimersByTime(QUIT_HOLD_RELEASE_GRACE_MS * 4); + expect(harness.quit).not.toHaveBeenCalled(); + }); + it("does not quit when the hold stops before the duration", async () => { const harness = makeHarness(); await harness.send(makeInput({})); diff --git a/apps/desktop/src/window/QuitHold.ts b/apps/desktop/src/window/QuitHold.ts index a995184ddd70..2b059932ab5f 100644 --- a/apps/desktop/src/window/QuitHold.ts +++ b/apps/desktop/src/window/QuitHold.ts @@ -125,13 +125,12 @@ export function makeQuitShortcutHandler( } if (quitOnRelease) { event.preventDefault(); - if (key === "q") { - if (modifierDown) { - quitAfterQuietPeriod(); - } else { - clearWatchdog(); - } - } + // A Q keydown proves the key is still down whether or not the modifier + // is still held, so it only pushes the quiet period back. Disarming the + // watchdog here instead stranded the quit whenever macOS dropped the + // final Q keyUp, leaving the window concealed until an unrelated + // modifier tap released the stale request and quit out of nowhere. + if (key === "q") quitAfterQuietPeriod(); return; } From 176d8046e8b5faed33f529223224dd4b0e49c47c Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Thu, 10 Sep 2026 00:43:56 +0000 Subject: [PATCH 2/2] refactor(desktop): trim quit-hold comment Co-Authored-By: Claude Opus 5 (1M context) --- apps/desktop/src/window/QuitHold.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/desktop/src/window/QuitHold.ts b/apps/desktop/src/window/QuitHold.ts index 2b059932ab5f..f656183f1375 100644 --- a/apps/desktop/src/window/QuitHold.ts +++ b/apps/desktop/src/window/QuitHold.ts @@ -126,10 +126,7 @@ export function makeQuitShortcutHandler( if (quitOnRelease) { event.preventDefault(); // A Q keydown proves the key is still down whether or not the modifier - // is still held, so it only pushes the quiet period back. Disarming the - // watchdog here instead stranded the quit whenever macOS dropped the - // final Q keyUp, leaving the window concealed until an unrelated - // modifier tap released the stale request and quit out of nowhere. + // is still held, so it only pushes the quiet period back. if (key === "q") quitAfterQuietPeriod(); return; }