diff --git a/src/hooks/use-clash.ts b/src/hooks/use-clash.ts index f07b2bdd2..20475103d 100644 --- a/src/hooks/use-clash.ts +++ b/src/hooks/use-clash.ts @@ -108,6 +108,7 @@ export const useClash = () => { await mutate(() => patchClashConfig(patch), { id: 'patch-clash-config', revalidate: [['getRuntimeConfig']], + errorNotice: false, }) }) @@ -138,6 +139,7 @@ export const useClashInfo = () => { await mutate(() => patchClashConfig(patch), { id: 'patch-clash-info', revalidate: [['getClashInfo'], ['getClashConfig']], + errorNotice: false, }) }) diff --git a/src/hooks/use-verge.ts b/src/hooks/use-verge.ts index 0b03a83c2..119b4ce1f 100644 --- a/src/hooks/use-verge.ts +++ b/src/hooks/use-verge.ts @@ -39,10 +39,13 @@ export const useVerge = () => { ) } + // Callers own the error toast (GuardState onCatch / page onError), so the + // funnel must not add a second one. const patchVerge = useCallback(async (value: Partial) => { await mutate(() => patchVergeConfig(value), { id: 'patch-verge-config', revalidate: [['getVergeConfig']], + errorNotice: false, }) }, []) diff --git a/src/services/bus/create-bus-handlers.test.ts b/src/services/bus/create-bus-handlers.test.ts index b0927b9ff..c1aa0bb88 100644 --- a/src/services/bus/create-bus-handlers.test.ts +++ b/src/services/bus/create-bus-handlers.test.ts @@ -56,6 +56,7 @@ const mountBus = async () => { revalidateProfiles: () => {}, refreshProxyView: () => {}, readPendingFailures: () => {}, + readRunState: () => {}, }) } @@ -190,6 +191,7 @@ it('routes every event exactly once through one handler', async () => { revalidateProfiles: vi.fn(), refreshProxyView: vi.fn(), readPendingFailures: vi.fn(), + readRunState: vi.fn(), }) bus.handlers['verge://run-state-changed']({ mode: 'Service' } as RunState) diff --git a/src/services/bus/create-bus-handlers.ts b/src/services/bus/create-bus-handlers.ts index b13279983..985951e51 100644 --- a/src/services/bus/create-bus-handlers.ts +++ b/src/services/bus/create-bus-handlers.ts @@ -24,6 +24,8 @@ export interface BusHandlerDeps { refreshProxyView: () => void /** Re-read the backend pending-failure snapshot into the store. */ readPendingFailures: () => void + /** Re-read the run-state snapshot into the store. */ + readRunState: () => void } export interface EventBus { @@ -107,6 +109,9 @@ export const createEventBus = (deps: BusHandlerDeps): EventBus => { } as BusHandlers const onSubscribed = () => { + // Run state is read after subscribing so an event racing the store's + // initial read cannot leave a stale snapshot behind. + deps.readRunState() deps.revalidateKeys(['getVergeConfig']) deps.readPendingFailures() deps.handleNotice(['dns_override::auto_disabled', '']) diff --git a/src/services/bus/use-event-bus.test.ts b/src/services/bus/use-event-bus.test.ts index 4eb964e88..c76742258 100644 --- a/src/services/bus/use-event-bus.test.ts +++ b/src/services/bus/use-event-bus.test.ts @@ -28,6 +28,7 @@ vi.mock('@/store/app-store-context', () => ({ })) vi.mock('@/services/cmds', () => ({ getPendingFailures: vi.fn().mockResolvedValue([]), + getRuntimeState: vi.fn().mockResolvedValue({ mode: 'NotRunning' }), })) vi.mock('@/services/query-client', () => ({ revalidateQueries: vi.fn() })) diff --git a/src/services/bus/use-event-bus.ts b/src/services/bus/use-event-bus.ts index d4797ecfe..a9cecc202 100644 --- a/src/services/bus/use-event-bus.ts +++ b/src/services/bus/use-event-bus.ts @@ -2,7 +2,7 @@ import { listen, type UnlistenFn } from '@tauri-apps/api/event' import { getCurrentWindow } from '@tauri-apps/api/window' import { useEffect } from 'react' -import { getPendingFailures } from '@/services/cmds' +import { getPendingFailures, getRuntimeState } from '@/services/cmds' import { revalidateQueries } from '@/services/query-client' import { useAppDispatch } from '@/store/app-store-context' @@ -31,6 +31,11 @@ export const useEventBus = ( console.warn('[bus] pending failures could not be read:', error) }) } + const readRunState = () => { + getRuntimeState() + .then((runState) => dispatch({ type: 'runState/loaded', runState })) + .catch(() => {}) + } const bus = createEventBus({ dispatch, @@ -41,6 +46,7 @@ export const useEventBus = ( }, refreshProxyView: () => revalidateKeys(['getProxyView']), readPendingFailures, + readRunState, }) let disposed = false