fix(push): capture $push_notification_opened on iOS cold start - #556
fix(push): capture $push_notification_opened on iOS cold start#556turnipdabeets wants to merge 4 commits into
Conversation
posthog-flutter Compliance ReportDate: 2026-09-08 01:53:56 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
Plugin registration runs inside didFinishLaunchingWithOptions, early enough to prewarm the native push-open swizzles before the tap response is delivered — Dart-side setup() lands about 90ms too late. Also wires UNUserNotificationCenter.current().delegate into the example app; without a delegate iOS reports the tap to nobody and no open is capturable at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
…quisite The podspec and Package.swift still allowed 3.70.0/3.71.0, neither of which has prewarmPushNotificationOpenCapture(), so a consumer with a locked Podfile.lock would have hit a compile error rather than the fix. Also names the UNUserNotificationCenter delegate requirement, which is the other half of the reported bug and was previously only fixed in the example. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
It still named 3.70.0 directly above the raised 3.72.0 requirement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
88487df to
f8e8246
Compare
The Dart flag can't reach the prewarm — it runs at plugin registration, before Dart does. Only the plist key opts it out, and that lived solely in a changeset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
Prompt To Fix All With AI### Issue 1
posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift:108
**Prewarm skips supported iOS 13**
The package still supports iOS 13, but this availability check skips the prewarm there. When a notification tap cold-launches an iOS 13 app, the response can still arrive before Dart setup without being buffered, so the advertised cold-start fix remains ineffective on a supported deployment target.
### Issue 2
posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift:86
**Prewarm path lacks coverage**
The new registration-time prewarm and its plist opt-out have no automated regression coverage. Please add a native test seam that verifies registration invokes prewarm when the key is enabled or absent and suppresses it when false; otherwise, removing the early call or reversing the guard could silently reintroduce the cold-start failure.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "docs(push): name the Info.plist opt-out ..." | Re-trigger Greptile |
| private static func prewarmPushNotificationOpenCapture() { | ||
| let capturePushNotificationOpened = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.CAPTURE_PUSH_NOTIFICATION_OPENED") as? Bool ?? true | ||
| guard capturePushNotificationOpened else { return } | ||
| if #available(iOS 14.0, macOS 11.0, *) { |
There was a problem hiding this comment.
Prewarm skips supported iOS 13
The package still supports iOS 13, but this availability check skips the prewarm there. When a notification tap cold-launches an iOS 13 app, the response can still arrive before Dart setup without being buffered, so the advertised cold-start fix remains ineffective on a supported deployment target.
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift
Line: 108
Comment:
**Prewarm skips supported iOS 13**
The package still supports iOS 13, but this availability check skips the prewarm there. When a notification tap cold-launches an iOS 13 app, the response can still arrive before Dart setup without being buffered, so the advertised cold-start fix remains ineffective on a supported deployment target.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| let instance = PosthogFlutterPlugin() | ||
| instance.channel = methodChannel | ||
| PosthogFlutterPlugin.instance = instance | ||
| prewarmPushNotificationOpenCapture() |
There was a problem hiding this comment.
The new registration-time prewarm and its plist opt-out have no automated regression coverage. Please add a native test seam that verifies registration invokes prewarm when the key is enabled or absent and suppresses it when false; otherwise, removing the early call or reversing the guard could silently reintroduce the cold-start failure.
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift
Line: 86
Comment:
**Prewarm path lacks coverage**
The new registration-time prewarm and its plist opt-out have no automated regression coverage. Please add a native test seam that verifies registration invokes prewarm when the key is enabled or absent and suppresses it when false; otherwise, removing the early call or reversing the guard could silently reintroduce the cold-start failure.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
🔗 Related PRs
One fix, five PRs — three SDKs plus the docs. Each Flutter PR is gated on the native release it depends on.
setup()Order: PostHog/posthog-ios#792 and PostHog/posthog-android#753 merge and release first → #556 and #557 leave draft and go green on their own once the floors publish → PostHog/posthog.com#19905 last, since posthog.com deploys on merge.
💡 Motivation and Context
Fixes #555 —
$push_notification_openedis never captured on iOS. Reproduced on a simulator; two independent causes:UNUserNotificationCenterdelegate, so iOS reports the tap to nobody and the native SDK's swizzling has nothing to attach to. (Noteflutter_local_notificationsdoes not set it either — it registers as an application delegate and relies onFlutterAppDelegateforwarding.)didReceivearrives ~150 ms in, about 90 ms before Dart reachesPosthog().setup().⏳ Waiting on the posthog-ios release, not on review. PostHog/posthog-ios#792 merged (
8566372).mainis at 3.71.6 with two pending changesets —minorfor the prewarm API andpatchfor the missing-delegate warning — so the next release is 3.72.0, which is exactly the floor declared here (>= 3.72.0in the podspec,3.72.0 ..< 4.0.0inPackage.swift).Until 3.72.0 is on CocoaPods trunk and tagged for SPM, merging or releasing this fails
pod install/ SPM resolution for every iOS user — a hard build break, not a degradation. Draft until the release lands, then this goes green without a code change.💚 How did you test it?
On device — posthog-flutter example app, iPhone 17 Pro sim / iOS 26.4, built against a local posthog-ios with the native change:
/batchpayload the SDK sent. Before the fix, cold starts captured 0 and warm starts captured 0 for want of a delegate.flutter analyzeclean. The native unit tests live in the posthog-ios PR.Testing
Verified on an iPhone 17 Pro simulator (iOS 26.4) against a local
posthog-iosbuild, with each event read out of the SDK's own/batchpayload:capturePushNotificationOpened: falseAppDelegatewith the delegate line removedRepeated across four rounds — 15
$push_notification_openedevents in total, each carrying$notification_titleand$notification_body.Re-verified 2026-09-08 on an iPhone 17 simulator (iOS 26.4), this branch built against posthog-ios#792:
capturePushNotificationOpened: false: nothing captured — and flipping it back on re-fires the same notification, so the negative is the flag and not a stale build.build-applejobs andAnalyze (swift)die at dependency resolution on the not-yet-publishedPostHog >= 3.72.0floor, so the red checks say nothing about the code itself. It builds clean locally against the merged posthog-ios branch, and the red turns green on its own once 3.72.0 publishes — the same way #557 went from red to 27/27 the moment posthog-android 3.62.0 hit Maven Central.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude Code (session), driven by @turnipdabeets. Nearly all the logic lives in posthog-ios; this side is the trigger plus the example wiring.
Worth a reviewer's attention:
prewarmPushNotificationOpenCapture()is called fromregister(with:)because that runs insidedidFinishLaunchingWithOptions.AUTO_INITwould also win the race (verified), but it is mutually exclusive withpushIdentityProvider, so it isn't a usable answer for everyone.capturePushNotificationOpened: falseisn't known yet. The native SDK releases an unwanted prewarm atsetup(), so this only matters for one add-to-app ordering, documented at the call site. That widens the key's scope beyondAUTO_INITapps, hence the second changeset.AppDelegatechange is the fix for cause (1) and is the copy-pasteable bit for users hitting this.Not done here, deliberately: a README/docs section (the SDK currently ships push with no iOS setup docs at all). The identical cold-start race exists in
@posthog/react-native-pluginand needs a follow-up issue on posthog-js.