diff --git a/src/renderer/src/components/settings/BrowserUsePane.tsx b/src/renderer/src/components/settings/BrowserUsePane.tsx index 146a6260ca3..53c85a0f98a 100644 --- a/src/renderer/src/components/settings/BrowserUsePane.tsx +++ b/src/renderer/src/components/settings/BrowserUsePane.tsx @@ -1,5 +1,5 @@ /* eslint-disable max-lines -- Why: Browser Use setup keeps enablement, CLI registration, skill install, cookie import, examples, and interaction tracking in one pane so the three-step setup state stays coherent. */ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { Import, Loader2, MousePointerClick } from 'lucide-react' import { toast } from 'sonner' import type { CliInstallStatus } from '../../../../shared/cli-install-types' @@ -58,6 +58,20 @@ export function BrowserUseSetup({ const [cliStatus, setCliStatus] = useState(null) const [cliLoading, setCliLoading] = useState(true) const [cliBusy, setCliBusy] = useState(false) + const mountedRef = useRef(true) + + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + + const handleCliStatusChange = useCallback((nextStatus: CliInstallStatus): void => { + if (mountedRef.current) { + setCliStatus(nextStatus) + } + }, []) // Why: the toggle gates only whether we show the setup instructions. We // persist it in localStorage instead of global settings because it has no @@ -75,16 +89,20 @@ export function BrowserUseSetup({ } } - const refreshCli = async (): Promise => { + const refreshCli = useCallback(async (): Promise => { setCliLoading(true) try { - setCliStatus(await window.api.cli.getInstallStatus()) + handleCliStatusChange(await window.api.cli.getInstallStatus()) } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to load CLI status.') + if (mountedRef.current) { + toast.error(error instanceof Error ? error.message : 'Failed to load CLI status.') + } } finally { - setCliLoading(false) + if (mountedRef.current) { + setCliLoading(false) + } } - } + }, [handleCliStatusChange]) useEffect(() => { // Why: skip IPC work when the feature is toggled off — the component @@ -94,7 +112,7 @@ export function BrowserUseSetup({ } void refreshCli() void fetchBrowserSessionProfiles() - }, [browserUseEnabled, fetchBrowserSessionProfiles]) + }, [browserUseEnabled, fetchBrowserSessionProfiles, refreshCli]) const defaultProfile = browserSessionProfiles.find((p) => p.id === 'default') // Why: this step explicitly imports into the default profile, so completion @@ -121,13 +139,15 @@ export function BrowserUseSetup({ setCliBusy(true) try { const next = await ensureOrcaCliAvailableForAgentSkillTerminal({ - onStatusChange: setCliStatus + onStatusChange: handleCliStatusChange }) - if (isOrcaCliAvailableOnPath(next)) { + if (mountedRef.current && isOrcaCliAvailableOnPath(next)) { toast.success('Registered the Orca CLI in PATH.') } } finally { - setCliBusy(false) + if (mountedRef.current) { + setCliBusy(false) + } } } @@ -328,7 +348,9 @@ export function BrowserUseSetup({ preInstallNotice={AGENT_SKILL_CLI_PREREQUISITE_NOTICE} onBeforeOpenTerminal={async () => { useAppStore.getState().recordFeatureInteraction('agent-browser-setup') - await ensureOrcaCliAvailableForAgentSkillTerminal({ onStatusChange: setCliStatus }) + await ensureOrcaCliAvailableForAgentSkillTerminal({ + onStatusChange: handleCliStatusChange + }) }} onRecheck={refreshSkill} />