From fbf240efbc706db2ee6735ac60b15d17803ae808 Mon Sep 17 00:00:00 2001 From: Melody Ruch <396921921@qq.com> Date: Thu, 13 Aug 2026 19:27:55 +0800 Subject: [PATCH] fix(desktop): keep update download active on dismiss --- .../components/layout/UpdateDialog.spec.ts | 23 +++++++++++++++++++ .../src/components/layout/UpdateDialog.vue | 7 ++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/layout/UpdateDialog.spec.ts b/apps/desktop/src/components/layout/UpdateDialog.spec.ts index d198329a72..f5bbbddaa1 100644 --- a/apps/desktop/src/components/layout/UpdateDialog.spec.ts +++ b/apps/desktop/src/components/layout/UpdateDialog.spec.ts @@ -119,6 +119,11 @@ async function pressEscape() { await flushDialog(); } +async function clickOutside() { + document.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true })); + await flushDialog(); +} + afterEach(() => { for (const app of mountedApps.splice(0)) app.unmount(); document.body.innerHTML = ""; @@ -202,6 +207,24 @@ describe("UpdateDialog close protection", () => { expect(state.open).toBe(false); }); + it("keeps downloading in the background when the dialog is dismissed by clicking outside", async () => { + const { state, cancelDownload } = await mountDialog(0, { isDownloadingUpdate: true, downloadProgress: 42 }); + + await clickOutside(); + + expect(cancelDownload).not.toHaveBeenCalled(); + expect(state.open).toBe(true); + }); + + it("keeps downloading in the background when the dialog is dismissed with Escape", async () => { + const { state, cancelDownload } = await mountDialog(0, { isDownloadingUpdate: true, downloadProgress: 42 }); + + await pressEscape(); + + expect(cancelDownload).not.toHaveBeenCalled(); + expect(state.open).toBe(true); + }); + it("allows closing while a downloaded update is idle", async () => { const { state } = await mountDialog(0, { updateDownloaded: true, downloadProgress: 100 }); diff --git a/apps/desktop/src/components/layout/UpdateDialog.vue b/apps/desktop/src/components/layout/UpdateDialog.vue index 6cf189f047..428c7386c6 100644 --- a/apps/desktop/src/components/layout/UpdateDialog.vue +++ b/apps/desktop/src/components/layout/UpdateDialog.vue @@ -37,6 +37,9 @@ const isDesktop = isTauriRuntime(); const renderedNotes = ref(""); // Only active file replacement (installation) must trap the dialog. const isCloseBlocked = computed(() => props.isInstallingUpdate); +// Accidental dismiss gestures (outside click, Escape) must not cancel a running download; +// only the explicit close/cancel buttons should. +const blocksImplicitDismiss = computed(() => props.isInstallingUpdate || props.isDownloadingUpdate); const canIgnoreVersion = computed(() => props.updateInfo?.update_available === true && !props.updateDownloaded && !props.isDownloadingUpdate && !props.isInstallingUpdate && !props.updateReady); function handleCancel() { @@ -91,12 +94,12 @@ watch( :show-close-button="!isCloseBlocked" @interact-outside=" (e: Event) => { - if (isCloseBlocked) e.preventDefault(); + if (blocksImplicitDismiss) e.preventDefault(); } " @escape-key-down=" (e: Event) => { - if (isCloseBlocked) e.preventDefault(); + if (blocksImplicitDismiss) e.preventDefault(); } " >