diff --git a/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts b/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts index 0d978a14f..fddd0a77d 100644 --- a/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts +++ b/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts @@ -175,18 +175,12 @@ describe('fetchGeminiRateLimits fallback oauth creds', () => { expect(result.error).toContain('Gemini project ID not found') }) - it('returns unavailable when geminiCliOAuthEnabled=false and no google entry in auth.json', async () => { - readFileMock.mockImplementation(async (filePath: string) => { - if (filePath.includes('auth.json')) { - return JSON.stringify({ 'opencode-go': { type: 'api', key: 'k' } }) - } - throw { code: 'ENOENT' } - }) - + it('returns unavailable without reading OAuth files when geminiCliOAuthEnabled=false', async () => { const result = await fetchGeminiRateLimits(false) expect(result.status).toBe('unavailable') expect(result.error).toContain('disabled') + expect(readFileMock).not.toHaveBeenCalled() // No network calls should have been made. expect(netFetchMock).not.toHaveBeenCalled() }) diff --git a/src/main/rate-limits/gemini-usage-fetcher.ts b/src/main/rate-limits/gemini-usage-fetcher.ts index 95b710ef9..1f214b4b5 100644 --- a/src/main/rate-limits/gemini-usage-fetcher.ts +++ b/src/main/rate-limits/gemini-usage-fetcher.ts @@ -203,22 +203,25 @@ async function fetchViaOauthCreds( export async function fetchGeminiRateLimits( geminiCliOAuthEnabled = false ): Promise { + if (!geminiCliOAuthEnabled) { + // Why: the OAuth sources include other apps' data folders on macOS. + // Do not touch them during background polling unless the user opts in. + return { + provider: 'gemini', + session: null, + weekly: null, + updatedAt: Date.now(), + error: 'Gemini CLI OAuth is disabled in settings', + status: 'unavailable' + } + } + try { const authJson = await readAuthJson() const result = authJson?.google?.type === 'oauth' ? await fetchViaAuthJson(authJson.google, geminiCliOAuthEnabled) : await (async () => { - if (!geminiCliOAuthEnabled) { - return { - provider: 'gemini', - session: null, - weekly: null, - updatedAt: Date.now(), - error: 'Gemini CLI OAuth is disabled in settings', - status: 'unavailable' - } as ProviderRateLimits - } const creds = await readGeminiCredentials() return !creds ? ({ diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 32d34ac9f..0e2c1e29a 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -96,7 +96,6 @@ function App(): React.JSX.Element { hydrateEditorSession: s.hydrateEditorSession, hydrateBrowserSession: s.hydrateBrowserSession, fetchBrowserSessionProfiles: s.fetchBrowserSessionProfiles, - fetchDetectedBrowsers: s.fetchDetectedBrowsers, reconnectPersistedTerminals: s.reconnectPersistedTerminals, setDeferredSshReconnectTargets: s.setDeferredSshReconnectTargets, setSshConnectionState: s.setSshConnectionState, @@ -233,7 +232,6 @@ function App(): React.JSX.Element { actions.hydrateEditorSession(session) actions.hydrateBrowserSession(session) await actions.fetchBrowserSessionProfiles() - await actions.fetchDetectedBrowsers() // Why: SSH connections must be re-established BEFORE terminal // reconnect so that reconnectPersistedTerminals can route SSH-backed diff --git a/src/renderer/src/components/browser-pane/BrowserToolbarMenu.tsx b/src/renderer/src/components/browser-pane/BrowserToolbarMenu.tsx index 81a5d1bf6..f050e3cce 100644 --- a/src/renderer/src/components/browser-pane/BrowserToolbarMenu.tsx +++ b/src/renderer/src/components/browser-pane/BrowserToolbarMenu.tsx @@ -42,6 +42,7 @@ export function BrowserToolbarMenu({ const createBrowserSessionProfile = useAppStore((s) => s.createBrowserSessionProfile) const importCookiesFromBrowser = useAppStore((s) => s.importCookiesFromBrowser) const importCookiesToProfile = useAppStore((s) => s.importCookiesToProfile) + const fetchDetectedBrowsers = useAppStore((s) => s.fetchDetectedBrowsers) const browserSessionImportState = useAppStore((s) => s.browserSessionImportState) const [newProfileDialogOpen, setNewProfileDialogOpen] = useState(false) @@ -171,7 +172,15 @@ export function BrowserToolbarMenu({ - + { + if (open) { + // Why: macOS treats other browsers' profile folders as app + // data. Only probe them when the user opens the import menu. + void fetchDetectedBrowsers() + } + }} + > Import Cookies diff --git a/src/renderer/src/components/settings/BrowserProfileRow.tsx b/src/renderer/src/components/settings/BrowserProfileRow.tsx index 3cc648fe2..edb9a78bf 100644 --- a/src/renderer/src/components/settings/BrowserProfileRow.tsx +++ b/src/renderer/src/components/settings/BrowserProfileRow.tsx @@ -46,6 +46,7 @@ export function BrowserProfileRow({ isDefault }: BrowserProfileRowProps): React.JSX.Element { const isImporting = importState?.profileId === profile.id && importState.status === 'importing' + const fetchDetectedBrowsers = useAppStore((s) => s.fetchDetectedBrowsers) const handleImportFromBrowser = async ( browserFamily: string, @@ -115,7 +116,15 @@ export function BrowserProfileRow({ )}
e.stopPropagation()}> - + { + if (open) { + // Why: macOS treats other browsers' profile folders as app + // data. Only probe them when the user opens the import menu. + void fetchDetectedBrowsers() + } + }} + > ) : null}
- + { + if (open) { + // Why: macOS treats other browsers' profile folders as app + // data. Only probe them when the user opens the import menu. + void fetchDetectedBrowsers() + } + }} + >