Skip to content

chore!: remove the public API of disabled package traits - #975

Merged
turnipdabeets merged 2 commits into
chore/v4-remove-deprecatedfrom
chore/v4-trait-gate-public-api
Oct 27, 2026
Merged

turnipdabeets merged 2 commits into
chore/v4-remove-deprecatedfrom
chore/v4-trait-gate-public-api

Conversation

@turnipdabeets

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Addresses ioannisj's review on #873 (#873): the SessionReplay, CrashReporting and Surveys package traits gated the implementation but not the public API. With a trait off, an app could set sessionReplay = true or errorTrackingConfig.autoCapture = true, compile cleanly, and get nothing but a debug-level .skipped(.disabledByPackageTrait) log.

Public API that only works with a trait now uses the same !SWIFT_PACKAGE || <Trait> condition as its implementation. With the trait off, that code fails to compile instead. CocoaPods, XCFramework and Xcode-project builds (!SWIFT_PACKAGE) and SPM with default traits keep everything, so the default API snapshot is unchanged.

Gated symbols

Trait Removed when the trait is off
SessionReplay PostHogConfig.sessionReplay, PostHogConfig.sessionReplayConfig, PostHogSessionReplayConfig, PostHogSessionReplayConsoleLogConfig, PostHogConsoleLogEntry, PostHogConsoleLogLevel, PostHogSDK.startSessionRecording(), startSessionRecording(resumeCurrent:), stopSessionRecording(), isSessionReplayActive(), View.postHogNoMask(), and the wrapper SPI captureSessionReplaySnapshot(episodeFirstFrame:) / sessionReplayDebugProperties()
CrashReporting PostHogErrorTrackingConfig.autoCapture
Surveys PostHogConfig.surveys, PostHogConfig.surveysConfig, PostHogSurveysConfig, PostHogSurveysDelegate, OnPostHogSurveyShown / OnPostHogSurveyResponse / OnPostHogSurveyClosed, PostHogDisplaySurvey and its question/appearance/enum types, PostHogSurveyResponse / PostHogSurveyResponseType, PostHogNextSurveyQuestion

Judgment calls:

  • errorTrackingConfig.autoCapture is gated, the rest of PostHogErrorTrackingConfig is not. Error tracking isn't a trait: captureException, inAppIncludes/inAppExcludes/inAppByDefault, ignoredExceptionTypes, exceptionSteps and addExceptionStep all apply to manual captures. autoCapture only installs PostHogErrorTrackingAutoCaptureIntegration. That integration is the PLCrashReporter handler, and it also holds the MetricKit OOM reporter, so with CrashReporting off autoCapture can't do anything.
  • postHogMask() stays public; postHogNoMask() is gated. postHogMask() regions also keep SwiftUI tap autocapture out of masked areas (SwiftUITapAutocapture reads PostHogSessionReplayMaskRegistry), so it still does something with replay off. postHogNoMask() only affects replay. Its internal UIView/CALayer flag helpers stay because UIView.isNoMask() is compiled with replay off.
  • Wrapper SPI. captureSessionReplaySnapshot and sessionReplayDebugProperties sit in the same block as start/stopSessionRecording(), so they're gated too. Wrappers built with default traits or CocoaPods are unaffected.
  • Survey types aren't restricted to iOS. They were already public on every platform; this PR only adds the trait gate and doesn't change platform availability.

Internal follow-through: the SDK's replay and survey plumbing (replayIntegration, startSessionRecordingOnOptIn, installReplayIntegration(), surveysIntegration, getIntegrations()) is gated the same way. That let me delete the trait-off PostHogSurveyIntegration stub. The trait-off PostHogReplayIntegration is now an empty enum, kept only as the namespace for the MaskedRegion helpers that postHogMask() uses. The two tests that checked .skipped(.disabledByPackageTrait) with a trait off are removed, because that path can no longer be reached from public API. The compile-time removal is covered by the new snapshot.

