fix(update): keep dismissed repeated errors hidden (#1773)

This commit is contained in:
Aqil Aziz
2026-05-14 21:04:17 -07:00
committed by GitHub
parent ef4ae5af1c
commit fa860a2a43
2 changed files with 31 additions and 6 deletions
+9 -5
View File
@@ -13,7 +13,7 @@
* Visual conventions mirror `LocalAIDownloadSnackbar` — bottom-right portal,
* stone-900 panel, primary gradient progress bar.
*/
import { useCallback, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useAppUpdate } from '../hooks/useAppUpdate';
@@ -48,12 +48,14 @@ const AppUpdatePrompt = (props: AppUpdatePromptProps) => {
const [dismissed, setDismissed] = useState(false);
const [prevPhase, setPrevPhase] = useState(phase);
// Re-show on every transition INTO a visible phase, even if the user had
// dismissed a previous error/prompt earlier in the session.
const dismissedErrorRef = useRef<string | null>(null);
const currentErrorKey = error ?? 'Update failed. See logs for details.';
// Re-show on every transition INTO a visible non-error phase, or when a new
// error differs from the one the user already dismissed this session.
if (phase !== prevPhase) {
setPrevPhase(phase);
if (shouldShow(phase) && !shouldShow(prevPhase)) {
setDismissed(false);
setDismissed(phase === 'error' && dismissedErrorRef.current === currentErrorKey);
}
}
@@ -66,15 +68,17 @@ const AppUpdatePrompt = (props: AppUpdatePromptProps) => {
}, []);
const handleRetryDownload = useCallback(() => {
dismissedErrorRef.current = null;
setDismissed(false);
reset();
void download();
}, [reset, download]);
const handleDismissError = useCallback(() => {
dismissedErrorRef.current = currentErrorKey;
reset();
setDismissed(true);
}, [reset]);
}, [currentErrorKey, reset]);
if (!shouldShow(phase) || dismissed) return null;
@@ -8,7 +8,7 @@
* (`ready_to_install`)
* - error surface with retry path
*/
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { renderWithProviders } from '../../test/test-utils';
@@ -225,6 +225,27 @@ describe('AppUpdatePrompt', () => {
});
});
it('does not re-open the same dismissed update error on the next background cycle', async () => {
renderWithProviders(
<AppUpdatePrompt autoCheck={false} initialCheckDelayMs={0} recheckIntervalMs={0} />
);
await waitFor(() => expect(statusListeners.length).toBeGreaterThan(0));
emitStatus('error');
await waitFor(() => expect(screen.getByText('Update failed')).toBeInTheDocument());
fireEvent.click(screen.getByRole('button', { name: /^Dismiss$/i }));
await waitFor(() => {
expect(screen.queryByText('Update failed')).not.toBeInTheDocument();
});
await act(async () => {
emitStatus('checking');
emitStatus('error');
});
expect(screen.queryByText('Update failed')).not.toBeInTheDocument();
});
it('renders nothing when not in Tauri', async () => {
mockIsTauri.mockReturnValue(false);