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
1 change: 1 addition & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ app.whenReady().then(() => {

// Updater IPC
ipcMain.handle('updater:getStatus', () => getUpdateStatus())
ipcMain.handle('updater:getVersion', () => app.getVersion())
ipcMain.handle('updater:check', () => checkForUpdates())
ipcMain.handle('updater:quitAndInstall', () => quitAndInstall())

Expand Down
35 changes: 22 additions & 13 deletions src/main/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export function checkForUpdates(): void {
sendStatus({ state: 'not-available' })
return
}
sendStatus({ state: 'checking' })
// Don't send 'checking' here — the 'checking-for-update' event handler does it,
// and sending it from both places causes duplicate notifications (issue #35).
autoUpdater.checkForUpdates().catch((err) => {
sendStatus({ state: 'error', message: String(err?.message ?? err) })
})
Expand All @@ -35,7 +36,8 @@ export function checkForUpdatesFromMenu(): void {
}

userInitiatedCheck = true
sendStatus({ state: 'checking', userInitiated: true })
// Don't send 'checking' here — the 'checking-for-update' event handler does it,
// and sending it from both places causes duplicate notifications (issue #35).

autoUpdater.checkForUpdates().catch((err) => {
userInitiatedCheck = false
Expand All @@ -44,18 +46,25 @@ export function checkForUpdatesFromMenu(): void {
}

export function quitAndInstall(): void {
// Graceful shutdown: close all windows before letting the updater restart.
// This prevents macOS from showing "quit unexpectedly" dialogs because
// autoUpdater.quitAndInstall() calls app.exit() which bypasses lifecycle.
const windows = BrowserWindow.getAllWindows()
for (const win of windows) {
win.removeAllListeners('close')
win.destroy()
if (process.platform === 'darwin') {
// On macOS, autoUpdater.quitAndInstall() calls app.exit() which bypasses
// the normal quit lifecycle, causing the "quit unexpectedly" crash dialog.
// Instead, use app.relaunch() + app.quit() which goes through the proper
// lifecycle. autoInstallOnAppQuit (set in setupAutoUpdater) ensures the
// update is applied during the quit process.
app.relaunch()
app.quit()
} else {
// On Windows/Linux, quitAndInstall works correctly with the NSIS installer.
const windows = BrowserWindow.getAllWindows()
for (const win of windows) {
win.removeAllListeners('close')
win.destroy()
}
setImmediate(() => {
autoUpdater.quitAndInstall(false, true)
})
}

setImmediate(() => {
autoUpdater.quitAndInstall(false, true)
})
}

export function setupAutoUpdater(mainWindow: BrowserWindow): void {
Expand Down
1 change: 1 addition & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ const api = {

updater: {
getStatus: (): Promise<unknown> => ipcRenderer.invoke('updater:getStatus'),
getVersion: (): Promise<string> => ipcRenderer.invoke('updater:getVersion'),
check: (): Promise<void> => ipcRenderer.invoke('updater:check'),
quitAndInstall: (): Promise<void> => ipcRenderer.invoke('updater:quitAndInstall'),
onStatus: (callback: (status: unknown) => void): (() => void) => {
Expand Down
10 changes: 9 additions & 1 deletion src/renderer/src/components/settings/GeneralPane.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useState } from 'react'
import type { GlobalSettings } from '../../../../shared/types'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
Expand All @@ -18,6 +19,11 @@ export function GeneralPane({
displayedGitUsername
}: GeneralPaneProps): React.JSX.Element {
const updateStatus = useAppStore((s) => s.updateStatus)
const [appVersion, setAppVersion] = useState<string | null>(null)

useEffect(() => {
window.api.updater.getVersion().then(setAppVersion)
}, [])

const handleBrowseWorkspace = async () => {
const path = await window.api.repos.pickFolder()
Expand Down Expand Up @@ -136,7 +142,9 @@ export function GeneralPane({
<section className="space-y-4">
<div className="space-y-1">
<h2 className="text-sm font-semibold">Updates</h2>
<p className="text-xs text-muted-foreground">Check for new versions of Orca.</p>
<p className="text-xs text-muted-foreground">
Current version: {appVersion ?? '…'}
</p>
</div>

<div className="flex items-center gap-3">
Expand Down