From 568f218fe936f619dc8dd902558ac61016243170 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Wed, 23 Sep 2026 20:52:16 -0400 Subject: [PATCH] feat(desktop): update failures say what happened and what to do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A friend's update failed ten times in a row ("Update check failed: error sending request for url (…nsis.zip)") before the 11th went through, and he didn't know what to do. The label was also wrong: the check had worked; the download failed. - Progress: "Downloading update… 28% (14.3 MB of 49.9 MB)", "The update server didn't respond. Trying again in 3 s (attempt 2 of 4)…", from the native `lotus-update-progress` events (cinny-desktop retries itself). - Failures name the step (check / download / install, from the native error prefix) in plain language, with Try again and a Download installer button (Windows: the setup .exe; else the release page), and the raw error under "Details". - Installing from the update toast now shows a "Downloading update" toast, and on failure a sticky "Update didn't install" toast that retries on click and points at Settings → General → App Updates. Before, the toast vanished and the failure was only visible in Settings. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/features/settings/general/General.tsx | 53 +++++++++++++++-- src/app/hooks/useTauriUpdater.ts | 52 ++++++++++++++--- src/app/pages/client/ClientNonUIFeatures.tsx | 51 +++++++++++++++-- src/app/utils/updateErrors.test.ts | 57 +++++++++++++++++++ src/app/utils/updateErrors.ts | 49 ++++++++++++++++ 5 files changed, 245 insertions(+), 17 deletions(-) create mode 100644 src/app/utils/updateErrors.test.ts create mode 100644 src/app/utils/updateErrors.ts diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 86735c6ee..9cafa1a8c 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -110,7 +110,8 @@ import { } from '../../../utils/translation/langUtils'; import { chromeTranslationEngine } from '../../../utils/translation/chromeEngine'; import { SequenceCardStyle } from '../styles.css'; -import { useTauriUpdater } from '../../../hooks/useTauriUpdater'; +import { UpdateProgress, useTauriUpdater } from '../../../hooks/useTauriUpdater'; +import { describeUpdateError, manualDownloadUrl } from '../../../utils/updateErrors'; import { isTauri as isTauriEnv, invokeTauri, tauriInvoke } from '../../../hooks/useTauri'; import { isSafeGlobalToggleKey } from '../../../hooks/useCallHotkeys'; import { customWindowChromeAtom } from '../../../state/customWindowChrome'; @@ -2682,6 +2683,22 @@ function Messages() { ); } +const formatMb = (bytes: number): string => `${(bytes / 1_048_576).toFixed(1)} MB`; + +function updateProgressText(progress: UpdateProgress | undefined): string { + if (!progress) return 'Checking for the update…'; + if (progress.phase === 'installing') return 'Installing — Lotus Chat will restart in a moment…'; + if (progress.phase === 'retrying') { + return `The update server didn’t respond. Trying again in ${progress.waitSecs} s (attempt ${progress.attempt + 1} of ${progress.maxAttempts})…`; + } + const attempt = + progress.attempt > 1 ? ` (attempt ${progress.attempt} of ${progress.maxAttempts})` : ''; + const { downloaded, total } = progress; + if (!total) return `Downloading update… ${formatMb(downloaded)}${attempt}`; + const pct = Math.min(100, Math.floor((downloaded / total) * 100)); + return `Downloading update… ${pct}% (${formatMb(downloaded)} of ${formatMb(total)})${attempt}`; +} + function AppUpdates() { const { isTauri, status, check, install } = useTauriUpdater(); if (!isTauri) return null; @@ -2694,18 +2711,41 @@ function AppUpdates() { : status.state === 'available' ? `Update available: v${status.version}` : status.state === 'installing' - ? 'Installing update, the app will restart shortly...' + ? updateProgressText(status.progress) : status.state === 'error' - ? `Update check failed: ${status.message}` + ? describeUpdateError(status.phase, status.message) : 'Check for a new version of Lotus Chat.'; + const retry = () => { + if (status.state === 'error' && status.phase !== 'check') install(); + else check(); + }; + const after = status.state === 'available' ? ( - ) : status.state === 'checking' || status.state === 'installing' ? ( + ) : status.state === 'error' ? ( + + + {status.phase !== 'check' && ( + + )} + ) : (