From 36d1dcaf6929497430b92302e4e1c473b0dcbb07 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:32:51 -0700 Subject: [PATCH] fix(macos): explain the TCC prompts on the settings row, drop the sidebar card The sidebar nudge added in 344d466b was premised on FDA being reachable "only inside onboarding". It isn't: Settings > macOS Permissions has had a full-disk-access row all along (searchable), the Setup Guide hosts the same prompt from both a settings pane and a re-openable modal, and the sidebar already links to that modal via the "Onboarding checklist" entry. The card added a fifth affordance to the same sidebar that already had the fourth, so it bought prominence rather than access - shown to every macOS user without FDA, most of whom never hit #9756. Keep the part that was actually new. The settings row still described the prompts as something projects and worktrees trigger, which is the same misattribution the card carried: the reads come from the agents Orca runs, and macOS names Orca only because it is the responsible process for every terminal child. It also never mentioned that the grant has to cover Orca Helper, or that the preserved daemon keeps stale TCC state until restart. Non-English catalogs get the English string as a placeholder; the bootstrap translators key their cache on the English value, so a changed string is re-translated on the next run. --- .../settings/DeveloperPermissionsPane.tsx | 2 +- .../sidebar/FullDiskAccessNudge.test.tsx | 443 ------------------ .../sidebar/FullDiskAccessNudge.tsx | 287 ------------ src/renderer/src/components/sidebar/index.tsx | 3 - src/renderer/src/i18n/locales/en.json | 13 +- src/renderer/src/i18n/locales/es.json | 13 +- src/renderer/src/i18n/locales/ja.json | 13 +- src/renderer/src/i18n/locales/ko.json | 13 +- src/renderer/src/i18n/locales/zh.json | 13 +- 9 files changed, 6 insertions(+), 794 deletions(-) delete mode 100644 src/renderer/src/components/sidebar/FullDiskAccessNudge.test.tsx delete mode 100644 src/renderer/src/components/sidebar/FullDiskAccessNudge.tsx diff --git a/src/renderer/src/components/settings/DeveloperPermissionsPane.tsx b/src/renderer/src/components/settings/DeveloperPermissionsPane.tsx index abf4507cb59..324039e7437 100644 --- a/src/renderer/src/components/settings/DeveloperPermissionsPane.tsx +++ b/src/renderer/src/components/settings/DeveloperPermissionsPane.tsx @@ -105,7 +105,7 @@ const PERMISSIONS: PermissionDefinition[] = [ get description() { return translate( 'auto.components.settings.DeveloperPermissionsPane.7ca17b62c8', - 'Recommended when projects, worktrees, or symlinked files touch macOS-protected folders.' + "macOS names Orca when the agents it runs read other apps' data, because Orca is the responsible process for terminal commands. Granting this reduces those prompts. Terminals run in a separate helper, so grant Orca Helper too and restart Orca." ) }, actionLabel: 'Open Settings', diff --git a/src/renderer/src/components/sidebar/FullDiskAccessNudge.test.tsx b/src/renderer/src/components/sidebar/FullDiskAccessNudge.test.tsx deleted file mode 100644 index 4a08c24c4e4..00000000000 --- a/src/renderer/src/components/sidebar/FullDiskAccessNudge.test.tsx +++ /dev/null @@ -1,443 +0,0 @@ -// @vitest-environment happy-dom - -import * as React from 'react' -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { toast } from 'sonner' -import type { - DeveloperPermissionRequestResult, - DeveloperPermissionState, - DeveloperPermissionStatus -} from '../../../../shared/developer-permissions-types' -import { TooltipProvider } from '@/components/ui/tooltip' -import { - FullDiskAccessNudge, - resetFullDiskAccessProbeForTests, - shouldShowFullDiskAccessNudge -} from './FullDiskAccessNudge' - -// Why: without this React skips its "update not wrapped in act" warnings, so a -// state update escaping act() in these async probe paths would pass unnoticed. -globalThis.IS_REACT_ACT_ENVIRONMENT = true - -vi.mock('sonner', () => ({ - toast: { - error: vi.fn(), - message: vi.fn(), - success: vi.fn() - } -})) - -const MAC_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' -const WINDOWS_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' - -function setUserAgent(userAgent: string): void { - Object.defineProperty(window.navigator, 'userAgent', { - value: userAgent, - configurable: true - }) -} - -function installDeveloperPermissionsApi(args: { - getStatus: () => Promise - request?: () => Promise -}): { getStatus: ReturnType; request: ReturnType } { - const getStatus = vi.fn(args.getStatus) - const request = vi.fn( - args.request ?? - (async () => ({ - id: 'full-disk-access', - status: 'unknown', - openedSystemSettings: true - })) - ) - Object.assign(window, { api: { developerPermissions: { getStatus, request } } }) - return { getStatus, request } -} - -function fdaStatus(status: DeveloperPermissionStatus): DeveloperPermissionState[] { - return [{ id: 'full-disk-access', status }] -} - -const mountedRoots: Root[] = [] - -async function renderNudge(): Promise<{ container: HTMLDivElement; root: Root }> { - const container = document.createElement('div') - document.body.appendChild(container) - const root = createRoot(container) - mountedRoots.push(root) - await act(async () => { - root.render( - React.createElement(TooltipProvider, null, React.createElement(FullDiskAccessNudge)) - ) - }) - // Flush the one-shot getStatus() probe microtasks. - await act(async () => { - await Promise.resolve() - await Promise.resolve() - }) - return { container, root } -} - -afterEach(async () => { - // Why: clearing document.body leaves the roots mounted, so their window focus - // listener survives and re-probes during later tests. Unmount them instead. - const roots = mountedRoots.splice(0) - await act(async () => { - for (const root of roots) { - root.unmount() - } - }) - vi.restoreAllMocks() - document.body.innerHTML = '' - resetFullDiskAccessProbeForTests() - try { - window.localStorage.clear() - } catch { - // ignore - } -}) - -describe('shouldShowFullDiskAccessNudge', () => { - it('shows on macOS when Full Disk Access is not yet granted', () => { - expect( - shouldShowFullDiskAccessNudge({ isMac: true, dismissed: false, status: 'unknown' }) - ).toBe(true) - }) - - it('hides when granted, ready, unsupported, or unresolved', () => { - for (const status of ['granted', 'ready', 'unsupported', undefined] as const) { - expect(shouldShowFullDiskAccessNudge({ isMac: true, dismissed: false, status })).toBe(false) - } - }) - - it('hides off macOS and when dismissed', () => { - expect( - shouldShowFullDiskAccessNudge({ isMac: false, dismissed: false, status: 'unknown' }) - ).toBe(false) - expect(shouldShowFullDiskAccessNudge({ isMac: true, dismissed: true, status: 'unknown' })).toBe( - false - ) - }) -}) - -describe('FullDiskAccessNudge', () => { - it('renders the ambient card with honest copy when FDA is ungranted on macOS', async () => { - setUserAgent(MAC_UA) - installDeveloperPermissionsApi({ getStatus: async () => fdaStatus('unknown') }) - const { container } = await renderNudge() - expect(container.textContent).toContain('Reduce macOS permission prompts') - expect(container.textContent).toContain('Full Disk Access') - expect(container.textContent).toContain('Open System Settings') - // The grant covers this app, not the detached PTY daemon that owns terminals. - expect(container.textContent).toContain('may still prompt separately') - expect(container.textContent).not.toContain('stops asking') - // Agent sweeps trigger these prompts; macOS only names Orca as the responsible process. - expect(container.textContent).toContain('agents it runs') - }) - - it('stays hidden while the probe is unresolved (no first-paint flash)', async () => { - setUserAgent(MAC_UA) - // A never-resolving probe leaves status undefined. - installDeveloperPermissionsApi({ getStatus: () => new Promise(() => {}) }) - const { container } = await renderNudge() - expect(container.textContent).toBe('') - }) - - it('hides once Full Disk Access is granted', async () => { - setUserAgent(MAC_UA) - installDeveloperPermissionsApi({ getStatus: async () => fdaStatus('granted') }) - const { container } = await renderNudge() - expect(container.textContent).toBe('') - }) - - it('does not probe permissions off macOS', async () => { - setUserAgent(WINDOWS_UA) - const { getStatus } = installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown') - }) - const { container } = await renderNudge() - expect(container.textContent).toBe('') - expect(getStatus).not.toHaveBeenCalled() - }) - - it('stays hidden on the web client whose status list is empty', async () => { - setUserAgent(MAC_UA) - installDeveloperPermissionsApi({ getStatus: async () => [] }) - const { container } = await renderNudge() - expect(container.textContent).toBe('') - }) - - it('opens System Settings via the Full Disk Access request', async () => { - setUserAgent(MAC_UA) - const { request } = installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown') - }) - const { container } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - expect(openButton).toBeTruthy() - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - expect(request).toHaveBeenCalledWith({ id: 'full-disk-access' }) - }) - - it('hides the card when the request reports Full Disk Access was granted', async () => { - setUserAgent(MAC_UA) - installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown'), - request: async () => ({ - id: 'full-disk-access', - status: 'granted', - openedSystemSettings: false - }) - }) - const { container } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - expect(container.textContent).toBe('') - expect(toast.success).toHaveBeenCalled() - }) - - it('re-enables the action and surfaces an error toast when the request rejects', async () => { - setUserAgent(MAC_UA) - installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown'), - request: async () => { - throw new Error('ipc failure') - } - }) - const { container } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - expect(toast.error).toHaveBeenCalled() - // Card stays visible and the button is usable again for a retry. - const retryButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - expect(retryButton).toBeTruthy() - expect((retryButton as HTMLButtonElement).disabled).toBe(false) - }) - - it('probes protected data only once even under React StrictMode', async () => { - setUserAgent(MAC_UA) - const { getStatus } = installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown') - }) - const container = document.createElement('div') - document.body.appendChild(container) - const root = createRoot(container) - // Why: this test builds its own root instead of renderNudge(), so it has to - // register for the afterEach unmount or it leaks a live component into later tests. - mountedRoots.push(root) - await act(async () => { - root.render( - React.createElement( - React.StrictMode, - null, - React.createElement(TooltipProvider, null, React.createElement(FullDiskAccessNudge)) - ) - ) - }) - await act(async () => { - await Promise.resolve() - await Promise.resolve() - }) - expect(getStatus).toHaveBeenCalledTimes(1) - expect(container.textContent).toContain('Reduce macOS permission prompts') - }) - - it('hides on return-focus once the grant takes effect after the CTA', async () => { - setUserAgent(MAC_UA) - let currentStatus: DeveloperPermissionStatus = 'unknown' - installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus(currentStatus), - request: async () => ({ - id: 'full-disk-access', - status: 'unknown', - openedSystemSettings: true - }) - }) - const { container } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - // Opening System Settings alone does not grant, so the card stays. - expect(container.textContent).toContain('Reduce macOS permission prompts') - // The user grants Full Disk Access in System Settings, then returns to Orca. - currentStatus = 'granted' - await act(async () => { - window.dispatchEvent(new Event('focus')) - await Promise.resolve() - await Promise.resolve() - }) - expect(container.textContent).toBe('') - }) - - it('ignores a slow focus refresh that resolves after a newer one saw the grant', async () => { - setUserAgent(MAC_UA) - const pendingResolvers: ((states: DeveloperPermissionState[]) => void)[] = [] - let deferGetStatus = false - installDeveloperPermissionsApi({ - getStatus: () => - deferGetStatus - ? new Promise((resolve) => { - pendingResolvers.push(resolve) - }) - : Promise.resolve(fdaStatus('unknown')), - request: async () => ({ - id: 'full-disk-access', - status: 'unknown', - openedSystemSettings: true - }) - }) - const { container } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - // Rapid blur/focus while the first round-trip is still in flight. - deferGetStatus = true - await act(async () => { - window.dispatchEvent(new Event('focus')) - window.dispatchEvent(new Event('focus')) - }) - expect(pendingResolvers.length).toBe(2) - // The newer refresh observes the grant and hides the card. - await act(async () => { - pendingResolvers[1]?.(fdaStatus('granted')) - await Promise.resolve() - await Promise.resolve() - }) - expect(container.textContent).toBe('') - // The older, slower refresh still reports the pre-grant status; it must not win. - await act(async () => { - pendingResolvers[0]?.(fdaStatus('unknown')) - await Promise.resolve() - await Promise.resolve() - }) - expect(container.textContent).toBe('') - // The stale result must not poison the session cache a later mount reads either. - const remount = await renderNudge() - expect(remount.container.textContent).toBe('') - }) - - it('stops watching for the grant once the card is dismissed', async () => { - setUserAgent(MAC_UA) - const { getStatus } = installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown'), - request: async () => ({ - id: 'full-disk-access', - status: 'unknown', - openedSystemSettings: true - }) - }) - const { container } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - const dismissButton = container.querySelector( - 'button[aria-label="Dismiss Full Disk Access suggestion"]' - ) - await act(async () => { - dismissButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - expect(container.textContent).toBe('') - const callsAtDismiss = getStatus.mock.calls.length - // A later window focus must not re-probe protected data after dismissal. - await act(async () => { - window.dispatchEvent(new Event('focus')) - await Promise.resolve() - await Promise.resolve() - }) - expect(getStatus.mock.calls.length).toBe(callsAtDismiss) - }) - - it('records a granted request result in the session cache even if unmounted mid-request', async () => { - setUserAgent(MAC_UA) - let resolveRequest: (result: DeveloperPermissionRequestResult) => void = () => {} - const pendingRequest = new Promise((resolve) => { - resolveRequest = resolve - }) - installDeveloperPermissionsApi({ - getStatus: async () => fdaStatus('unknown'), - request: () => pendingRequest - }) - const { container, root } = await renderNudge() - const openButton = Array.from(container.querySelectorAll('button')).find((button) => - button.textContent?.includes('Open System Settings') - ) - await act(async () => { - openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - await act(async () => { - root.unmount() - }) - await act(async () => { - resolveRequest({ id: 'full-disk-access', status: 'granted', openedSystemSettings: false }) - await Promise.resolve() - await Promise.resolve() - }) - // A fresh mount reads the updated session cache and stays hidden (granted). - installDeveloperPermissionsApi({ getStatus: async () => fdaStatus('unknown') }) - const remount = await renderNudge() - expect(remount.container.textContent).toBe('') - }) - - it('dismisses immediately, persists, and does not re-probe after remount', async () => { - setUserAgent(MAC_UA) - const first = installDeveloperPermissionsApi({ getStatus: async () => fdaStatus('unknown') }) - const rendered = await renderNudge() - const dismissButton = rendered.container.querySelector( - 'button[aria-label="Dismiss Full Disk Access suggestion"]' - ) - expect(dismissButton).toBeTruthy() - await act(async () => { - dismissButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - expect(rendered.container.textContent).toBe('') - expect(window.localStorage.getItem('orca.fullDiskAccessNudgeDismissed.v1')).toBe('1') - - // Remounting after a permanent dismissal must not render or re-probe. - const second = installDeveloperPermissionsApi({ getStatus: async () => fdaStatus('unknown') }) - const remounted = await renderNudge() - expect(remounted.container.textContent).toBe('') - expect(second.getStatus).not.toHaveBeenCalled() - expect(first.getStatus).toHaveBeenCalledTimes(1) - }) - - it('dismisses for the session even when persistence throws', async () => { - setUserAgent(MAC_UA) - installDeveloperPermissionsApi({ getStatus: async () => fdaStatus('unknown') }) - vi.spyOn(window.localStorage, 'setItem').mockImplementation(() => { - throw new Error('storage unavailable') - }) - const { container } = await renderNudge() - const dismissButton = container.querySelector( - 'button[aria-label="Dismiss Full Disk Access suggestion"]' - ) - await act(async () => { - dismissButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) - expect(container.textContent).toBe('') - }) -}) diff --git a/src/renderer/src/components/sidebar/FullDiskAccessNudge.tsx b/src/renderer/src/components/sidebar/FullDiskAccessNudge.tsx deleted file mode 100644 index 8315e506f52..00000000000 --- a/src/renderer/src/components/sidebar/FullDiskAccessNudge.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { ExternalLink, HardDrive, Loader2, X } from 'lucide-react' -import { toast } from 'sonner' -import type { - DeveloperPermissionId, - DeveloperPermissionState, - DeveloperPermissionStatus -} from '../../../../shared/developer-permissions-types' -import { Button } from '@/components/ui/button' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { useMountedRef } from '@/hooks/useMountedRef' -import { isMacUserAgent } from '../terminal-pane/pane-helpers' -import { - isFullDiskAccessReady, - isFullDiskAccessSetupVisible -} from '../feature-wall/FullDiskAccessSetupPrompt' -import { translate } from '@/i18n/i18n' - -const FULL_DISK_ACCESS_PERMISSION_ID: DeveloperPermissionId = 'full-disk-access' -// Why: camelCase segment + .v1 suffix matches sibling one-time sidebar keys -// (orca.workspaceBoardMovedHintSeen.v1); bump the version to re-surface later. -const DISMISS_KEY = 'orca.fullDiskAccessNudgeDismissed.v1' - -// Why: the FDA status probe reads Safari's TCC-protected data — the same "other -// apps' data" read this nudge exists to reduce — so it must run at most once per -// renderer session, shared across StrictMode replay and sidebar remounts (#9756). -let cachedFullDiskAccessStatus: DeveloperPermissionStatus | undefined -let fullDiskAccessProbed = false -let fullDiskAccessProbe: Promise | null = null -let fullDiskAccessRefreshSequence = 0 - -function getFullDiskAccessStatus( - states: readonly DeveloperPermissionState[] -): DeveloperPermissionStatus | undefined { - return states.find((state) => state.id === FULL_DISK_ACCESS_PERMISSION_ID)?.status -} - -function rememberFullDiskAccessStatus(status: DeveloperPermissionStatus | undefined): void { - cachedFullDiskAccessStatus = status - fullDiskAccessProbed = true -} - -function probeFullDiskAccessStatusOnce(): Promise { - if (fullDiskAccessProbed) { - return Promise.resolve(cachedFullDiskAccessStatus) - } - if (!fullDiskAccessProbe) { - fullDiskAccessProbe = window.api.developerPermissions - .getStatus() - .then((states) => { - rememberFullDiskAccessStatus(getFullDiskAccessStatus(states)) - return cachedFullDiskAccessStatus - }) - .catch(() => { - // Inconclusive probe: allow a later mount to retry rather than caching it. - fullDiskAccessProbe = null - return undefined - }) - } - return fullDiskAccessProbe -} - -// Why: a fresh, cache-bypassing probe used only on CTA return-focus so the card -// can hide the moment a Full Disk Access grant takes effect for this process. -function refreshFullDiskAccessStatus(): Promise { - // Why: rapid focus/blur puts several getStatus() round-trips in flight, and an - // earlier pre-grant result resolving last would un-hide the card and poison the - // session cache — same refresh-sequence guard as FullDiskAccessSetupPrompt. - const refreshId = ++fullDiskAccessRefreshSequence - return window.api.developerPermissions - .getStatus() - .then((states) => { - if (refreshId !== fullDiskAccessRefreshSequence) { - return cachedFullDiskAccessStatus - } - const next = getFullDiskAccessStatus(states) - rememberFullDiskAccessStatus(next) - return next - }) - .catch(() => cachedFullDiskAccessStatus) -} - -export function resetFullDiskAccessProbeForTests(): void { - cachedFullDiskAccessStatus = undefined - fullDiskAccessProbed = false - fullDiskAccessProbe = null -} - -function readDismissed(): boolean { - try { - return window.localStorage.getItem(DISMISS_KEY) === '1' - } catch { - return false - } -} - -function persistDismissed(): void { - try { - window.localStorage.setItem(DISMISS_KEY, '1') - } catch { - // Best-effort; if storage is unavailable the nudge returns next launch. - } -} - -export function shouldShowFullDiskAccessNudge(args: { - isMac: boolean - dismissed: boolean - status: DeveloperPermissionStatus | undefined -}): boolean { - return ( - args.isMac && - !args.dismissed && - isFullDiskAccessSetupVisible(args.status) && - !isFullDiskAccessReady(args.status) - ) -} - -/** - * Ambient, dismissable sidebar card that surfaces the durable Full Disk Access - * grant for the recurring macOS "access other apps' data" prompt (#9756) — a TCC - * identity-churn artifact, not a code bug we can otherwise fix. Off macOS renders nothing. - */ -export function FullDiskAccessNudge(): React.JSX.Element | null { - const mountedRef = useMountedRef() - const isMac = isMacUserAgent() - const [dismissed, setDismissed] = useState(() => readDismissed()) - const [status, setStatus] = useState( - () => cachedFullDiskAccessStatus - ) - const [requesting, setRequesting] = useState(false) - const [watchForGrant, setWatchForGrant] = useState(false) - - useEffect(() => { - // Why: probe only when the card could actually show — never off macOS or once - // dismissed. probeFullDiskAccessStatusOnce() reads protected data at most once. - if (!isMac || dismissed) { - return - } - void probeFullDiskAccessStatusOnce().then((next) => { - if (mountedRef.current) { - setStatus(next) - } - }) - }, [isMac, dismissed, mountedRef]) - - useEffect(() => { - // Why: only after the user clicked "Open System Settings" (explicit intent to - // grant), watch return-focus for the grant so the card hides once FDA is - // effective — one gated read per focus. Stops once granted or dismissed so a - // permanently dismissed card never keeps probing protected data. - if (!watchForGrant || dismissed || isFullDiskAccessReady(status)) { - return - } - const onFocus = (): void => { - void refreshFullDiskAccessStatus().then((next) => { - if (mountedRef.current) { - setStatus(next) - } - }) - } - window.addEventListener('focus', onFocus) - return () => window.removeEventListener('focus', onFocus) - }, [watchForGrant, dismissed, status, mountedRef]) - - const handleDismiss = useCallback((): void => { - persistDismissed() - setDismissed(true) - }, []) - - const handleOpenSystemSettings = useCallback(async (): Promise => { - setRequesting(true) - try { - const result = await window.api.developerPermissions.request({ - id: FULL_DISK_ACCESS_PERMISSION_ID - }) - // Why: the request re-probes in main (authoritative); record it in the - // session cache before the mount check so an unmount mid-request can't - // strand a later remount on a stale status. - rememberFullDiskAccessStatus(result.status) - if (!mountedRef.current) { - return - } - // Why: feed it back so a granted result hides the card, and watch return- - // focus for the eventual grant since it opens Settings first. - setStatus(result.status) - setWatchForGrant(true) - if (result.status === 'granted') { - toast.success( - translate( - 'auto.components.sidebar.FullDiskAccessNudge.granted', - 'Full Disk Access granted' - ) - ) - } else if (result.openedSystemSettings) { - toast.message( - translate( - 'auto.components.sidebar.FullDiskAccessNudge.openedPrivacy', - 'Opened macOS Privacy & Security' - ) - ) - } - } catch { - if (mountedRef.current) { - toast.error( - translate( - 'auto.components.sidebar.FullDiskAccessNudge.requestError', - 'Could not open System Settings' - ) - ) - } - } finally { - if (mountedRef.current) { - setRequesting(false) - } - } - }, [mountedRef]) - - // Why: hidden while the probe is unresolved (no first-paint flash) and once FDA - // is already granted; off macOS or after a permanent dismissal it never shows. - if (!shouldShowFullDiskAccessNudge({ isMac, dismissed, status })) { - return null - } - - return ( -
-
-
-
- -

