diff --git a/resources/build/entitlements.mac.plist b/resources/build/entitlements.mac.plist index bbe60166c..0d3e45f0e 100644 --- a/resources/build/entitlements.mac.plist +++ b/resources/build/entitlements.mac.plist @@ -2,6 +2,8 @@ + com.apple.security.device.audio-input + com.apple.security.cs.allow-dyld-environment-variables com.apple.security.cs.allow-jit diff --git a/src/main/index.ts b/src/main/index.ts index f18dcda30..f6e011c02 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, nativeImage, nativeTheme } from 'electron' +import { app, BrowserWindow, nativeImage, nativeTheme, systemPreferences } from 'electron' import { electronApp, is } from '@electron-toolkit/utils' import devIcon from '../../resources/icon-dev.png?asset' import { Store, initDataPath } from './persistence' @@ -54,6 +54,26 @@ let rateLimits: RateLimitService | null = null let runtimeRpc: OrcaRuntimeRpcServer | null = null let starNag: StarNagService | null = null +function triggerStartupMicrophoneRegistration(): void { + if (process.platform !== 'darwin') { + return + } + try { + const status = systemPreferences.getMediaAccessStatus('microphone') + if (status !== 'not-determined') { + return + } + // Why: terminal child processes (sox/ffmpeg/voice CLIs) cannot reliably + // surface the TCC dialog themselves. Prompt once from the app process after + // the window is visible so mic capture in embedded terminals can work. + void systemPreferences.askForMediaAccess('microphone').catch((error: unknown) => { + console.error('[permissions] Failed to request microphone access:', error) + }) + } catch (error) { + console.error('[permissions] Failed to check microphone access status:', error) + } +} + installUncaughtPipeErrorGuard() // Why: propagate the Orca app version into `process.env` so PTY-env // construction in both main (local-pty-provider) and the forked daemon @@ -242,6 +262,7 @@ app.whenReady().then(async () => { // window, making it impossible for the user to click "Allow". win.once('show', () => { triggerStartupNotificationRegistration(store!) + triggerStartupMicrophoneRegistration() }) app.on('activate', () => { diff --git a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx index 1ffa0cfa1..b7fcb6bcc 100644 --- a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx +++ b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx @@ -155,6 +155,7 @@ export function useDiffCommentDecorator({ }) disposablesRef.current = [onMouseMove, onMouseLeave, onScroll] + const zones = zonesRef.current return () => { for (const d of disposablesRef.current) { @@ -169,10 +170,10 @@ export function useDiffCommentDecorator({ // stale zone ids from a dead editor. The diff effect below deliberately // has no cleanup so comment-only changes don't cause a full zone // rebuild; this cleanup is the single place we reset zone tracking. - for (const entry of zonesRef.current.values()) { + for (const entry of zones.values()) { entry.root.unmount() } - zonesRef.current.clear() + zones.clear() } }, [editor])