-
-
-
-
-
- Open folder picker to add a repo
-
-
-
-
-
-
-
- Settings
-
-
+
+ {showUpdateBanner && (
+
+ )}
+
+
+
+
+
+
+ Open folder picker to add a repo
+
+
+
+
+
+
+
+ Settings
+
+
+
)
})
diff --git a/src/renderer/src/hooks/useIpcEvents.ts b/src/renderer/src/hooks/useIpcEvents.ts
index 13232f948b2..09a364b47d1 100644
--- a/src/renderer/src/hooks/useIpcEvents.ts
+++ b/src/renderer/src/hooks/useIpcEvents.ts
@@ -1,6 +1,8 @@
import { useEffect } from 'react'
+import { toast } from 'sonner'
import { useAppStore } from '../store'
import { applyUIZoom } from '@/lib/ui-zoom'
+import type { UpdateStatus } from '../../../shared/types'
const ZOOM_STEP = 0.5
@@ -26,6 +28,46 @@ export function useIpcEvents(): void {
})
)
+ // Hydrate initial update status then subscribe to changes
+ window.api.updater.getStatus().then((status) => {
+ useAppStore.getState().setUpdateStatus(status as UpdateStatus)
+ })
+
+ let checkingToastId: string | number | undefined
+ unsubs.push(
+ window.api.updater.onStatus((raw) => {
+ const status = raw as UpdateStatus
+ useAppStore.getState().setUpdateStatus(status)
+
+ // Show toasts for user-initiated checks
+ if (status.state === 'checking' && 'userInitiated' in status && status.userInitiated) {
+ checkingToastId = toast.loading('Checking for updates...')
+ } else if (status.state === 'not-available') {
+ if ('userInitiated' in status && status.userInitiated) {
+ toast.success('You\u2019re on the latest version.', { id: checkingToastId })
+ checkingToastId = undefined
+ }
+ } else if (status.state === 'available') {
+ if (checkingToastId) toast.dismiss(checkingToastId)
+ checkingToastId = undefined
+ } else if (status.state === 'downloaded') {
+ toast.success(`Version ${status.version} is ready to install.`, {
+ duration: Infinity,
+ action: {
+ label: 'Restart Now',
+ onClick: () => window.api.updater.quitAndInstall()
+ }
+ })
+ } else if (status.state === 'error' && 'userInitiated' in status && status.userInitiated) {
+ toast.error('Could not check for updates.', {
+ description: status.message,
+ id: checkingToastId
+ })
+ checkingToastId = undefined
+ }
+ })
+ )
+
// Browser zoom fallback when no terminal is active
unsubs.push(
window.api.ui.onTerminalZoom((direction) => {
diff --git a/src/renderer/src/store/slices/ui.ts b/src/renderer/src/store/slices/ui.ts
index 267faa06a24..b4f605f8d05 100644
--- a/src/renderer/src/store/slices/ui.ts
+++ b/src/renderer/src/store/slices/ui.ts
@@ -1,6 +1,6 @@
import type { StateCreator } from 'zustand'
import type { AppState } from '../types'
-import type { PersistedUIState } from '../../../../shared/types'
+import type { PersistedUIState, UpdateStatus } from '../../../../shared/types'
export interface UISlice {
sidebarOpen: boolean
@@ -29,6 +29,8 @@ export interface UISlice {
clearPendingRevealWorktreeId: () => void
persistedUIReady: boolean
hydratePersistedUI: (ui: PersistedUIState) => void
+ updateStatus: UpdateStatus
+ setUpdateStatus: (status: UpdateStatus) => void
}
export const createUISlice: StateCreator
= (set) => ({
@@ -72,5 +74,8 @@ export const createUISlice: StateCreator = (set) => (
groupBy: ui.groupBy,
sortBy: ui.sortBy,
persistedUIReady: true
- })
+ }),
+
+ updateStatus: { state: 'idle' },
+ setUpdateStatus: (status) => set({ updateStatus: status })
})
diff --git a/src/shared/types.ts b/src/shared/types.ts
index fdac95f9e6e..dac711d2d4b 100644
--- a/src/shared/types.ts
+++ b/src/shared/types.ts
@@ -123,12 +123,12 @@ export interface RepoHookSettings {
// ─── Updater ─────────────────────────────────────────────────────────
export type UpdateStatus =
| { state: 'idle' }
- | { state: 'checking' }
+ | { state: 'checking'; userInitiated?: boolean }
| { state: 'available'; version: string }
- | { state: 'not-available' }
+ | { state: 'not-available'; userInitiated?: boolean }
| { state: 'downloading'; percent: number }
| { state: 'downloaded'; version: string }
- | { state: 'error'; message: string }
+ | { state: 'error'; message: string; userInitiated?: boolean }
// ─── Settings ────────────────────────────────────────────────────────
export interface GlobalSettings {