Public API snapshot with traits off

  • scripts/check-public-api.sh --no-traits builds the iOS SDK with swift build --disable-default-traits and extracts its symbol graph into api/posthog-ios.public-api.no-traits.txt, using the same extraction and format as the existing snapshot.
  • make apiCheck now checks both snapshots and make apiUpdate regenerates both, so the existing Public API snapshot CI job (make apiCheck) fails if either one drifts. Only the step name changed. CONTRIBUTING.md documents the new file.
  • The default snapshot comes from the Xcode project and the no-traits one from SPM. Because of that, a few declarations render differently between the two files, unrelated to traits: @Sendable closure types, and the extra top-level PostHogDataMode mirror in the Xcode build. Every other difference between the files is a symbol from the table above.

Spec

sdk-specs has nothing on SwiftPM package traits. This changes compile-time availability in SPM builds, not runtime behavior.

💚 How did you test it?

Ran locally on Xcode 26.4.1. The machine was heavily loaded by parallel builds (load average 140–300).

Command Result
make lint Passed (no new violations)
make buildSdkSpmNoTraits Passed (macOS + iOS, traits off)
make buildTestsSpmTraitsIOS Passed (iOS tests with traits off, and with each trait on alone)
make buildSdkSpm Passed
scripts/check-public-api.sh --update --no-traits Generated the new snapshot
make test 1153 tests. Failures in PostHogStorageTest app-group tests (sandbox denies writes to the group container), PostHogLogsQueueTest, PostHogSDKTest identify tests and a PostHogPropertiesSerializationTest timeout. PostHogSDKTest and PostHogPropertiesSerializationTest passed when rerun alone. PostHogLogsQueueTest timing tests fail on the base branch too under the same load.
make testNoTraits 981 tests. Same app-group failures, plus a logs-queue timing test and a push #746 test. PostHogPushNotificationTest passed when rerun alone.
make buildSdk, make apiCheck (default snapshot), make testOniOSSimulator Not run. xcpretty isn't installed on this machine, and xcodebuild can't resolve the example projects' ../posthog-ios package reference from the agent worktree's directory name. CI covers these. Note that the default snapshot only comes from !SWIFT_PACKAGE builds, where every new gate is true.

📝 Checklist

  • All commits are signed (see Commit signing)
  • I reviewed the submitted code.
  • I added tests to verify the changes. (No new test cases. The traits-off API snapshot check is the guard.)
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

No new changeset file. I added a sub-bullet to the existing trait entry under Added in .changeset/v4-major-release.md. Traits are new in 4.0, so this isn't a break against 3.x.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Written by Claude Code (Opus) as one of several parallel follow-ups to chore!: iOS Major Release 4.0 #873, branched from chore/v4-remove-deprecated.
  • How the gated list was built: I started from api/posthog-ios.public-api.txt and checked each candidate's readers. That check is why postHogMask() (autocapture reads it) and the non-crash PostHogErrorTrackingConfig options (manual captureException uses them) stay public.
  • I considered keeping the integration stubs and gating only the public entry points. I gated the internal plumbing instead so no dead stub paths are left. The replay stub can't be removed outright because the mask-region helpers are extensions on it.
  • I considered a separate CI job for the no-traits snapshot. I folded it into make apiCheck instead, so the existing required check covers it and the local workflow is unchanged.

🤖 Generated with Claude Code

Gate trait-specific public API with the same `!SWIFT_PACKAGE || <Trait>`
condition as its implementation, so an SPM app that turns a trait off gets
a compile error instead of an option that silently does nothing.

- SessionReplay: PostHogConfig.sessionReplay/sessionReplayConfig,
  PostHogSessionReplayConfig, PostHogSessionReplayConsoleLogConfig,
  PostHogConsoleLogEntry/Level, start/stopSessionRecording(),
  isSessionReplayActive(), View.postHogNoMask() and the replay SPI.
