Add sandboxed Mac App Store build variant - #56
Conversation
|
Copilot resolve the merge conflicts in this pull request |
There was a problem hiding this comment.
Pull request overview
Adds a Mac App Store–style sandboxed build variant to the ZoomIt for Mac codebase, including conditional compilation to remove non-App Store functionality and new security-scoped bookmark handling for user-selected resources.
Changes:
- Introduces
ZOOMIT_APP_STOREbuild flag andZOOMIT_DISTRIBUTIONbuild variant selection (Homebrew vs App Store) with dedicated entitlements and build-number stamping. - Adds security-scoped bookmark persistence/access for user-selected break sound/background and snip save directory, plumbing access through capture/export and break-timer paths.
- Compiles out DemoType UI, hotkeys, and self-tests for the App Store surface; adds a self-test validating distribution-specific Settings tabs.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/ZoomItMacCore/Settings/UserSelectedResourceAccess.swift | Adds bookmark persistence + withAccess wrapper for security-scoped resource access. |
| Sources/ZoomItMacCore/Settings/SettingsWindowController.swift | Saves bookmarks on user selection; conditionally removes DemoType settings surface for App Store builds. |
| Sources/ZoomItMacCore/SelfTest/SelfTestRunner.swift | Skips DemoType tests in App Store builds; adds tab-surface self-test. |
| Sources/ZoomItMacCore/Overlay/ZoomCanvasView.swift | Routes snip directory writes through updated exporter with bookmark-based access. |
| Sources/ZoomItMacCore/Overlay/OverlayWindowController.swift | Injects UserSelectedResourceAccess into overlay/canvas creation. |
| Sources/ZoomItMacCore/Overlay/BreakTimerController.swift | Loads background/sound via security-scoped access; adds break-timer error for access failures. |
| Sources/ZoomItMacCore/Hotkeys/HotkeyService.swift | Compiles out DemoType hotkeys/commands for App Store builds. |
| Sources/ZoomItMacCore/Core/ModeCoordinator.swift | Plumbs UserSelectedResourceAccess; compiles out DemoType controller/commands for App Store builds. |
| Sources/ZoomItMacCore/Core/AppCommand.swift | Compiles out DemoType commands for App Store builds. |
| Sources/ZoomItMacCore/Capture/SnipController.swift | Passes UserSelectedResourceAccess into image export pipeline. |
| Sources/ZoomItMacCore/Capture/PanoramaController.swift | Passes UserSelectedResourceAccess into image export pipeline. |
| Sources/ZoomItMacCore/Capture/ImageExporter.swift | Adds bookmark-aware directory writes and Boolean success signaling with fallback to Save panel. |
| Sources/ZoomItMacCore/App/DistributionChannel.swift | Adds compile-time DistributionChannel.isAppStore flag. |
| Sources/ZoomItMacCore/App/DemoTypeController.swift | Compiles out DemoType implementation for App Store builds. |
| Sources/ZoomItMacCore/App/AppDelegate.swift | Creates and injects UserSelectedResourceAccess; updates overlay/mode wiring. |
| Sources/ZoomItMacCore/App/AppController.swift | Passes UserSelectedResourceAccess into Settings window wiring. |
| Scripts/ZoomIt-AppStore.entitlements | Adds sandbox + bookmarks + mic/camera entitlements for App Store variant. |
| Scripts/build-app.sh | Adds distribution selection, compiler flags, entitlements switching, and independent build-number stamping. |
| README.md | Documents App Store compiler-condition self-tests and build variants/usage. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
# Conflicts: # Sources/ZoomItMacCore/Core/ModeCoordinator.swift # Sources/ZoomItMacCore/Hotkeys/HotkeyService.swift # Sources/ZoomItMacCore/SelfTest/SelfTestRunner.swift # Sources/ZoomItMacCore/Settings/SettingsWindowController.swift Co-authored-by: MarioHewardt <15442480+MarioHewardt@users.noreply.github.com>
Co-authored-by: MarioHewardt <15442480+MarioHewardt@users.noreply.github.com>
Co-authored-by: MarioHewardt <15442480+MarioHewardt@users.noreply.github.com>
Co-authored-by: MarioHewardt <15442480+MarioHewardt@users.noreply.github.com>
Resolved the merge conflicts with |
| try userSelectedResourceAccess.withAccess( | ||
| to: .breakSound, | ||
| legacyPath: settings.breakSoundFile | ||
| ) { url in | ||
| NSSound(contentsOf: url, byReference: false)?.play() |
There was a problem hiding this comment.
BreakTimerView now takes a userSelectedResourceAccess: UserSelectedResourceAccess in its initializer, and the controller passes its own instance when constructing the view (commit 84c5899). Note: the push to marioh/store was rejected by repository ruleset protection ("Cannot update this protected ref"), so the commit exists locally but could not be pushed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Sources/ZoomItMacCore/Capture/DemoMirrorController.swift:468
MainActor.assumeIsolatedin theTimercallback can trap at runtime if the timer ever fires off the main actor (e.g. if scheduled on a non-main run loop). You can avoid the runtime precondition by moving theselfaccess entirely into aTask { @MainActor … }, which is safe regardless of which thread the timer callback runs on.
trackingTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
MainActor.assumeIsolated {
guard let self else { return }
Task { @MainActor in
await self.refreshTrackedWindow(source: source)
Co-authored-by: MarioHewardt <15442480+MarioHewardt@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Sources/ZoomItMacCore/Capture/ImageExporter.swift:75
- In the App Store build,
snipSaveDirectory == ""(meaning “Documents”) will always lack a security-scoped bookmark.writeToDirectorycurrently turns that into an error alert, then callers fall back to showing a Save panel anyway—so users will see a confusing alert before the expected Save dialog when they enable “save to directory” without explicitly choosing a folder. It would be better to treat an empty directory as “no configured directory” for App Store builds and immediately returnfalseto trigger the Save panel without an alert.
@discardableResult
static func writeToDirectory(
_ image: CGImage,
directoryPath: String,
userSelectedResourceAccess: UserSelectedResourceAccess
Sources/ZoomItMacCore/Settings/UserSelectedResourceAccess.swift:84
- On non‑App Store builds, the generic
catchblock swallows any error from the bookmark path and then falls back to the legacy path. This can causeoperationto be executed twice on failures (bookmark URL first, legacy URL second), which is risky for non-idempotent operations like writing files.
} catch {
if DistributionChannel.isAppStore {
throw error
}
}
No description provided.