Skip to content

feat: [SDK-5032] enable Mac Catalyst demo builds - #1716

Open
fadi-george wants to merge 4 commits into
fadi/sdk-5022from
fadi/sdk-5032
Open

feat: [SDK-5032] enable Mac Catalyst demo builds#1716
fadi-george wants to merge 4 commits into
fadi/sdk-5022from
fadi/sdk-5032

Conversation

@fadi-george

Copy link
Copy Markdown
Collaborator

Description

One Line Summary

Enables the SwiftUI demo app and its extensions to build and run with Mac Catalyst.

Details

Motivation

A runnable Catalyst host is needed to validate the SDK's KMP Catalyst framework slice and platform behavior end to end.

Scope

Adds Catalyst project support, excludes unsupported Live Activity behavior, provides a Catalyst widget placeholder, and makes resilient storage tolerate a missing App Group directory.

Other

Stacked on #1715.

Local development builds may show the macOS App Group authorization prompt when their provisioning profile does not authorize the Catalyst group. Properly provisioned App Store and TestFlight builds are unaffected.

Testing

Unit testing

Added resilient-storage coverage for creating a missing container before an atomic write.

Manual testing

Built the demo and SDK frameworks for both iOS Simulator and Mac Catalyst. Verified the Catalyst demo launch, crash upload flow, storage behavior, and Live Activities no-op behavior.

Affected code checklist

  • Notifications
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all required sections above
  • PR does one thing
  • Any public API changes are explained above

Testing

  • I have included test coverage for these changes
  • Applicable automated tests pass
  • I have manually tested iOS and Catalyst builds

Final pass

  • Code is as readable as possible
  • I have reviewed this PR

Made with Cursor

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multimodal adversarial review (interrogate)

Verdict: Do not merge for Catalyst E2E validation until the App Group identity mismatch is fixed. The storage createDirectory change treats a misconfigured group name as a missing directory.

Intent

Enable the SwiftUI demo (and extensions) to build/run under Mac Catalyst so the SDK’s KMP Catalyst slice and platform behavior can be validated end-to-end: Catalyst project settings, Live Activities stubs/exclusions, widget placeholder, and resilient storage creating a missing container before atomic writes. Stacked on #1715.

Reviewers

  • A: claude-fable-5-thinking-xhigh — 6 findings
  • B: gpt-5.6-sol-xhigh — 2 findings
  • C: cursor-grok-4.5-high-fast — 4 findings
  • D: claude-opus-5-thinking-high — 8 findings

Act On

  1. DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER breaks App Group naming (A/B/C/D — critical). Demo has no OneSignal_app_groups_key. SDK falls back to group.<bundleId>.onesignal. Catalyst bundle becomes maccatalyst.com.onesignal.example → computed group group.maccatalyst.com.onesignal.example.onesignal, but entitlements only grant group.com.onesignal.example.onesignal. Shared storage / NSE co-access / the PR’s own App Group prompt note all follow from this. Fix: pin OneSignal_app_groups_key on App+NSE, or disable derive so the bundle id stays com.onesignal.example.
  2. App Group prepare failure silently rehomes to Application Support (A/C/D). New do/catch falls through after a non-nil container URL. That severs the app↔NSE single-file contract and can make Catalyst “validation” look green on private storage. Keep using the group URL / fail closed once a container URL was selected.
  3. Demo Live Activities UI not actually stubbed (A/B/C/D). LiveActivitySection still mounts; start no-ops with no feedback; update/end still POST REST for activities that cannot start locally. Hide/disable the section on Catalyst (or gate all controller ops + show unavailable copy).

Consider

  • OneSignal.m Catalyst #if is largely redundant with the LiveActivities module already blanking sources; stub still logs “add the module” on Catalyst (A/D).
  • Widget placeholder vs excluding the widget target from Catalyst (A).
  • New test mostly asserts Foundation createDirectory+atomic write; misses prepare-failure / fallback behavior (A/C/D).
  • createDirectory on every fileURL() read; directory lacks NSFileProtectionNone while the file is carefully unprotected (D).
  • No CI macOS,variant=Mac Catalyst build of the demo (D).

