Skip to content

Commit f6c292e

Browse files
committed
fix(updater): make update downloads non-blocking and announce new version
Return from app_update_download immediately while download continues in the background, so the Settings UI can show live progress without a long pending RPC. Also add a one-time startup toast when the app version changes to confirm a successful update. Made-with: Cursor
1 parent 11785fd commit f6c292e

3 files changed

Lines changed: 58 additions & 15 deletions

File tree

src/main/app-updater.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ let remoteVersion: string | null = null;
2929
let lastError: string | null = null;
3030
let state: AppUpdateStatusJson["state"] = "idle";
3131
let updateDownloaded = false;
32+
let downloadPromise: Promise<string[]> | null = null;
3233

3334
function snapshot(): AppUpdateStatusJson {
3435
return {
@@ -170,20 +171,32 @@ export async function downloadUpdate(): Promise<AppUpdateStatusJson> {
170171
// before the first `download-progress` event fires (those can lag 2–3s on
171172
// slow networks).
172173
setState("downloading");
173-
try {
174-
const result = await autoUpdater.downloadUpdate();
175-
console.log("[updater] downloadUpdate() resolved with:", result);
176-
// If update-downloaded event didn't fire yet (race on some platforms),
177-
// force the state transition based on the promise resolution.
178-
if (state !== "ready") {
179-
updateDownloaded = true;
180-
downloadProgress = 100;
181-
setState("ready");
182-
}
183-
} catch (err) {
184-
console.warn("[updater] downloadUpdate() rejected:", err);
185-
lastError = (err as Error)?.message ?? String(err);
186-
setState("error");
174+
// Important UX behavior: return immediately so renderer request doesn't sit
175+
// in pending for minutes on large updates. Progress/finish/error keeps coming
176+
// through updater events pushed via `app_update_status_changed`.
177+
if (!downloadPromise) {
178+
downloadPromise = autoUpdater.downloadUpdate();
179+
void downloadPromise
180+
.then((result) => {
181+
console.log("[updater] downloadUpdate() resolved with:", result);
182+
// If update-downloaded event didn't fire yet (race on some
183+
// platforms), force the state transition from the resolved promise.
184+
if (state !== "ready") {
185+
updateDownloaded = true;
186+
downloadProgress = 100;
187+
setState("ready");
188+
}
189+
})
190+
.catch((err) => {
191+
console.warn("[updater] downloadUpdate() rejected:", err);
192+
lastError = (err as Error)?.message ?? String(err);
193+
setState("error");
194+
})
195+
.finally(() => {
196+
downloadPromise = null;
197+
});
198+
} else {
199+
console.log("[updater] download already in progress");
187200
}
188201
return snapshot();
189202
}

src/mainview/App.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@ import SettingsPage from './pages/Settings'
1717
import OnboardingWizard from './components/OnboardingWizard'
1818
import { useTheme } from './hooks/useTheme'
1919
import CloseConfirmDialog from './components/CloseConfirmDialog'
20+
import { useToast } from './components/ToastProvider'
2021

2122
const GITHUB_REPO_URL =
2223
'https://github.com/beautyfree/skiller-skills-desktop-manager'
2324
const STAR_PROMPT_MIN_LAUNCHES = 3
2425
const STAR_PROMPT_MIN_AGE_MS = 24 * 60 * 60 * 1000
2526
const STAR_PROMPT_RESHOW_LAUNCHES = 3
2627
const STAR_PROMPT_RESHOW_MS = 3 * 24 * 60 * 60 * 1000
28+
const LAST_SEEN_VERSION_STORAGE_KEY = 'skiller.last_seen_app_version'
2729

2830
function AppInner() {
2931
const queryClient = useQueryClient()
3032
const location = useLocation()
31-
const { i18n } = useTranslation()
33+
const { i18n, t } = useTranslation()
34+
const { toast } = useToast()
3235
useTheme()
3336
const appOpenedTrackedRef = useRef(false)
3437
const [telemetryReady, setTelemetryReady] = useState(false)
@@ -45,6 +48,32 @@ function AppInner() {
4548
}
4649
})
4750

51+
useEffect(() => {
52+
let cancelled = false
53+
void invoke('get_app_version')
54+
.then((currentVersion) => {
55+
if (cancelled) return
56+
try {
57+
const previousVersion = localStorage.getItem(LAST_SEEN_VERSION_STORAGE_KEY)
58+
if (previousVersion && previousVersion !== currentVersion) {
59+
toast(
60+
t('settings.updateWelcomeToast', {
61+
from: previousVersion,
62+
to: currentVersion,
63+
}),
64+
)
65+
}
66+
localStorage.setItem(LAST_SEEN_VERSION_STORAGE_KEY, currentVersion)
67+
} catch {
68+
// Ignore storage errors in private/locked environments.
69+
}
70+
})
71+
.catch(() => {})
72+
return () => {
73+
cancelled = true
74+
}
75+
}, [t, toast])
76+
4877
useEffect(() => {
4978
if (!telemetryReady || appOpenedTrackedRef.current) return
5079
appOpenedTrackedRef.current = true

src/mainview/i18n/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ const en = {
321321
updateVersionLine: "Current {{current}} — latest {{latest}}",
322322
updateVersionOnly: "Version {{version}}",
323323
updateLastChecked: "Last checked {{time}}",
324+
updateWelcomeToast: "Updated successfully: {{from}} → {{to}}",
324325
// Marketplace Cache
325326
onboardingTitle: "Onboarding",
326327
onboardingDescription:

0 commit comments

Comments
 (0)