Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,19 @@ 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
a newer release.
- **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.
Expand Down
6 changes: 4 additions & 2 deletions docs/agents/desktop-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
13 changes: 9 additions & 4 deletions src-tauri/src/commands/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,19 @@ pub async fn check_for_updates(app: AppHandle) -> Result<UpdateInfo, String> {
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 {
Expand Down
56 changes: 4 additions & 52 deletions src/components/layout/StartupSplash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -15,7 +14,6 @@ type SplashPhase =
| "checking"
| "mandatory"
| "progress"
| "error"
| "ready";

const SPLASH_CHECK_TIMEOUT_MS = 8_000;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -191,49 +186,6 @@ export function StartupSplash({ updates, onComplete }: StartupSplashProps) {
</div>
)}

{phase === "error" && (
<div className="splash-update-info">
<div className="splash-update-title">
<AlertTriangle size={14} /> {t("startupSplash.updateCheckFailed")}
</div>
<p className="splash-update-summary text-muted text-sm">
{updates.info?.channelExplanation ?? updates.error ?? t("startupSplash.couldNotCheckForUpdates")}
</p>
{updates.info?.rawError ? (
<details className="update-raw-error">
<summary className="text-muted text-sm">{t("startupSplash.rawError")}</summary>
<pre className="text-sm mono">{updates.info.rawError}</pre>
</details>
) : null}
{updates.info?.rawError ? (
<div className="splash-error-copy">
<CopyButton text={updates.info.rawError} label={t("startupSplash.copyError")} />
</div>
) : null}
<div className="splash-actions">
<button
className="btn btn-sm"
type="button"
title={t("startupSplash.retryUpdateCheck")}
onClick={handleRetry}
>
<RefreshCw size={12} /> {t("startupSplash.retry")}
</button>
<button
className="btn btn-ghost btn-sm"
type="button"
title={t("startupSplash.continueToBasebuildWithoutUpdating")}
onClick={() => {
setPhase("ready");
onComplete();
}}
>
{t("startupSplash.continueAnyway")}
</button>
</div>
</div>
)}

{phase === "checking" && (
<button
className="btn btn-ghost btn-sm splash-skip-check"
Expand Down
8 changes: 0 additions & 8 deletions src/i18n/en/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,14 +708,6 @@ export const layoutEn = {
"startupSplash.minimumSupportedVersionNote": " Minimum supported version is {minimumSupportedVersion}.",
"startupSplash.updatingToBasebuild": "Updating to Basebuild {version}",
"startupSplash.updating": "Updating…",
"startupSplash.updateCheckFailed": "Update check failed",
"startupSplash.couldNotCheckForUpdates": "Could not check for updates.",
"startupSplash.rawError": "Raw error",
"startupSplash.copyError": "Copy error",
"startupSplash.retry": "Retry",
"startupSplash.retryUpdateCheck": "Retry update check",
"startupSplash.continueAnyway": "Continue anyway",
"startupSplash.continueToBasebuildWithoutUpdating": "Continue to Basebuild without updating",
"startupSplash.skip": "Skip",
"startupSplash.skipUpdateCheckAndContinue": "Skip update check and continue to Basebuild",
"statusBar.clickToViewAllWarnings": "Click to view all warnings and errors",
Expand Down
8 changes: 0 additions & 8 deletions src/i18n/zh-CN/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,14 +708,6 @@ export const layoutZhCN = {
"startupSplash.minimumSupportedVersionNote": " 最低支持版本为 {minimumSupportedVersion}。",
"startupSplash.updatingToBasebuild": "正在更新至 Basebuild {version}",
"startupSplash.updating": "正在更新…",
"startupSplash.updateCheckFailed": "更新检查失败",
"startupSplash.couldNotCheckForUpdates": "无法检查更新。",
"startupSplash.rawError": "原始错误",
"startupSplash.copyError": "复制错误",
"startupSplash.retry": "重试",
"startupSplash.retryUpdateCheck": "重试更新检查",
"startupSplash.continueAnyway": "仍然继续",
"startupSplash.continueToBasebuildWithoutUpdating": "不更新,继续使用 Basebuild",
"startupSplash.skip": "跳过",
"startupSplash.skipUpdateCheckAndContinue": "跳过更新检查,继续进入 Basebuild",
"statusBar.clickToViewAllWarnings": "点击查看所有警告和错误",
Expand Down
8 changes: 0 additions & 8 deletions src/i18n/zh-TW/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,14 +710,6 @@ export const layoutZhTW = {
"startupSplash.minimumSupportedVersionNote": " 最低支援版本為 {minimumSupportedVersion}。",
"startupSplash.updatingToBasebuild": "正在更新至 Basebuild {version}",
"startupSplash.updating": "正在更新…",
"startupSplash.updateCheckFailed": "更新檢查失敗",
"startupSplash.couldNotCheckForUpdates": "無法檢查更新。",
"startupSplash.rawError": "原始錯誤",
"startupSplash.copyError": "複製錯誤",
"startupSplash.retry": "重試",
"startupSplash.retryUpdateCheck": "重試更新檢查",
"startupSplash.continueAnyway": "仍然繼續",
"startupSplash.continueToBasebuildWithoutUpdating": "不更新,繼續使用 Basebuild",
"startupSplash.skip": "跳過",
"startupSplash.skipUpdateCheckAndContinue": "跳過更新檢查,繼續進入 Basebuild",
"statusBar.clickToViewAllWarnings": "點選檢視所有警告和錯誤",
Expand Down
1 change: 0 additions & 1 deletion src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3903,7 +3903,6 @@ mark.chat-search-highlight-active { background: var(--bb-warning); color: var(--
@keyframes splash-indeterminate { 0% { transform: translateX(-100%); } 100% { transform: translateX(350%); } }
.splash-progress-message { text-align: center; line-height: 1.4; }
.splash-skip-check { margin-top: 8px; }
.splash-error-copy { display: flex; justify-content: center; }
.splash-spinner { flex-shrink: 0; }
.workspace-splash { transition: opacity 0.2s ease-out; }
.workspace-splash-fading { opacity: 0; }
Expand Down
Loading