Noted

  • Inconsistent #if around import OneSignalLiveActivities vs unguarded import in App.swift (D).
  • TEMP_* UUID churn in pbxproj from xcodegen (noise).

Dismissed

  • Dropping createDirectory entirely once group naming is fixed — still useful for a legitimately missing entitled container; keep it, but stop using it to paper over the wrong group / silent private fallback.
  • Claiming LiveActivities framework cannot link on Catalyst — module already compiles empty under targetEnvironment(macCatalyst); linking is intentional empty.

Agreement Map

All four models independently hit the derive→App Group mismatch and incomplete demo LA stubbing. Three flagged silent Application Support fallback and weak test coverage. Divergence is mostly on whether OneSignal.m guards / widget placeholder / file-protection / CI belong in Act On vs Consider — lead kept Act On to the three merge-blocking items above.

Open in Web View Automation 

Sent by Cursor Automation: Automatic PR Review

Comment thread examples/demo/project.yml
Comment on lines +21 to +22
SUPPORTS_MACCATALYST: YES
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER: YES

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Act on (A/B/C/D): With DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES and no OneSignal_app_groups_key in Info.plist, OneSignalUserDefaults.appGroupName() becomes group.maccatalyst.com.onesignal.example.onesignal, but entitlements only declare group.com.onesignal.example.onesignal.

That breaks the shared App Group / NSE path this PR is meant to validate, and matches the “macOS App Group authorization prompt” note in the PR body.

Pin OneSignal_app_groups_key on App + NSE, or set derive to NO so the Catalyst bundle id stays com.onesignal.example.

Comment on lines +65 to +73
if let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupName) {
do {
return try preparedFileURL(in: container, fileManager: fileManager)
} catch {
OneSignalLog.onesignalLog(
.LL_WARN,
message: "OSResilientStorage could not prepare the App Group container: \(error)"
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Act on (A/C/D): After a non-nil App Group containerURL, prepare failure now warns and falls through to Application Support.

Previously the group URL was always returned and a write failure stayed on that path. Silent private rehoming breaks the documented app↔NSE single-file contract and can make Catalyst storage checks look green without touching the entitled group.

Once a container URL is selected, fail closed (return nil / keep the group URL) instead of switching backing stores mid-flight.

Comment on lines 92 to +106
@@ -98,6 +103,7 @@ enum LiveActivityController {
attributes: attributes,
content: content
)
#endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Act on (A/B/C/D): Only setup/start are compiled out. LiveActivitySection still mounts on Catalyst (ContentView), Start silently no-ops, and update/end below still hit the Live Activities REST API for an activity that cannot exist locally.

Hide/disable the section on Catalyst (mirror the widget’s “unavailable” copy), and gate all controller operations consistently.

Comment on lines +210 to +211
#if TARGET_OS_MACCATALYST
return [OSStubLiveActivities liveActivities];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider (A/D): OneSignalLiveActivities sources are already blanked under #if targetEnvironment(macCatalyst), so NSClassFromString already misses and the pre-existing else path already returned the stub. These #ifs mainly suppress the facade logs; OSStubLiveActivities still ERROR-logs “module must be added,” which is the wrong diagnosis on Catalyst.

Prefer one platform-aware stub/message owned by the LiveActivities module rather than a third dispatch path in OneSignal.m.

Comment on lines +67 to +76
func testPreparedFileURL_createsMissingContainerForAtomicWrites() throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
let container = root.appendingPathComponent("group.example", isDirectory: true)
defer { try? FileManager.default.removeItem(at: root) }

let fileURL = try OSResilientStorage.preparedFileURL(in: container)
try Data("value".utf8).write(to: fileURL, options: .atomic)

XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider (A/C/D): This asserts FileManager.createDirectory + atomic Data.write on a temp path. It does not cover the new risky fileURL() behavior (prepare-failure fallthrough / group vs Application Support choice), which is what motivated the production change.

Cover prepare-failure semantics (no private fallback once a group URL was selected), or drop the helper-only assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant