From 0d2ad6e0214e89f490022ee2499d2ec392fc6529 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Mon, 10 Aug 2026 17:03:33 +0200 Subject: [PATCH] Give Mac Catalyst its own App Group prefix and sandbox entitlements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The App Group identifier registered in the portal does not change, but the string the runtime expects does: macOS and Mac Catalyst require the Team ID prefix, iOS forbids it. CalendarrAppGroup now resolves the right one per platform. Getting this wrong is the worst failure mode in the whole port — containerURL() returns nil, every snapshot read and write quietly no-ops, and the widgets show placeholder content forever with no error anywhere. A DEBUG assertion now makes that loud during development. Because the two platforms need different values, they need different entitlements files — listing both strings in one file breaks iOS provisioning on the unregistered prefixed value. Selected via CODE_SIGN_ENTITLEMENTS[sdk=macosx*], verified to resolve correctly for both destinations. The Catalyst entitlements are written App Store grade from the start, so one configuration serves both the Mac App Store and a notarized DMG: sandbox, network client, Contacts (birthday import), user-selected files (.ics import and export), the prefixed App Group, and keychain sharing. Deliberately absent: files.downloads, network.server, device.*, temporary-exception.* — nothing needs them and each is App Review friction. ENABLE_HARDENED_RUNTIME is required for notarization and ignored by the App Store, so it is safe to set unconditionally. Verified: builds for both Mac Catalyst and iOS Simulator. Co-Authored-By: Claude Opus 5 --- Calendarr iOS.xcodeproj/project.pbxproj | 6 +++ .../Calendarr iOS-Catalyst.entitlements | 46 +++++++++++++++++++ .../CalendarrWidgets-Catalyst.entitlements | 19 ++++++++ Shared/WidgetData.swift | 40 ++++++++++++++-- 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 Calendarr iOS/Calendarr iOS-Catalyst.entitlements create mode 100644 CalendarrWidgets/CalendarrWidgets-Catalyst.entitlements diff --git a/Calendarr iOS.xcodeproj/project.pbxproj b/Calendarr iOS.xcodeproj/project.pbxproj index b2b3269..a3e28b3 100644 --- a/Calendarr iOS.xcodeproj/project.pbxproj +++ b/Calendarr iOS.xcodeproj/project.pbxproj @@ -490,10 +490,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Calendarr iOS/Calendarr iOS.entitlements"; + "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "Calendarr iOS/Calendarr iOS-Catalyst.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = PP34X97WS3; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_KEY_CFBundleDisplayName = Calendarr; INFOPLIST_KEY_CFBundleName = Calendarr; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; @@ -533,10 +535,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Calendarr iOS/Calendarr iOS.entitlements"; + "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "Calendarr iOS/Calendarr iOS-Catalyst.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = PP34X97WS3; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_KEY_CFBundleDisplayName = Calendarr; INFOPLIST_KEY_CFBundleName = Calendarr; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; @@ -575,6 +579,7 @@ buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGN_ENTITLEMENTS = CalendarrWidgets/CalendarrWidgets.entitlements; + "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "CalendarrWidgets/CalendarrWidgets-Catalyst.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = PP34X97WS3; GENERATE_INFOPLIST_FILE = NO; @@ -608,6 +613,7 @@ buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGN_ENTITLEMENTS = CalendarrWidgets/CalendarrWidgets.entitlements; + "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "CalendarrWidgets/CalendarrWidgets-Catalyst.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = PP34X97WS3; GENERATE_INFOPLIST_FILE = NO; diff --git a/Calendarr iOS/Calendarr iOS-Catalyst.entitlements b/Calendarr iOS/Calendarr iOS-Catalyst.entitlements new file mode 100644 index 0000000..f8ac65f --- /dev/null +++ b/Calendarr iOS/Calendarr iOS-Catalyst.entitlements @@ -0,0 +1,46 @@ + + + + + + com.apple.security.app-sandbox + + + + com.apple.security.network.client + + + + com.apple.security.personal-information.addressbook + + + + com.apple.security.files.user-selected.read-write + + + + com.apple.security.application-groups + + PP34X97WS3.group.com.scarriffleservices.calendarr + + + + keychain-access-groups + + $(AppIdentifierPrefix)com.scarriffleservices.calendarr.ios + $(AppIdentifierPrefix)com.scarriffleservices.calendarr.shared + + + diff --git a/CalendarrWidgets/CalendarrWidgets-Catalyst.entitlements b/CalendarrWidgets/CalendarrWidgets-Catalyst.entitlements new file mode 100644 index 0000000..8d85469 --- /dev/null +++ b/CalendarrWidgets/CalendarrWidgets-Catalyst.entitlements @@ -0,0 +1,19 @@ + + + + + + com.apple.security.app-sandbox + + + + com.apple.security.application-groups + + PP34X97WS3.group.com.scarriffleservices.calendarr + + + diff --git a/Shared/WidgetData.swift b/Shared/WidgetData.swift index b78bcf6..0a4aea0 100644 --- a/Shared/WidgetData.swift +++ b/Shared/WidgetData.swift @@ -4,9 +4,30 @@ import WidgetKit #endif /// App-Group identifier shared between the main app and the widget extension. -/// IMPORTANT: This must match the App Group capability in BOTH targets -/// and the App Group ID registered in the Apple Developer Portal. -let widgetAppGroupID = "group.com.scarriffleservices.calendarr" +/// +/// IMPORTANT: this must stay byte-identical to `com.apple.security.application-groups` +/// in the matching .entitlements file for the platform being built, and match the +/// App Group registered in the Apple Developer portal. +/// +/// The identifier registered in the portal never changes — only the string the +/// *runtime* expects does. macOS (including Mac Catalyst) requires the Team ID +/// prefix; iOS forbids it. Get this wrong and `containerURL(forSecurityApplication‑ +/// GroupIdentifier:)` returns nil, every read and write below quietly no-ops, and +/// the widgets show placeholder content forever with no error anywhere. +enum CalendarrAppGroup { + /// As registered in the Apple Developer portal. + static let unprefixed = "group.com.scarriffleservices.calendarr" + /// Team ID — the value `$(AppIdentifierPrefix)` expands to at build time. + static let teamID = "PP34X97WS3" + + #if os(macOS) || targetEnvironment(macCatalyst) + static let current = "\(teamID).\(unprefixed)" + #else + static let current = unprefixed + #endif +} + +let widgetAppGroupID = CalendarrAppGroup.current /// Lightweight calendar descriptor stored alongside the event cache so the /// widget configuration intent can offer calendar options without a network call. @@ -108,7 +129,18 @@ enum WidgetStore { private static let calendarsFilename = "widget-calendars.json" private static var containerURL: URL? { - FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: widgetAppGroupID) + let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: widgetAppGroupID) + #if DEBUG + if url == nil { + // A nil container is always a build-configuration bug: the entitlement + // is missing, or its value does not match widgetAppGroupID. It is + // otherwise completely silent, so make it loud while developing. + assertionFailure("App Group container unavailable for \(widgetAppGroupID) — " + + "check com.apple.security.application-groups in the " + + "entitlements for this platform.") + } + #endif + return url } private static var cacheURL: URL? {