fix(desktop): expire unknown readiness notices and clear on recovery

This commit is contained in:
funky-xamarin
2026-09-27 07:59:25 +08:00
committed by brooklyn!
parent 4c3da4fbe5
commit 52276088c9
2 changed files with 27 additions and 24 deletions

View File

@@ -246,28 +246,31 @@ describe('refreshOnboarding', () => {
expect(window.localStorage.getItem('hermes-desktop-onboarded-v1')).toBe('1') expect(window.localStorage.getItem('hermes-desktop-onboarded-v1')).toBe('1')
}) })
it('shows a non-blocking notification when preserving configured on fallback', async () => { it('keeps an unknown readiness notice temporary and clears it on recovery (#124545)', async () => {
const notifySpy = vi.spyOn(notifications, 'notify') vi.useFakeTimers()
notifications.clearNotifications()
installApiMock(vi.fn()) installApiMock(vi.fn())
$desktopOnboarding.set( $desktopOnboarding.set(baseState({ configured: true }))
baseState({
configured: true,
providers: [makeOAuthProvider('cached')],
reason: null,
requested: false
})
)
await refreshOnboarding(onboardingContext(fallbackTimeoutGateway())) try {
await refreshOnboarding(onboardingContext(fallbackTimeoutGateway()))
expect(notifications.$notifications.get()).toEqual([
expect.objectContaining({ id: 'runtime-not-ready', kind: 'info' })
])
expect($desktopOnboarding.get().configured).toBe(true)
await vi.advanceTimersByTimeAsync(5_000)
expect(notifications.$notifications.get()).toEqual([])
expect(notifySpy).toHaveBeenCalledWith( // A later outage can show a fresh notice; an authoritative ready clears it
expect.objectContaining({ // without waiting for its timer, and must not dismiss unrelated errors.
id: 'runtime-not-ready', await refreshOnboarding(onboardingContext(fallbackTimeoutGateway()))
kind: 'error' notifications.notify({ id: 'unrelated', kind: 'error', message: 'Keep me' })
}) await refreshOnboarding(onboardingContext(keylessCustomGateway()))
) expect(notifications.$notifications.get().map(item => item.id)).toEqual(['unrelated'])
expect($desktopOnboarding.get().configured).toBe(true) } finally {
notifications.clearNotifications()
vi.useRealTimers()
}
}) })
it('enters setup when the selected OpenRouter credential is genuinely empty', async () => { it('enters setup when the selected OpenRouter credential is genuinely empty', async () => {

View File

@@ -18,7 +18,7 @@ import { isProviderSetupErrorMessage } from '@/lib/provider-setup-errors'
import { evaluateRuntimeReadiness, type RuntimeReadinessResult } from '@/lib/runtime-readiness' import { evaluateRuntimeReadiness, type RuntimeReadinessResult } from '@/lib/runtime-readiness'
import { ackFreeTierNotice, freeTierReadyPending, refreshFreeTierStatus, setFreeTierRoute } from '@/store/free-tier' import { ackFreeTierNotice, freeTierReadyPending, refreshFreeTierStatus, setFreeTierRoute } from '@/store/free-tier'
import { setMainModelAssignment } from '@/store/model-assignment' import { setMainModelAssignment } from '@/store/model-assignment'
import { notify, notifyError } from '@/store/notifications' import { dismissNotification, notify, notifyError } from '@/store/notifications'
import { guidedOnboardingActive } from '@/store/onboarding-gate' import { guidedOnboardingActive } from '@/store/onboarding-gate'
import { captureOnboardingScope, type OnboardingScope } from '@/store/onboarding-scope' import { captureOnboardingScope, type OnboardingScope } from '@/store/onboarding-scope'
import type { OAuthProvider, OAuthStartResponse } from '@/types/hermes' import type { OAuthProvider, OAuthStartResponse } from '@/types/hermes'
@@ -645,6 +645,7 @@ export function closeManualOnboarding() {
export function completeDesktopOnboarding() { export function completeDesktopOnboarding() {
clearPoll() clearPoll()
dismissNotification('runtime-not-ready')
writeCachedConfigured(true) writeCachedConfigured(true)
// A real provider is now connected, so any earlier "choose later" skip is // A real provider is now connected, so any earlier "choose later" skip is
// moot — clear it so the flag never lingers in a configured install. // moot — clear it so the flag never lingers in a configured install.
@@ -721,12 +722,11 @@ export async function refreshOnboarding(ctx: OnboardingContext, stillWanted?: ()
if (shouldPreserveConfiguredOnFallback(runtime, state)) { if (shouldPreserveConfiguredOnFallback(runtime, state)) {
// Gateway probes timed out but the user was already configured — don't // Gateway probes timed out but the user was already configured — don't
// downgrade to the blocking onboarding overlay. Surface a non-blocking // downgrade to the blocking onboarding overlay or claim an error verdict.
// notification with a stable id so repeated calls during an outage dedup // Use the temporary informational notice; recovery clears it early.
// instead of stacking toasts.
notify({ notify({
id: 'runtime-not-ready', id: 'runtime-not-ready',
kind: 'error', kind: 'info',
title: 'Runtime not ready', title: 'Runtime not ready',
message: message:
'Hermes Desktop could not verify the running backend on startup. Some features may be unavailable until the gateway is reachable.' 'Hermes Desktop could not verify the running backend on startup. Some features may be unavailable until the gateway is reachable.'