Skip to content

Commit 09e7582

Browse files
committed
fix(tauri): fall back when WinGet updates fail
Wait for WinGet while CodeNomad remains active so launch and non-zero exit failures propagate back to the UI without blocking the async runtime. Use one notification action path for live toasts and history entries, opening the release URL whenever the native update action rejects. Successful installs remain owned by WinGet and do not attempt an in-process restart. Validated with the UI typecheck and build plus the Rust test suite.
1 parent ba663e3 commit 09e7582

3 files changed

Lines changed: 57 additions & 50 deletions

File tree

packages/tauri-app/src-tauri/src/windows_update.rs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,35 @@ use std::process::Command;
44
use std::os::windows::process::CommandExt;
55

66
#[tauri::command]
7-
pub fn install_stable_update() -> Result<(), String> {
8-
let mut command = Command::new("winget");
9-
command.args([
10-
"upgrade",
11-
"--exact",
12-
"--id",
13-
"NeuralNomadsAI.CodeNomad",
14-
"--source",
15-
"winget",
16-
"--silent",
17-
"--accept-source-agreements",
18-
"--accept-package-agreements",
19-
"--disable-interactivity",
20-
]);
7+
pub async fn install_stable_update() -> Result<(), String> {
8+
tauri::async_runtime::spawn_blocking(|| {
9+
let mut command = Command::new("winget");
10+
command.args([
11+
"upgrade",
12+
"--exact",
13+
"--id",
14+
"NeuralNomadsAI.CodeNomad",
15+
"--source",
16+
"winget",
17+
"--silent",
18+
"--accept-source-agreements",
19+
"--accept-package-agreements",
20+
"--disable-interactivity",
21+
]);
2122

22-
#[cfg(windows)]
23-
command.creation_flags(0x08000000);
23+
#[cfg(windows)]
24+
command.creation_flags(0x08000000);
2425

25-
command
26-
.spawn()
27-
.map(|_| ())
28-
.map_err(|err| format!("Unable to start the WinGet update: {err}"))
26+
let status = command
27+
.status()
28+
.map_err(|err| format!("Unable to start the WinGet update: {err}"))?;
29+
30+
if status.success() {
31+
Ok(())
32+
} else {
33+
Err(format!("WinGet update failed with status {status}"))
34+
}
35+
})
36+
.await
37+
.map_err(|err| format!("Unable to monitor the WinGet update: {err}"))?
2938
}

packages/ui/src/components/toast-history-panel.tsx

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import {
2121
deleteToastHistoryItem,
2222
markAllToastHistoryAsRead,
2323
markToastHistoryAsRead,
24+
runToastAction,
2425
subscribeToastHistory,
2526
} from "../lib/notifications"
26-
import { isTauriHost } from "../lib/runtime-env"
2727

2828
// ==================== Types ====================
2929

@@ -188,8 +188,8 @@ const ToastHistoryPanel: Component<ToastHistoryPanelProps> = (props) => {
188188
}
189189

190190
// Open action link if exists
191-
if (item.action?.href) {
192-
void handleOpenAction(item.action.href);
191+
if (item.action) {
192+
void runToastAction(item.action);
193193
}
194194
};
195195

@@ -209,21 +209,6 @@ const ToastHistoryPanel: Component<ToastHistoryPanelProps> = (props) => {
209209
markAllToastHistoryAsRead();
210210
};
211211

212-
// Open external link
213-
async function handleOpenAction(href: string): Promise<void> {
214-
if (isTauriHost()) {
215-
try {
216-
const { openUrl } = await import("@tauri-apps/plugin-opener");
217-
await openUrl(href);
218-
return;
219-
} catch (error) {
220-
console.warn("[toast-history] unable to open via system opener", error);
221-
}
222-
}
223-
224-
window.open(href, "_blank", "noopener,noreferrer");
225-
}
226-
227212
// Stop click propagation from backdrop to panel
228213
const handleBackdropClick = (event: MouseEvent) => {
229214
if (event.target === event.currentTarget) {
@@ -383,8 +368,8 @@ const ToastHistoryPanel: Component<ToastHistoryPanelProps> = (props) => {
383368
class="toast-history-item-action inline-flex items-center gap-1 text-xs mt-1"
384369
onClick={(e) => {
385370
e.stopPropagation();
386-
if (item.action?.href) {
387-
void handleOpenAction(item.action.href);
371+
if (item.action) {
372+
void runToastAction(item.action);
388373
}
389374
}}
390375
>

packages/ui/src/lib/notifications.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ export type ToastHandle = {
1111

1212
type ToastPosition = "top-left" | "top-right" | "top-center" | "bottom-left" | "bottom-right" | "bottom-center"
1313

14+
export type ToastAction = {
15+
label: string
16+
href: string
17+
onClick?: () => void | Promise<void>
18+
}
19+
1420
export type ToastPayload = {
1521
title?: string
1622
message: string
1723
variant: ToastVariant
1824
duration?: number
1925
position?: ToastPosition
20-
action?: {
21-
label: string
22-
href: string
23-
onClick?: () => void | Promise<void>
24-
}
26+
action?: ToastAction
2527
}
2628

2729
// ==================== Toast History Types ====================
@@ -43,10 +45,7 @@ export interface IToastHistoryItem {
4345
/** Read state (clicked) */
4446
read: boolean;
4547
/** Action link (optional) */
46-
action?: {
47-
label: string;
48-
href: string;
49-
};
48+
action?: ToastAction;
5049
}
5150

5251
/**
@@ -298,6 +297,20 @@ async function openExternalUrl(url: string): Promise<void> {
298297
}
299298
}
300299

300+
export async function runToastAction(action: ToastAction): Promise<void> {
301+
if (!action.onClick) {
302+
await openExternalUrl(action.href)
303+
return
304+
}
305+
306+
try {
307+
await action.onClick()
308+
} catch (error) {
309+
console.warn("[notifications] action failed; opening fallback link", error)
310+
await openExternalUrl(action.href)
311+
}
312+
}
313+
301314
// ==================== Variant Accent Styles ====================
302315

303316
const variantAccent: Record<
@@ -382,7 +395,7 @@ export function showToastNotification(payload: ToastPayload): ToastHandle {
382395
<button
383396
type="button"
384397
class="mt-3 inline-flex items-center text-xs font-semibold uppercase tracking-wide text-sky-300 hover:text-sky-200"
385-
onClick={() => void (payload.action!.onClick?.() ?? openExternalUrl(payload.action!.href))}
398+
onClick={() => void runToastAction(payload.action!)}
386399
>
387400
{payload.action.label}
388401
</button>

0 commit comments

Comments
 (0)