Repository navigation
chore!: iOS Major Release 4.0 - #873
turnipdabeets wants to merge 18 commits into
Conversation
There was a problem hiding this comment.
This review was skipped because it would exceed your organization's monthly flex usage limit. Raise the limit in billing settings or wait until the next billing period resets limits.
posthog-ios Compliance ReportDate: 2026-10-23T17:28:47.945070+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 2904ms |
| Format Validation.Event Has Uuid | ✅ | 328ms |
| Format Validation.Event Has Lib Properties | ✅ | 2674ms |
| Format Validation.Distinct Id Is String | ✅ | 2704ms |
| Format Validation.Token Is Present | ✅ | 2737ms |
| Format Validation.Custom Properties Preserved | ✅ | 313ms |
| Format Validation.Event Has Timestamp | ✅ | 2750ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 2702ms |
| Retry Behavior.Retries On 503 | ✅ | 11306ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 4668ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 4829ms |
| Retry Behavior.Respects Retry After Header | ✅ | 5301ms |
| Retry Behavior.Implements Backoff | ✅ | 21746ms |
| Retry Behavior.Retries On 500 | ✅ | 9355ms |
| Retry Behavior.Retries On 502 | ✅ | 5405ms |
| Retry Behavior.Retries On 504 | ✅ | 9369ms |
| Retry Behavior.Max Retries Respected | ❌ | 21235ms |
| Deduplication.Generates Unique Uuids | ✅ | 2782ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 8732ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 16811ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 7700ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 2857ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 2817ms |
| Compression.Sends Gzip When Enabled | ✅ | 2752ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 2805ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 290ms |
| Batch Format.Multiple Events Batched Together | ✅ | 2921ms |
| Error Handling.Does Not Retry On 403 | ✅ | 4786ms |
| Error Handling.Does Not Retry On 413 | ✅ | 4710ms |
| Error Handling.Retries On 408 | ✅ | 8802ms |
Failures
retry_behavior.max_retries_respected
Expected 4 requests, got 6
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 2787ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 2824ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 2756ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 2749ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 2855ms |
| Request Payload.Groups Round Trip | ✅ | 2865ms |
| Request Payload.Groups Default To Empty Object | ✅ | 2789ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 2681ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 2810ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 2767ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 65ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 2799ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 5501ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 2734ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 2708ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 2822ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 2850ms |
6db44c0 to
9393686
Compare
|
[High risk] Removes public API surface for iOS SDK major version. The PR appears safe to merge; no actionable regression was identified. Reviews (1) · Last reviewed commit: "chore!: remove deprecated public APIs fo..." |
e1cc8cf to
5b6c50e
Compare
0901a7b to
2ed8f7f
Compare
2ed8f7f to
0274681
Compare
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 0274681: 1 Must fix. This pull request is large, so the review ran in 4 parts with less depth than usual.
This is review round 6 on this pull request. When findings keep coming back in the same code, the approach is usually the problem. Before the next fix, check whether a different design would remove the whole class of findings.
| .flatMap(UIViewController.getViewControllerTypeName) | ||
| .flatMap { PostHogScreenNameSanitizer.sanitize(rawScreenName: $0) } | ||
|
|
||
| scheduleSettledCapture(window: window, screenName: screenName, postHog: postHog) |
There was a problem hiding this comment.
P1 · Mask the full rendered extent of unclipped images
For a UIImageView with .scaleAspectFill and clipsToBounds = false, mandatory screenshots upload image pixels outside its bounds despite maskAllImages = true. MaskedRegion covers only the view bounds; wireframes omitted the sensitive image entirely. Preserve the privacy invariant in AGENTS.md:12 by expanding masks to the rendered image extent.
There was a problem hiding this comment.
PostHog Review · Standard · reviewed dcd51a5: 2 Must fix. This pull request is large, so the review ran in 4 parts with less depth than usual.
This is review round 7 on this pull request. When findings keep coming back in the same code, the approach is usually the problem. Before the next fix, check whether a different design would remove the whole class of findings.
| .flatMap(UIViewController.getViewControllerTypeName) | ||
| .flatMap { PostHogScreenNameSanitizer.sanitize(rawScreenName: $0) } | ||
|
|
||
| scheduleSettledCapture(window: window, screenName: screenName, postHog: postHog) |
There was a problem hiding this comment.
P1 · Mask attributed image attachments before switching to screenshots
With maskAllTextInputs = false and maskAllImages = true, a UILabel containing an image NSTextAttachment now uploads it unmasked: isLabelSensitive ignores image masking, and attachments have no UIImageView to visit. Wireframes sent only the label's text. Preserve AGENTS.md:12 by masking attributed image attachments before capture.
| .flatMap(UIViewController.getViewControllerTypeName) | ||
| .flatMap { PostHogScreenNameSanitizer.sanitize(rawScreenName: $0) } | ||
|
|
||
| scheduleSettledCapture(window: window, screenName: screenName, postHog: postHog) |
There was a problem hiding this comment.
P1 · Include text shadows in masks before requiring screenshots
With clipsToBounds = false and an offset CALayer text shadow, a UILabel draws readable glyphs outside its bounds. MaskedRegion covers only those bounds, so mandatory screenshots upload the shadow despite maskAllTextInputs = true; wireframes serialized masked text instead. Expand text masks to cover the rendered shadow extent before making screenshot capture unconditional.
dcd51a5 to
0274681
Compare
Removes PostHogConfig.apiKey and the apiKey initializers, propertiesSanitizer and PostHogPropertiesSanitizer, evaluationEnvironments, remoteConfig, PostHogSDK.getFeatureFlagPayload, and the session replay debouncerDelay and maskPhotoLibraryImages options. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…hangeset Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Raise the iOS floor from 13 to 15 (Mac Catalyst follows) and remove the availability checks and fallbacks that only protected iOS 13/14. macOS, tvOS, watchOS and visionOS minimums are unchanged, so checks that also gate those platforms keep their non-iOS clauses. Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Moves postHogSdkName, postHogVersion, PostHogSessionManager, PostHogStorageManager, PostHogConfig.storageManager, PostHogConfig.snapshotEndpoint, dateToMillis and imageToBase64 behind @_spi(PostHogInternal), and makes unused public helpers internal. Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
PostHogDataMode.cellular has always behaved the same as .any. Remove it; callers switch to .any with identical flushing behavior. .wifi and .any keep explicit raw values 0 and 2 so the Objective-C constants and any stored raw values stay the same as in 3.x. Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Collapse Package.swift and [email protected] into a single swift-tools-version 6.2 manifest that keeps Swift 5 language mode, and drop the compiler-version fallbacks that a Swift 6 floor makes dead. CI's minimum-toolchain, Pods-example and CocoaPods publish jobs move to Xcode 26.0.1. Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore!: raise minimum macOS to 11, tvOS to 15 and watchOS to 8 Xcode 26, which the package now requires, supports deployment targets down to macOS 11, tvOS 15 and watchOS 8, so the old 10.15 / 13 / 6 floors could no longer be built or tested. Raise them in Package.swift, the podspec, PostHog.xcodeproj and the downgrade smoke package, and remove the availability checks and fallbacks the new floors make dead. iOS 15, Mac Catalyst and visionOS 1 are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * chore!: raise minimum watchOS to 10 No posthog-ios events came from watchOS 9 or earlier in the last 30 days on US or EU Cloud, and watchOS 10 supports the same hardware as watchOS 9 (Apple Watch Series 4 and later). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
PostHogLogLevel and PostHogLogEntry belong to session replay console capture, but their names read like the Logs product's PostHogLogSeverity and PostHogLogRecord. Rename them to PostHogConsoleLogLevel and PostHogConsoleLogEntry, matching the existing PostHogSessionReplayConsoleLogConfig naming. No behavior change. Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore!: remove the v2 legacy queue migration for v4 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * chore!: stop reading the 2.x event format in queued events Drop the top-level $set and message_id fallbacks in PostHogEvent.fromJSON. They only served events copied unchanged from the 2.x queue, whose import was removed in the previous commit. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: narrow the changeset to events created by 3.x Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore!: drop legacy version and build from lifecycle events Application Installed, Application Updated and Application Opened no longer set `version`/`build`; every event already carries `$app_version` and `$app_build` from the static context. `previous_version` and `previous_build` stay on Application Updated. Matches posthog-react-native 4.0 (#557). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: check the background-open event in the from_background test Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore!: pass the reload result to reloadFeatureFlags' callback reloadFeatureFlags(_:) now hands its callback a PostHogFeatureFlagsLoaded, the same type onFeatureFlags receives, so callers can tell a failed reload from a successful one. errorsLoading is true when the request failed or the SDK isn't set up; flags and variants then hold the last known values. Part of #557. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: snapshot a failed reload's last known flags once for all callbacks Coalesced reload callbacks each read the live cache on failure, so a reset() in one callback emptied the last known flags reported to the next. The load now captures its result once, before fan-out. Also updates the setPersonPropertiesForFlags example and note for the new callback argument. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: gate the first failed reload so the reset regression always coalesces The coalesced-failure + reset() test assumed the first reload was still in flight when the next two registered. Hold its response until both callbacks are pending, and check both shared one request. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * chore!: mark off-main public callbacks @sendable for v4 (#914) * chore!: mark off-main public callbacks @sendable for v4 The reloadFeatureFlags callback, beforeSend blocks, getAnonymousId, pushIdentityProvider and logSanitizer run off the main thread but weren't @sendable. In Swift 6 apps a reloadFeatureFlags closure written in main-actor code traps at runtime; the other callbacks race silently. Also make PostHogFeatureFlagsLoaded Sendable and add a Swift 6 language-mode test target that calls the API as an app would. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * chore: match the public API snapshot to CI's Xcode 26.3 output Xcode 26.3's symbol graph leaves @sendable out of declaration fragments, which 26.4 prints; the mangled names still differ. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: store bridged flag values in PostHogFeatureFlagsLoaded The init kept the original variant values after filtering, so a mutable NSString passed via bootstrap stayed mutable inside a Sendable payload. Store the `as? Bool`/`as? String` results instead. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: clarify the Sendable callbacks changeset Name the config properties unambiguously and warn that array conversions crash in both directions. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore!: return nil for malformed feature flag payloads Follow the sdk-specs payload decoding contract: a serialized payload that fails to decode (malformed, empty or whitespace-only) now reads as nil and is logged, instead of leaking the raw string. The flag's value, enabled state and sibling payloads are unaffected. Bootstrapped payloads are already decoded, so they are now serialized when seeded into the payload cache. Reads decode each payload exactly once, so a bootstrapped "123" or "true" string stays a String instead of becoming a number or Bool. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: cover bootstrapped string payloads read back after a restart Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: clear leftover queue files before each ignoredExceptionTypes capture test storage.reset() keeps the event queue, and getBatchedEvents returns once the mock receives a batch, before the SDK deletes the sent file. The previous test's $exception could survive on disk and be resent by the next SDK, failing the "drops ignored types" assertion on the iOS simulator job. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore: replace vendored Reachability with NWPathMonitor Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: match the old reachability notifications Notify on every path update and report the current connection when the notifier starts, as the SCNetworkReachability version did. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: cap the first-path wait and test the reachability notifier Wait for the first path at most 100 ms after init in total, not 100 ms per read. Add tests for the start/update/stop notification rules. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: report reachability from the connection at delivery A start notification queued before a newer path could arrive last and pause the queues while online. Also narrow the changeset to what is actually preserved and list the edge-state changes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: cover unknown and known network state in the context test Seed the context's reachability instead of relying on a live NWPathMonitor delivering its first path within 100 ms, and assert that network properties are omitted until a path arrives. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: drive queue reachability tests with update(_:) and cover data modes The logs and replay queue reachability tests used a live NWPathMonitor, so a real path update could race the simulated offline event. Drive them through update(_:) instead, and add tests for .wifi waiting for a Wi-Fi path and .any sending before the first path. Mark the changeset major like the rest of v4. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* feat: add SessionReplayWebP and CrashReporting package traits SwiftPM apps can now leave out the vendored libwebp and PLCrashReporter by disabling the traits. Both are on by default, so existing setups are unchanged; CocoaPods and xcodeproj builds always include both. Also moves the ignoredExceptionTypes matcher out of the crash-reporting gate so it applies to manual captures everywhere, which fixes it on watchOS and visionOS. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: gate WebP suites on the SessionReplayWebP trait The WebP test suites called `UIImage.webpData`, which SwiftPM compiles out on iOS when the trait is off, so the test target failed to build. Gate them like the encoder and add a separate encoding test that expects WebP or the JPEG fallback depending on the trait. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * ci: compile the iOS test target with package traits off `make test` runs on macOS, where the iOS-only trait gates compile out, so a test calling trait-gated SDK code on iOS went unnoticed. Build the iOS test target for the simulator with traits off and with each trait on its own. The concurrent flag reload suite's `.timeLimit` needs iOS 16, so limit it to macOS SwiftPM builds rather than every SwiftPM build. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * feat!: replace the SessionReplayWebP trait with SessionReplay SessionReplay now gates the whole replay engine along with libwebp, so an app that doesn't use replay can leave all of it out. The public replay API (config, masking modifiers, URLSession helpers, start/stop recording) stays compiled; with the trait off the replay integration is a stub that is skipped with `disabledByPackageTrait`. A finer split (replay without WebP) can come later in a minor release by keeping SessionReplay as an umbrella trait that enables the new ones. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: gate the remaining replay tests on the SessionReplay trait The session replay suite and the two SDK tests that assert replay debug properties need the replay engine, so skip them in SPM builds with the SessionReplay trait off. Also list buildTestsSpmTraitsIOS for trait-gated changes in the contributor guide. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: limit the Swift 6 callback suite's timeLimit to macOS `.timeLimit` needs iOS 16, above the iOS 15 minimum, so the iOS SwiftPM test build failed after merging the 4.0 base. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * feat: add Surveys package trait (#918) * feat: add Surveys package trait SwiftPM apps that don't use surveys can now leave out the survey engine and UI by disabling the `Surveys` trait. It is on by default, so existing setups are unchanged; CocoaPods and xcodeproj builds always include surveys. The public survey API (`config.surveys`, `surveysConfig`, the delegate protocol and display models) stays compiled. With the trait off, the survey integration is a stub that is skipped with `disabledByPackageTrait`. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: note the Surveys trait on surveysConfig and surveysDelegate A custom surveysDelegate is never called when the trait is off, so say so where apps set it. Also drop the unused clearInstalls() from the stub. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* chore!: remove wireframe session replay capture Session replay on iOS now always captures masked screenshots. Wireframe mode skipped SwiftUI-rooted windows entirely and leaked ph-no-capture containers (sdk-specs n26). `screenshotMode` is kept as a deprecated no-op so wrappers that set it keep compiling. The replay meta href is now resolved from the visible screen on every capture. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: keep secure text fields masked under ph-no-mask Wireframe masked secure fields regardless of ph-no-mask; screenshot capture stopped the walk at the unmasked ancestor. Now that screenshots are the only mode, keep secure-entry inputs masked in that subtree, matching Android's password handling and the session-replay-privacy spec. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: mask full-window no-capture leaves and secure fields under a no-mask cover ancestor A full-window view tagged ph-no-capture with no subviews now contributes its own mask instead of relying on descendants it doesn't have. When a ph-no-mask ancestor sits above a selected presentation cover, secure-entry fields inside the cover are still masked. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix(replay): keep view controller titles out of the replay href A plain UIViewController's title is display text and can contain user data, so the replay meta href now uses the controller's type name only. $screen naming still falls back to the title. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Screenshots are always captured on the main thread with the settle-then-shoot path. Removes the opt-in off-main render path, its sweptRects mask helper and the tests that only covered it. Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
NetworkSample is compiled out when the SessionReplay package trait is off. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
0274681 to
ce9e3ac
Compare
There was a problem hiding this comment.
PostHog Review · Standard · reviewed ce9e3ac: 1 Must fix. This pull request is large, so the review ran in 4 parts with less depth than usual.
This is review round 8 on this pull request. When findings keep coming back in the same code, the approach is usually the problem. Before the next fix, check whether a different design would remove the whole class of findings.
| .flatMap(UIViewController.getViewControllerTypeName) | ||
| .flatMap { PostHogScreenNameSanitizer.sanitize(rawScreenName: $0) } | ||
|
|
||
| scheduleSettledCapture(window: window, screenName: screenName, postHog: postHog) |
There was a problem hiding this comment.
P1 · Traverse masked controls' children before requiring screenshots
When an unclipped UIButton positions its title outside its bounds with titleEdgeInsets, mandatory screenshots upload that text despite maskAllTextInputs = true. findMaskableWidgets masks only the button and returns before visiting its title label; the removed wireframe path recursively masked that label. Continue traversing masked controls' descendants.
💡 Motivation and Context
Closes #557
The 4.0 major release branch. Its first change removes every public API that is already marked deprecated; the other 4.0 changes are listed under "Also on this branch".
PostHogConfig.apiKey,init(apiKey:),init(apiKey:host:)projectToken,init(projectToken:),init(projectToken:host:)PostHogConfig.propertiesSanitizer,PostHogPropertiesSanitizerPostHogConfig.setBeforeSend(_:)PostHogConfig.evaluationEnvironmentsevaluationContextsPostHogConfig.remoteConfigPostHogSDK.getFeatureFlagPayload(_:)getFeatureFlagResult(_:sendFeatureFlagEvent:)?.payloadPostHogSessionReplayConfig.debouncerDelaythrottleDelayPostHogSessionReplayConfig.maskPhotoLibraryImagesPublic API change: this is a breaking removal, see the
api/posthog-ios.public-api.txtdiff.Left in place on purpose:
PostHog/Utils/Reachability.swift, which is internal vendored code.PostHogConfig(apiKey:)inscripts/downgrade-compatibility-smoke, which compiles against 3.48.0.PostHogEvent.apiKeyandPostHogRemoteConfig.getFeatureFlagPayload.migrateLegacyStorage(added in 3.20) andmergeLegacyContainerIfNeeded(the app-group merge, added in 3.27). Both run on every init, but removing them would cost data. If someone updates an app straight from an older version to 4.x, their users lose their distinct ID and any queued events. In the last 7 days (US), about 8% of iOS SDK end-users were on an app build older than 3.27, and about 6% of apps had not shipped 3.27 or later. The app-group merge only affects apps that setappGroupIdentifier, so its share is smaller. We'll revisit in a later major.Wrapper SDKs: posthog-flutter called
getFeatureFlagPayload; PostHog/posthog-flutter#630 moves it off. The React Native plugin calls none of the removed APIs. Both pin posthog-ios below 4.0.Also on this branch
This branch is the 4.0 integration branch. Each item below was reviewed and merged as its own PR, and each has its own changeset.
swift-tools-version: 6.2, with[email protected]removed (chore!: require Xcode 26 (swift-tools-version 6.2) #898).SessionReplayandCrashReportingSwiftPM traits (feat: add SessionReplay and CrashReporting package traits #916).PostHogDataMode.cellular(chore!: remove PostHogDataMode.cellular for v4 #890)PostHogConsoleLog*(chore!: rename console log types to PostHogConsoleLog* for v4 #912)reloadFeatureFlagscallback receives the reload result (chore!: pass the reload result to reloadFeatureFlags' callback #913)nil(chore!: return nil for malformed feature flag payloads #903)screenshotModeBackgroundCapture(chore!: remove experimental screenshotModeBackgroundCapture #937)PostHogSDK.didReceiveFeatureFlagsnotification: useonFeatureFlags(_:), which reports the flags and load errors per SDK instance. posthog-flutter moves to the listener in chore!: adopt posthog-ios 4.0 posthog-flutter#649$setare dropped; 3.x migrations are kept (chore!: remove the v2 legacy queue migration for v4 #875).NWPathMonitor(chore!: replace vendored Reachability with NWPathMonitor #909).macos-15with Xcode 26.0.1.buildSdkSpmNoTraits,testNoTraits).Wrapper SDK releases
posthog-ios 4.0 ships with a major release of each wrapper that embeds it. Each adoption PR waits until 4.0 is published.
💚 How did you test it?
make testpasses locally (909 tests, macOS).make apiUpdate.make buildfor the other platforms and example apps, and the iOS simulator test job.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset fileThe changeset is
majorand was written by hand in the changeset format. The release workflow only runs onmain, so nothing publishes untilrelease/4.0merges there.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
@available(*, deprecated), plus the code, tests and comments that only existed to support them.🤖 Generated with Claude Code