From deef73672eb7b80ca7923c8885502302102f9e41 Mon Sep 17 00:00:00 2001 From: kngsly <33945956+kngsly@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:45:02 +1100 Subject: [PATCH] fix(updater): a failed update check never blocks startup The startup splash stopped on a full-screen 'Update check failed' card with Retry / Continue anyway, so an offline machine or any updater hiccup greeted users with an error before the app. The splash now opens the app on error and the taskbar update control (which already surfaces the failure and a retry) takes over. Updater failures are also logged to stderr with an [updater] prefix so packaged builds are diagnosable. --- docs/DEVELOPMENT.md | 8 +++- docs/agents/desktop-shell.md | 6 ++- src-tauri/src/commands/updater.rs | 13 ++++-- src/components/layout/StartupSplash.tsx | 56 ++----------------------- src/i18n/en/layout.ts | 8 ---- src/i18n/zh-CN/layout.ts | 8 ---- src/i18n/zh-TW/layout.ts | 8 ---- src/styles/globals.css | 1 - 8 files changed, 23 insertions(+), 85 deletions(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ed32ac2c..463b0fb9 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -179,7 +179,9 @@ The `latest.json` manifest supports optional policy fields: On launch, Basebuild shows a startup splash that checks for updates before the main shell becomes interactive. The splash has these states: -- **Checking** — Shows current version and update-check progress. +- **Checking** — Shows current version and update-check progress. Runs on + Windows, macOS, and Linux. After 8 seconds the app opens anyway and the + check finishes in the background. - **Optional update** — Shows target version, summary, and `Upgrade` / `Skip update for now` buttons. The skip is version-scoped: the user is not prompted again for the same target version, but will be prompted for @@ -187,7 +189,9 @@ the main shell becomes interactive. The splash has these states: - **Mandatory update** — Hides the skip button and auto-starts the update when the running version is below `minimumSupportedVersion`. - **Progress** — Shows download/install progress bar and step text. -- **Error** — Shows actionable diagnostics with retry and "Continue anyway". +- **Check failed** — never blocks startup: the app opens and the taskbar + update control shows the failure with a retry. The backend also logs it to + stderr with an `[updater]` prefix. The existing in-app update UI (taskbar button + Settings → Updates tab) remains functional after startup for manual checks and installs. diff --git a/docs/agents/desktop-shell.md b/docs/agents/desktop-shell.md index cc649355..0c73689b 100644 --- a/docs/agents/desktop-shell.md +++ b/docs/agents/desktop-shell.md @@ -297,7 +297,8 @@ Before the main shell renders, Basebuild shows a startup update splash that checks for updates. The splash displays the current build version and transitions through these states: -- **Checking** — update check in progress. +- **Checking** — update check in progress (every platform). After 8 seconds + the shell opens and the check continues in the background. - **Optional update** — target version, summary, and `Upgrade` / `Skip update for now` buttons. Skip is version-scoped: the same target version is not prompted again, but a newer release will prompt. @@ -306,7 +307,8 @@ and transitions through these states: update auto-starts. - **Progress** — download progress bar and step text (downloading, installing, restarting). -- **Error** — actionable diagnostics with retry and "Continue anyway". +- **Check failed** — the shell opens immediately; the taskbar update control + surfaces the failure and its retry. The splash does not replace the in-app update controls. The taskbar update button and Settings → Updates tab remain functional after startup. diff --git a/src-tauri/src/commands/updater.rs b/src-tauri/src/commands/updater.rs index 106a4d95..8a8ec671 100644 --- a/src-tauri/src/commands/updater.rs +++ b/src-tauri/src/commands/updater.rs @@ -288,14 +288,19 @@ pub async fn check_for_updates(app: AppHandle) -> Result { skipped: false, }); } - let updater = app - .updater() - .map_err(|e| format!("Failed to get updater: {e}"))?; + let updater = app.updater().map_err(|e| { + let message = format!("Failed to get updater: {e}"); + eprintln!("[updater] {message}"); + message + })?; let update = updater.check().await.map_err(|e| { let status = UpdateChannelStatus::from_plugin_error(&e); let explanation = status.explanation(); - format!("Failed to check for updates: {e} | channel_status={status:?} | {explanation}") + let message = + format!("Failed to check for updates: {e} | channel_status={status:?} | {explanation}"); + eprintln!("[updater] {message}"); + message })?; match update { diff --git a/src/components/layout/StartupSplash.tsx b/src/components/layout/StartupSplash.tsx index afb3bc2f..acaa995a 100644 --- a/src/components/layout/StartupSplash.tsx +++ b/src/components/layout/StartupSplash.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from "react"; import { AlertTriangle, Download, RefreshCw, X } from "lucide-react"; import type { UpdaterState } from "../../state/updater"; import { appVersion } from "../../lib/app"; -import { CopyButton } from "./CopyButton"; import { t } from "../../i18n"; type StartupSplashProps = { @@ -15,7 +14,6 @@ type SplashPhase = | "checking" | "mandatory" | "progress" - | "error" | "ready"; const SPLASH_CHECK_TIMEOUT_MS = 8_000; @@ -65,9 +63,11 @@ export function StartupSplash({ updates, onComplete }: StartupSplashProps) { return; } - // Error state. + // A failed check never blocks startup: enter the app; the taskbar update + // control shows the failure and offers a retry. if (status === "error") { - setPhase("error"); + setPhase("ready"); + onComplete(); return; } @@ -104,11 +104,6 @@ export function StartupSplash({ updates, onComplete }: StartupSplashProps) { onComplete(); }; - const handleRetry = () => { - void updates.checkNow(); - setAutoStarted(false); - }; - // Progress percentage from backend events. const progressPct = (() => { const p = updates.progress; @@ -191,49 +186,6 @@ export function StartupSplash({ updates, onComplete }: StartupSplashProps) { )} - {phase === "error" && ( -
-
- {t("startupSplash.updateCheckFailed")} -
-

- {updates.info?.channelExplanation ?? updates.error ?? t("startupSplash.couldNotCheckForUpdates")} -

- {updates.info?.rawError ? ( -
- {t("startupSplash.rawError")} -
{updates.info.rawError}
-
- ) : null} - {updates.info?.rawError ? ( -
- -
- ) : null} -
- - -
-
- )} - {phase === "checking" && (