mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(update): keep dismissed repeated errors hidden (#1773)
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user