Skip to content

Commit ca36927

Browse files
beautyfreeclaude
andcommitted
fix(updater): surface download failures and dev-mode no-op in UI
Two symptoms collapsed into the same report: "click Download, button disables briefly, re-enables, nothing happens." 1. In dev mode `app.isPackaged` is false and `downloadUpdate` silently returned a snapshot. The renderer saw no state change and gave up. Now we emit a state='error' with a human message explaining updates require a packaged build. 2. In prod, if electron-updater's `downloadUpdate()` rejects (network, signature check, missing platform yml), the catch set state='error' but never surfaced the error to the foreground toast. Now the renderer inspects the returned snapshot and toasts whatever error string came back. 3. Optimistically transition state → 'downloading' the moment downloadUpdate is called, and → 'ready' on promise resolution — some platforms race the `update-downloaded` event past our await. Added main-side console.log around downloadUpdate so future reports show what actually happened. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4032f11 commit ca36927

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

src/main/app-updater.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,31 @@ export async function checkForUpdate(): Promise<AppUpdateStatusJson> {
157157
}
158158

159159
export async function downloadUpdate(): Promise<AppUpdateStatusJson> {
160-
if (!app.isPackaged) return snapshot();
160+
// In dev the autoUpdater is a no-op — surface that as a clear error state
161+
// so the UI shows something instead of the button silently re-enabling.
162+
if (!app.isPackaged) {
163+
lastError =
164+
"Updates only work in a packaged build (bun run dist:mac / :win / :linux).";
165+
setState("error");
166+
return snapshot();
167+
}
168+
console.log("[updater] downloadUpdate() invoked; state=", state);
169+
// Optimistic transition so the renderer UI reflects work-in-progress even
170+
// before the first `download-progress` event fires (those can lag 2–3s on
171+
// slow networks).
172+
setState("downloading");
161173
try {
162-
await autoUpdater.downloadUpdate();
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+
}
163183
} catch (err) {
184+
console.warn("[updater] downloadUpdate() rejected:", err);
164185
lastError = (err as Error)?.message ?? String(err);
165186
setState("error");
166187
}

src/mainview/pages/Settings.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,13 @@ export default function SettingsPage() {
129129
async function handleUpdateDownload() {
130130
setUpdateBusy('downloading')
131131
try {
132-
await invoke('app_update_download')
132+
// Returns a status snapshot; if the final state is 'error', the
133+
// autoUpdater rejected (network, signature, etc.) and we should surface
134+
// that to the user instead of just re-enabling the button.
135+
const snapshot = await invoke('app_update_download')
136+
if (snapshot?.state === 'error' && snapshot.error) {
137+
toast(snapshot.error, 'destructive')
138+
}
133139
} catch (e) {
134140
toast(
135141
t('settings.updateStateError') +

0 commit comments

Comments
 (0)