diff --git a/oAI/Services/AppleMailService.swift b/oAI/Services/AppleMailService.swift index 2761cb8..14fe2cd 100644 --- a/oAI/Services/AppleMailService.swift +++ b/oAI/Services/AppleMailService.swift @@ -101,24 +101,23 @@ final class AppleMailService { /// Triggers the real macOS "Confab wants to control Mail" Automation permission prompt if /// the user hasn't been asked yet (a no-op if already granted or denied). /// - /// Live-verified (2026-08-19, several rounds with Rune): the prompt never appears on this - /// machine (macOS 27 beta) for Confab specifically — not a threading bug (tried direct call, - /// `DispatchQueue.main.async`), not the wrong API (tried `AEDeterminePermissionToAutomateTarget` - /// AND a real Apple Event via `runHandler`, both fail identically), not a build/signing issue - /// (verified the installed app's signature, team ID, Info.plist key, and even a notarized - /// build — all correct), and not an OS-wide Automation outage (Terminal → Mail works fine). - /// Every failure returns in single-digit milliseconds — far too fast for a real dialog to have - /// been shown and answered. This matches an already-documented macOS 27 beta bug in this same - /// project (`EKEventStore.requestFullAccessToEvents()`/`CNContactStore.requestAccess()` failing - /// identically for Calendar/Contacts while Reminders/Location work fine) — a category of - /// TCC-gated permission requests that this OS beta just never prompts for from third-party - /// apps, regardless of what the app does. + /// History (2026-08-19 through 2026-08-26): this prompt appeared to never show on Rune's + /// machine (macOS 27 beta), with every attempt failing in single-digit milliseconds — far too + /// fast for a real dialog to have been shown and answered. Four rounds of investigation ruled + /// out threading, the wrong API, and a build/signing issue, converging on "genuine macOS 27 + /// beta OS bug" (matching an apparently identical Calendar/Contacts failure elsewhere in this + /// project). **That diagnosis was wrong.** Checking `tccd`'s own unified log directly (instead + /// of trusting the pattern-match to the Calendar/Contacts case) showed the real reason: + /// `Confab.entitlements` was missing `com.apple.security.automation.apple-events`, so `tccd`'s + /// hardened-runtime policy refused to even prompt. Adding that entitlement fixed it — + /// live-verified granting successfully on beta 7. See `feedback_tccd_log_before_os_bug_diagnosis` + /// in project memory for the standing lesson this produced. /// - /// Given that, this still attempts the real thing first (the way it *should* work, and will - /// once the OS bug is fixed) via the same `runHandler` path `mail_list_accounts`/ - /// `testConnection()` use, but times the attempt and treats an implausibly-fast failure as the - /// known platform bug rather than a real denial — callers can then fall back to sending the - /// user straight to System Settings' Automation pane instead of a dead-end "nothing happened." + /// The instant-failure heuristic below is kept as a defensive fallback (not dead code) in case + /// a future regression — a missing entitlement again, a stuck TCC denial state, or a genuine + /// new OS issue — reintroduces the same symptom: it distinguishes "no dialog was ever shown" + /// from a real, human-timed denial, and routes the former to System Settings instead of a + /// dead-end "nothing happened." @discardableResult func requestAccess() async -> MailAccessOutcome { Log.mail.info("requestAccess: called — attempting a real Apple Event (listAccounts) to trigger the OS consent prompt") @@ -717,7 +716,7 @@ final class AppleMailService { } } let namesSuffix = names.isEmpty ? "" : ": \(names.joined(separator: ", "))" - return .success("Connected \u{2014} found \(count) account\(count == 1 ? "" : "s")\(namesSuffix)") + return .success(String(localized: "Connected \u{2014} found ^[\(count) account](inflect: true)\(namesSuffix)")) case .failure(let error): let message: String switch error { diff --git a/oAI/Views/Screens/SettingsView.swift b/oAI/Views/Screens/SettingsView.swift index aca2165..36fd282 100644 --- a/oAI/Views/Screens/SettingsView.swift +++ b/oAI/Views/Screens/SettingsView.swift @@ -1178,12 +1178,14 @@ It's better to admit "I need more information" or "I cannot do that" than to fak mailAccessState = .denied mailAccessNote = nil case .suspectedPlatformBug: - // Known macOS 27 beta issue (see AppleMailService.requestAccess doc - // comment) — the OS never shows the consent dialog for this app, so - // there's nothing more to try in-app. Send the user to System Settings - // directly rather than leaving the button looking like it did nothing. + // Defensive fallback, not the expected path anymore — see + // AppleMailService.requestAccess doc comment. The one confirmed real + // cause (a missing entitlement) was fixed 2026-08-26; this still + // triggers if some future issue reproduces the same instant-failure + // symptom, so there's nothing more to try in-app but sending the user + // to System Settings. mailAccessState = .denied - mailAccessNote = "macOS isn't showing the permission prompt (a known macOS 27 beta issue) — opened System Settings instead. If Confab isn't listed there under Automation, this can't be granted until Apple fixes it." + mailAccessNote = String(localized: "macOS isn't showing the permission prompt — opened System Settings instead. If Confab isn't listed there under Automation, try quitting and relaunching Confab, or check Settings → MCP → Mail again after a moment.") openPrivacySettings(anchor: "Privacy_Automation") } }