- CrashReporting: PostHogErrorTrackingConfig.autoCapture.
- Surveys: PostHogConfig.surveys/surveysConfig, PostHogSurveysConfig,
  PostHogSurveysDelegate and its callback typealiases, the display,
  response and next-question types.

Add api/posthog-ios.public-api.no-traits.txt (SPM build with
--disable-default-traits), checked by `make apiCheck` in CI and
regenerated by `make apiUpdate`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@turnipdabeets turnipdabeets self-assigned this Oct 27, 2026
@turnipdabeets turnipdabeets mentioned this pull request Oct 27, 2026
2 of 15 tasks

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PostHog Review · Standard · reviewed 8e6ef7c: no findings. This pull request is large, so the review ran in 4 parts with less depth than usual.

@turnipdabeets
turnipdabeets marked this pull request as ready for review October 27, 2026 17:20
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 27, 2026 17:20
Xcode 26.4.1 prints @sendable on five closure types in the SPM symbol graph; CI's Xcode 26.3 doesn't, so apiCheck failed.
@github-actions

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-10-27T17:40:08.266622+00:00
Duration: 232541ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 2789ms
Format Validation.Event Has Uuid ✅ 2786ms
Format Validation.Event Has Lib Properties ✅ 2842ms
Format Validation.Distinct Id Is String ✅ 507ms
Format Validation.Token Is Present ✅ 377ms
Format Validation.Custom Properties Preserved ✅ 377ms
Format Validation.Event Has Timestamp ✅ 3012ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 2829ms
Retry Behavior.Retries On 503 ✅ 11412ms
Retry Behavior.Does Not Retry On 400 ✅ 4829ms
Retry Behavior.Does Not Retry On 401 ✅ 4865ms
Retry Behavior.Respects Retry After Header ✅ 7937ms
Retry Behavior.Implements Backoff ✅ 21806ms
Retry Behavior.Retries On 500 ✅ 9365ms
Retry Behavior.Retries On 502 ✅ 9350ms
Retry Behavior.Retries On 504 ✅ 9403ms
Retry Behavior.Max Retries Respected ❌ 21892ms
Deduplication.Generates Unique Uuids ✅ 682ms
Deduplication.Preserves Uuid On Retry ✅ 9261ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 16335ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7924ms
Deduplication.No Duplicate Events In Batch ✅ 2858ms
Deduplication.Different Events Have Different Uuids ✅ 2930ms
Compression.Sends Gzip When Enabled ✅ 2806ms
Batch Format.Uses Proper Batch Structure ✅ 489ms
Batch Format.Flush With No Events Sends Nothing ✅ 299ms
Batch Format.Multiple Events Batched Together ✅ 3084ms
Error Handling.Does Not Retry On 403 ✅ 4879ms
Error Handling.Does Not Retry On 413 ✅ 4813ms
Error Handling.Retries On 408 ✅ 9190ms

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 ✅ 2837ms
Request Payload.Flags Request Uses V2 Query Param ✅ 2906ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 2769ms
Request Payload.Flags Request Omits Authorization Header ✅ 2761ms
Request Payload.Token In Flags Body Matches Init ✅ 2877ms
Request Payload.Groups Round Trip ✅ 2821ms
Request Payload.Groups Default To Empty Object ✅ 2775ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 2742ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 2793ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 2749ms
Request Lifecycle.No Flags Request On Init Alone ✅ 100ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 2805ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 5601ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 2771ms
Retry Behavior.Retries Flags On 502 ✅ 2931ms
Retry Behavior.Retries Flags On 504 ✅ 2929ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 3138ms

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Trait-gated public API matches feature availability while preserving manual exception reporting and autocapture masking. No actionable findings.

AI-assisted review.

@turnipdabeets
turnipdabeets merged commit 732dc7d into chore/v4-remove-deprecated Oct 27, 2026
48 checks passed
@turnipdabeets
turnipdabeets deleted the chore/v4-trait-gate-public-api branch October 27, 2026 22:09
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.

2 participants