- {translate( - 'auto.components.sidebar.FullDiskAccessNudge.title', - 'Reduce macOS permission prompts' - )} -

-
- - - - - - {translate('auto.components.sidebar.FullDiskAccessNudge.dismiss', 'Dismiss')} - - -
-

- {translate( - 'auto.components.sidebar.FullDiskAccessNudge.body', - "These prompts come from Orca and the agents it runs reading other apps' data. Full Disk Access reduces them; terminal sessions may still prompt separately." - )} -

- -
-
- ) -} diff --git a/src/renderer/src/components/sidebar/index.tsx b/src/renderer/src/components/sidebar/index.tsx index 504e284f70d..b224e594277 100644 --- a/src/renderer/src/components/sidebar/index.tsx +++ b/src/renderer/src/components/sidebar/index.tsx @@ -5,7 +5,6 @@ import { useSidebarResize } from '@/hooks/useSidebarResize' import SidebarHeader from './SidebarHeader' import SidebarNav from './SidebarNav' import SetupScriptPromptCard from './SetupScriptPromptCard' -import { FullDiskAccessNudge } from './FullDiskAccessNudge' import WorktreeList from './WorktreeList' import SidebarToolbar from './SidebarToolbar' import WorkspaceKanbanDrawer from './WorkspaceKanbanDrawer' @@ -157,8 +156,6 @@ function Sidebar({ - - {/* Fixed bottom toolbar */}