Plan: Mac (Catalyst) via TestFlight
Status: planned 2026-09-06, blocked on the holder’s Mac signing materials. Tracked in the backlog under Publishing.
Context
The iOS build 1.0.1 (3) is in TestFlight (app-only; widgets are disabled until the corrected widget profile arrives). The Mac build goes through the same App Store Connect record but follows Mac App Store rules: App Sandbox is mandatory, the app needs Mac App Store provisioning profiles, and the .pkg needs a Mac Installer Distribution certificate. Decision: keep the App Group in the Mac build.
Key finding (Apple Forums, DTS, macOS 15 beta 4+): a Mac Catalyst app should keep the iOS-style group name group.*, authorized by the embedded provisioning profile. The macOS 15 prompt “wants to access data from other apps” does not appear when the group is in the profile or the build comes from the Mac App Store. So WidgetDataConstants.appGroupIdentifier = "group.tools.minuta.apps" stays and no App Group code changes. Section 8 of the TestFlight guide (Team-ID-prefixed group) is outdated and must be rewritten.
The only code change: a sandbox guard for the CLI discovery pointer. StorageLocationManager.publishResolvedStoragePath() (Minuta/Sources/Services/StorageLocationManager.swift) writes <realHome>/Library/Application Support/minuta/storage-path.txt through CLIConfigService.writeStoragePath (Shared/Sources/MinutaShared/Services/CLIConfigService.swift); under App Sandbox that path is outside the container and the write is denied. There is no guard today, only a caught error.
Verified locally: xcodebuild -help documents the installerSigningCertificate export option (manual signing, selector "Mac Installer Distribution").
Needed from the Account Holder (blocks the archive)
Add a “Mac” section to TESTFLIGHT-HOLDER.md:
- Identifiers > App ID
tools.minuta.app> enable the Mac Catalyst capability (same fortools.minuta.app.widgetsfor later). App Groups is already on. - Certificates > + > Mac Installer Distribution (CSR on the holder’s Mac, as before) > export as
.p12with the private key; password through a separate channel. - Profiles > + > Distribution > Mac App Store Connect, platform Mac Catalyst, App ID
tools.minuta.app, the existing Apple Distribution certificate > name exactlytools.minuta.app MacAppStore. Later the same fortools.minuta.app.widgets MacAppStore. - Send: the Mac Installer
.p12+ password, the.provisionprofile.
In App Store Connect (Admin): app Minuta > add the macOS platform.
Project changes (can be done before the materials arrive)
Minuta/TestFlight/Minuta.macOS.entitlements(new, committed, no secrets):com.apple.security.app-sandbox = truecom.apple.security.files.user-selected.read-write = truecom.apple.security.files.bookmarks.app-scope = true— without itbookmarkData(options: .withSecurityScope)inStorageLocationManager.saveBookmarkfails under sandboxcom.apple.security.application-groups = [group.tools.minuta.apps]
No network entitlements: the app is offline and the TestFlight xcconfig drops
MANUAL_DISTRIBUTION, so the update check is not compiled in.Sandbox guard in
StorageLocationManager.publishResolvedStoragePath(): skippublisher.writeStoragePath(url)when the process is sandboxed (ProcessInfo.processInfo.environment["APP_SANDBOX_CONTAINER_ID"] != nil) and log that the CLI pointer is not published. KeepwriteVersionMarker(it writes inside the storage folder). ExistingStorageLocationManagertests with an injected publisher are unaffected.Minuta/TestFlight-mac.xcconfig(untracked; add to.gitignorenext toTestFlight.xcconfig). In a Catalyst archiveMinutaTrayis really built (embedded), and an[sdk=macosx*]condition cannot separate it from the app (both use the macosx SDK). Select profile and entitlements by target name through nested substitution:CODE_SIGN_STYLE = Manual DEVELOPMENT_TEAM = HOLDER_TEAM_ID CODE_SIGN_IDENTITY = Apple Distribution PROFILE_Minuta = tools.minuta.app MacAppStore PROFILE_MinutaWidgets = tools.minuta.app.widgets MacAppStore PROVISIONING_PROFILE_SPECIFIER = $(PROFILE_$(TARGET_NAME)) ENTITLEMENTS_Minuta = TestFlight/Minuta.macOS.entitlements ENTITLEMENTS_MinutaWidgets = TestFlight/MinutaWidgets.macOS.entitlements CODE_SIGN_ENTITLEMENTS = $(ENTITLEMENTS_$(TARGET_NAME)) SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited)For
MinutaTrayboth variables are undefined, so it is signed with Apple Distribution without a profile or entitlements (a bundle inside a sandboxed app needs neither). Check the substitution before archiving:xcodebuild -showBuildSettings -scheme MinutaArchive \ -destination 'generic/platform=macOS,variant=Mac Catalyst' \ -xcconfig TestFlight-mac.xcconfig | grep -E 'PROVISIONING_PROFILE_SPECIFIER|CODE_SIGN_ENTITLEMENTS'Scheme: reuse
MinutaArchive(Tray comes in as a dependency, tests are excluded).Version: keep
MARKETING_VERSION 1.0.1/CURRENT_PROJECT_VERSION 3; build numbers are unique per platform in App Store Connect. Bump to 4 only on “Build already exists”.
Archive, export, upload (after the materials)
Import the Mac Installer
.p12(Keychain Access). Check:security find-identity -v | grep "Mac Installer"shows3rd Party Mac Developer Installer: <holder> (HOLDER_TEAM_ID).Install the profile as
~/Library/Developer/Xcode/UserData/Provisioning Profiles/<UUID>.provisionprofile(UUID viasecurity cms -D | plutil -extract UUID). ConfirmPlatformcontainsOSXand the entitlements listgroup.tools.minuta.apps.Archive:
cd Minuta && xcodebuild clean archive -project Minuta.xcodeproj -scheme MinutaArchive \ -configuration Release -destination 'generic/platform=macOS,variant=Mac Catalyst' \ -archivePath ../build/testflight/Minuta-mac.xcarchive -xcconfig TestFlight-mac.xcconfigBefore exporting:
codesign -d --entitlements - Minuta.appshowsapp-sandbox,files.user-selected.read-write,bookmarks.app-scope,application-groups;Contents/PlugIns/MinutaTray.bundleis present and signed by the holder’s team.ExportOptions-mac.plist:method app-store-connect,destination upload,teamID HOLDER_TEAM_ID,signingStyle manual,signingCertificate Apple Distribution,installerSigningCertificate Mac Installer Distribution,provisioningProfiles { tools.minuta.app: "tools.minuta.app MacAppStore" }.xcodebuild -exportArchive ...untilUpload succeeded. Typical rejections: ITMS-90296 (sandbox) > entitlements above; ITMS-90242 (category) >LSApplicationCategoryTypeis already in Info.plist; “Mac Installer certificate not found” > step 1.App Store Connect > TestFlight > the macOS build > internal group; install through the TestFlight app on the Mac.
Docs and backlog
TESTFLIGHT-ORG.mdsection 8 and the TestFlight guide section 8: rewrite to the real procedure —group.*stays (profile authorization on macOS 15+), the full entitlements list,TestFlight-mac.xcconfigwith theTARGET_NAMEindirection, the archive command,ExportOptionswithinstallerSigningCertificate, the sandbox guard for the CLI pointer. Add a troubleshooting row for the “access data from other apps” prompt (group missing from the profile > recreate the profile).TESTFLIGHT-HOLDER.md: new “Mac” section (steps above).CLAUDE.md: the Mac archive usesMinutaArchive+TestFlight-mac.xcconfig+Minuta/TestFlight/Minuta.macOS.entitlements; a Mac App Store build does not publishstorage-path.txt(sandbox), the CLI falls back to~/Documents/Minutaor--storage.- Known behavior to document: under sandbox the default
~/Documents/Minutais the container’s Documents; Homebrew users moving to the App Store build pick their folder in Settings (bookmarks work). - Follow-up: CLI discovery pointer under App Sandbox — write the pointer into the group container
~/Library/Group Containers/group.tools.minuta.apps/and add that path toStorageResolver(Shared/Sources/MinutaCLI/Runtime/StorageResolver.swift).
Verification
cd Shared && swift testandxcodebuild test -scheme Minuta -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:MinutaTests—StorageLocationManagertests green.- The personal-team Catalyst Debug build still works:
./scripts/run-mac.sh(the Mac entitlements/xcconfig do not touch regular builds; the guard is inert withoutAPP_SANDBOX_CONTAINER_ID). -showBuildSettingswithTestFlight-mac.xcconfig:Minutagets theMacAppStoreprofile andTestFlight/Minuta.macOS.entitlements;MinutaTraygets both empty.- After the materials:
ARCHIVE SUCCEEDED,codesignshows sandbox + group entitlements and the holder’sTeamIdentifier, exportUpload succeeded, the macOS build shows in TestFlight, installs on a Mac, and launches with no “access data from other apps” prompt; choosing a storage folder in Settings persists across relaunch. git status:TestFlight-mac.xcconfigis ignored.