diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 61ec4151..35f3df1e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2368,6 +2368,7 @@ function App() { hideUpdateDownloadProgress, isAboutOpen, isBackgroundProgressForLatestUpdate, + isCheckingForUpdates, isLatestUpdateDownloaded, isUpdateChannelLoading, isUpdateChannelSaving, @@ -5162,7 +5163,14 @@ function App() { lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? ( ) : null, - , + , closeAction ?? null, lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? ( diff --git a/frontend/src/hooks/useAppUpdateManager.test.tsx b/frontend/src/hooks/useAppUpdateManager.test.tsx index 88dea93d..200d463b 100644 --- a/frontend/src/hooks/useAppUpdateManager.test.tsx +++ b/frontend/src/hooks/useAppUpdateManager.test.tsx @@ -198,6 +198,38 @@ describe('useAppUpdateManager', () => { expect(backendApp.CheckForUpdates).not.toHaveBeenCalled(); }); + it('exposes a loading state while checking for updates', async () => { + let resolveCheck: ((result: Record) => void) | undefined; + const checkPromise = new Promise>((resolve) => { + resolveCheck = resolve; + }); + backendApp.CheckForUpdates.mockReturnValue(checkPromise); + + renderHook(); + + let pendingCheck: Promise | undefined; + act(() => { + pendingCheck = hook?.checkForUpdates(false); + }); + + expect(hook?.isCheckingForUpdates).toBe(true); + expect(hook?.aboutUpdateStatus).toBe('app.about.update_status.checking'); + + await act(async () => { + resolveCheck?.({ + success: true, + data: { + hasUpdate: false, + currentVersion: '0.8.1', + latestVersion: '0.8.1', + }, + }); + await pendingCheck; + }); + + expect(hook?.isCheckingForUpdates).toBe(false); + }); + it('merges complete MSI download metadata returned by the backend', async () => { backendApp.CheckForUpdates.mockResolvedValue({ success: true, diff --git a/frontend/src/hooks/useAppUpdateManager.ts b/frontend/src/hooks/useAppUpdateManager.ts index 3eccbc71..75b496f4 100644 --- a/frontend/src/hooks/useAppUpdateManager.ts +++ b/frontend/src/hooks/useAppUpdateManager.ts @@ -221,6 +221,7 @@ export const useAppUpdateManager = ({ const [installMode, setInstallMode] = useState('unknown'); const [isUpdateChannelLoading, setIsUpdateChannelLoading] = useState(false); const [isUpdateChannelSaving, setIsUpdateChannelSaving] = useState(false); + const [isCheckingForUpdates, setIsCheckingForUpdates] = useState(false); const [aboutInfo, setAboutInfo] = useState(() => DEFAULT_ABOUT_INFO); const [aboutUpdateStatus, setAboutUpdateStatus] = useState(''); const [lastUpdateInfo, setLastUpdateInfo] = useState(null); @@ -470,6 +471,7 @@ export const useAppUpdateManager = ({ const checkForUpdates = useCallback(async (silent: boolean) => { if (updateCheckInFlightRef.current) return; updateCheckInFlightRef.current = true; + setIsCheckingForUpdates(true); if (!silent) { setAboutUpdateStatus(t('app.about.update_status.checking')); } @@ -477,8 +479,13 @@ export const useAppUpdateManager = ({ const checkFn = silent && typeof updateAPI.CheckForUpdatesSilently === 'function' ? updateAPI.CheckForUpdatesSilently : updateAPI.CheckForUpdates; - const res = await checkFn(); - updateCheckInFlightRef.current = false; + let res: any = null; + try { + res = await checkFn(); + } finally { + updateCheckInFlightRef.current = false; + setIsCheckingForUpdates(false); + } if (!res?.success) { if (!silent) { const error = res?.message || t('common.unknown'); @@ -785,6 +792,7 @@ export const useAppUpdateManager = ({ hideUpdateDownloadProgress, isAboutOpen, isBackgroundProgressForLatestUpdate, + isCheckingForUpdates, isLatestUpdateDownloaded, isUpdateChannelLoading, isUpdateChannelSaving,