fix(macos): add a Full Disk Access nudge to reduce recurring TCC prompts (#9756)

macOS shows the "Orca wants to access other apps' data"
(kTCCServiceSystemPolicyAppData) prompt and it can keep reappearing. The
reappearing loop is not a fixable app bug: it is TCC identity churn — an
unsigned local rebuild mints a new code identity each build, so macOS treats
each as a new app — and Orca's other-app reads are already gated behind opt-in
settings or explicit user actions.

The durable remedy for the population we can help (release users) is Full Disk
Access, a superset macOS grant that stops these prompts for a stable identity.
Surface it with an ambient, dismissable sidebar card that reuses the existing
developer-permissions IPC. macOS-only; probes FDA status at most once per
renderer session (the probe itself reads protected data, so it must not repeat
on focus/remount); "Open System Settings" opens the Full Disk Access pane;
permanent localStorage dismissal.
This commit is contained in:
Neil
2026-07-22 02:25:21 -07:00
parent 9097ea0409
commit 344d466b88
8 changed files with 707 additions and 0 deletions
@@ -0,0 +1,370 @@
// @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'
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<DeveloperPermissionState[]>
request?: () => Promise<DeveloperPermissionRequestResult>
}): { getStatus: ReturnType<typeof vi.fn>; request: ReturnType<typeof vi.fn> } {
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 }]
}
async function renderNudge(): Promise<{ container: HTMLDivElement; root: Root }> {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
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(() => {
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')
})
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)
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('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<DeveloperPermissionRequestResult>((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('')
})
})
@@ -0,0 +1,279 @@
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<DeveloperPermissionStatus | undefined> | null = null
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<DeveloperPermissionStatus | undefined> {
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<DeveloperPermissionStatus | undefined> {
return window.api.developerPermissions
.getStatus()
.then((states) => {
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<boolean>(() => readDismissed())
const [status, setStatus] = useState<DeveloperPermissionStatus | undefined>(
() => 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<void> => {
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 (
<div className="shrink-0 px-3 pb-2">
<div className="worktree-sidebar-notice-card rounded-lg p-3 text-worktree-sidebar-accent-foreground">
<div className="flex items-start justify-between gap-2">
<div className="flex min-w-0 items-start gap-2">
<HardDrive className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<p className="text-sm font-semibold leading-snug">
{translate(
'auto.components.sidebar.FullDiskAccessNudge.title',
'Reduce macOS permission prompts'
)}
</p>
</div>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon-xs"
className="-mr-1 -mt-1 shrink-0 text-muted-foreground"
aria-label={translate(
'auto.components.sidebar.FullDiskAccessNudge.dismissAriaLabel',
'Dismiss Full Disk Access suggestion'
)}
onClick={handleDismiss}
>
<X className="size-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>
{translate('auto.components.sidebar.FullDiskAccessNudge.dismiss', 'Dismiss')}
</TooltipContent>
</Tooltip>
</div>
<p className="mt-1 text-xs leading-snug text-muted-foreground">
{translate(
'auto.components.sidebar.FullDiskAccessNudge.body',
'Grant Full Disk Access so macOS stops asking when this copy of Orca reads protected app and folder data.'
)}
</p>
<Button
type="button"
variant="outline"
size="xs"
className="mt-2 w-full gap-1.5"
disabled={requesting}
onClick={() => void handleOpenSystemSettings()}
>
{requesting ? (
<Loader2 className="size-3 animate-spin" />
) : (
<ExternalLink className="size-3" />
)}
{requesting
? translate('auto.components.sidebar.FullDiskAccessNudge.opening', 'Opening…')
: translate(
'auto.components.sidebar.FullDiskAccessNudge.openSystemSettings',
'Open System Settings'
)}
</Button>
</div>
</div>
)
}
@@ -5,6 +5,7 @@ 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'
@@ -156,6 +157,8 @@ function Sidebar({
<SetupScriptPromptCard />
<FullDiskAccessNudge />
{/* Fixed bottom toolbar */}
<SidebarToolbar
workspaceBoardOpen={workspaceBoardOpen}
+11
View File
@@ -4827,6 +4827,17 @@
"cancel": "Cancel",
"forget": "Remove from Orca",
"reconnectAndDelete": "Reconnect & Delete"
},
"FullDiskAccessNudge": {
"granted": "Full Disk Access granted",
"openedPrivacy": "Opened macOS Privacy & Security",
"requestError": "Could not open System Settings",
"title": "Reduce macOS permission prompts",
"dismissAriaLabel": "Dismiss Full Disk Access suggestion",
"dismiss": "Dismiss",
"body": "Grant Full Disk Access so macOS stops asking when this copy of Orca reads protected app and folder data.",
"opening": "Opening…",
"openSystemSettings": "Open System Settings"
}
},
"shared": {
+11
View File
@@ -4804,6 +4804,17 @@
"cancel": "Cancelar",
"forget": "Eliminar de Orca",
"reconnectAndDelete": "Reconectar y eliminar"
},
"FullDiskAccessNudge": {
"granted": "Full Disk Access granted",
"openedPrivacy": "Opened macOS Privacy & Security",
"requestError": "Could not open System Settings",
"title": "Reduce macOS permission prompts",
"dismissAriaLabel": "Dismiss Full Disk Access suggestion",
"dismiss": "Dismiss",
"body": "Grant Full Disk Access so macOS stops asking when this copy of Orca reads protected app and folder data.",
"opening": "Opening…",
"openSystemSettings": "Open System Settings"
}
},
"shared": {
+11
View File
@@ -4804,6 +4804,17 @@
"cancel": "キャンセル",
"forget": "Orcaから削除",
"reconnectAndDelete": "再接続して削除"
},
"FullDiskAccessNudge": {
"granted": "Full Disk Access granted",
"openedPrivacy": "Opened macOS Privacy & Security",
"requestError": "Could not open System Settings",
"title": "Reduce macOS permission prompts",
"dismissAriaLabel": "Dismiss Full Disk Access suggestion",
"dismiss": "Dismiss",
"body": "Grant Full Disk Access so macOS stops asking when this copy of Orca reads protected app and folder data.",
"opening": "Opening…",
"openSystemSettings": "Open System Settings"
}
},
"shared": {
+11
View File
@@ -4804,6 +4804,17 @@
"cancel": "취소",
"forget": "Orca에서 제거",
"reconnectAndDelete": "재연결 후 삭제"
},
"FullDiskAccessNudge": {
"granted": "Full Disk Access granted",
"openedPrivacy": "Opened macOS Privacy & Security",
"requestError": "Could not open System Settings",
"title": "Reduce macOS permission prompts",
"dismissAriaLabel": "Dismiss Full Disk Access suggestion",
"dismiss": "Dismiss",
"body": "Grant Full Disk Access so macOS stops asking when this copy of Orca reads protected app and folder data.",
"opening": "Opening…",
"openSystemSettings": "Open System Settings"
}
},
"shared": {
+11
View File
@@ -4804,6 +4804,17 @@
"cancel": "取消",
"forget": "从 Orca 中移除",
"reconnectAndDelete": "重新连接并删除"
},
"FullDiskAccessNudge": {
"granted": "Full Disk Access granted",
"openedPrivacy": "Opened macOS Privacy & Security",
"requestError": "Could not open System Settings",
"title": "Reduce macOS permission prompts",
"dismissAriaLabel": "Dismiss Full Disk Access suggestion",
"dismiss": "Dismiss",
"body": "Grant Full Disk Access so macOS stops asking when this copy of Orca reads protected app and folder data.",
"opening": "Opening…",
"openSystemSettings": "Open System Settings"
}
},
"shared": {