Skip to content

fix(surveys): dismiss a survey when a cancel event is captured - #1042

Merged
marandaneto merged 7 commits into
PostHog:mainfrom
o-mid:fix/survey-cancel-events
Nov 13, 2026
Merged

marandaneto merged 7 commits into
PostHog:mainfrom
o-mid:fix/survey-cancel-events

Conversation

@o-mid

@o-mid o-mid commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Fixes #450.

Add support for survey cancel_events, matching the web SDK's pending-only cancellation behavior.

A matching cancel event clears unconsumed event activation. For the built-in UI, it also cancels a survey that is still waiting out its popup delay and releases the reservation without sending survey shown or survey dismissed, marking it seen, or updating the last-seen date. It does not dismiss a survey that is already visible.

Once renderSurvey hands a survey to a custom PostHogSurveysDelegate, that delegate retains ownership, including while it defers display. Cancel events do not invalidate the attempt or its callbacks, so the host can still report it shown, submit answers, and close it normally without losing responses or admitting another survey on top.

No public API change. The delegate documentation explains this ownership boundary. The changeset is classified as minor because this adds support for a previously unsupported remote capability.

💚 How did you test it?

  • Test-first reproduction of shown and deferred custom-delegate ownership failures, with unrelated-event controls. Matching cancellation previously invalidated callbacks and lost responses; the same tests now pass.
  • Simulator regression coverage preserves visible built-in surveys on main/background capture and verifies normal user submission/dismissal afterward.
  • Existing coverage retains activation clearing, built-in pending-delay cancellation without interaction events, nonblocking background capture, stale-attempt/reset isolation, and cancellation between selection and reservation.
  • make lint, make checkTestRegistration, and git diff --check
  • make test: 1,186 tests passed in a fresh sequential run
  • make buildSdk: all supported platform build lanes passed
  • Full iOS simulator unit suite: 1,723 tests passed, with the existing declared SwiftUI drawing-group masking limitation
  • make testSurveyUI: all 7 mounted UI tests passed
  • make testPresentationMasks: all 31 hosted presentation/privacy tests passed

Simulator runs used iPhone 17 / iOS 27.2 with a validation-only iOS 15 build override for the installed Xcode. Repository deployment minima are unchanged; the cross-platform SDK builds used Xcode 26.6 without that override.

An initial SPM run alongside simulator validation failed in queue/storage tests. The fresh sequential full rerun passed without source changes; both results were retained locally.

📝 Checklist

  • All commits are signed (see Commit signing)
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • 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

🤖 Agent context

Autonomy: Human-driven (agent-assisted), directed by @marandaneto.

Pi and the GitHub CLI were used for test-first follow-up fixes and review replies. The implementation deliberately follows pending-only web behavior and preserves custom-renderer ownership rather than introducing a new delegate cancellation API.

@o-mid
o-mid requested a review from a team as a code owner November 9, 2026 12:24
@o-mid
o-mid force-pushed the fix/survey-cancel-events branch from e23fada to fa11f60 Compare November 9, 2026 17:38
@signed-commit-check

signed-commit-check Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Commit signatures resolved

GitHub now verifies every commit signature in this PR. No further action is needed.

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated advisory code review. Reproduced blocking findings from the requested review.

@marandaneto
marandaneto requested a review from a team November 10, 2026 12:35
@o-mid

o-mid commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

@marandaneto survey dismissal now runs on the main thread, and the cancel test is in the Xcode target. Can you review again?

@marandaneto

Copy link
Copy Markdown
Member

@omid

Commits must have verified signatures.

and force push

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated advisory follow-up review. Both earlier findings are fixed; two new reproduced blockers remain.

@marandaneto
marandaneto requested a review from a team November 10, 2026 13:54
Capture can invoke cancel handlers off the main queue, and the new regression test was missing from the Xcode test target.
The main-queue dismissal is asynchronous, and it leaves a survey alone when reset has already started a new attempt.
@o-mid

o-mid commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

@marandaneto rewrote the branch onto main so the unsigned merge is gone. The three commits on the branch are signed.

@o-mid

o-mid commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

@marandaneto

Unrelated to this PR, so feel free to ignore it here.

I applied for the Technical Support Engineer role today. The careers form returned an application-limit error, so I sent the application to careers@posthog.com instead.

Working through the mobile SDKs over the last two months, I kept running into the same kinds of gaps: surveys behaving differently on Flutter, iOS and Android, opt-out not fully stopping replay or integrations, and beforeSend not covering every capture path on Flutter web. I have ideas for dealing with the patterns behind those and not only the individual bugs, and I'd like the chance to work on them properly.

Would you be open to a short chat about it, or to pointing me to the right person on the talent team? Happy to do it async, wherever suits you.

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agent-drafted and reviewed by Manoel.

Posting the new reproduced selection/reservation cancellation race. The existing deadlock and stale-attempt findings also still reproduce on this head; their inline comments are not duplicated here:

@marandaneto
marandaneto requested a review from a team November 11, 2026 12:24
@marandaneto

Copy link
Copy Markdown
Member

@PostHog/team-client-libraries could you please re-review this PR? New changes have been pushed to address the survey cancellation feedback.

o-mid and others added 2 commits November 12, 2026 08:27
Recheck activation atomically during reservation and reuse attempt-checked close handling for pending and shown surveys. Preserve the queued selection filter and cover cancellation after a response before the shown callback.
@marandaneto

Copy link
Copy Markdown
Member

Pushed the agent-assisted follow-up in a92adcb on top of eefde9f3, without rewriting history.

The previous head passed the three original regressions, but a further simulator test exposed a lifecycle-state inconsistency: a renderer can report a response before calling onSurveyShown. The existing response handler intentionally treats that answer as proof the survey is on screen (activeSurveyShown = true), while cancellation consulted a separate activeSurveyWasShown snapshot. It therefore dropped an incomplete, answered survey without recording survey dismissed.

The retained responseThenCancellation regression fails on eefde9f3 with no dismissal event and passes with this implementation. Cancellation now reuses the existing attempt/generation-checked close handler, so pending and shown attempts use the authoritative state rather than duplicated bookkeeping.

This also keeps the latest queued-selection filter and rechecks activation under the reservation lock before committing an attempt. The asynchronous main-queue handoff and stale-attempt protection remain in place.

Validation passed:

  • make lint, make checkTestRegistration, and git diff --check
  • make test: 1,186 tests
  • make buildSdk: all supported platform build lanes
  • Focused iOS simulator survey coverage: 192 tests, including the new regression
  • make testSurveyUI: all 7 mounted UI tests

Simulator runs used iPhone 17 / iOS 27.2 with a validation-only iOS 15 build override for the installed Xcode. Repository deployment minima are unchanged; the multi-platform SDK builds ran without that override.

@marandaneto marandaneto self-assigned this Nov 12, 2026

@turnipdabeets turnipdabeets 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.

[nit] The description says web dismisses an on-screen survey on a cancel event. It doesn't: cancelSurvey in surveys-renderer.tsx only acts on surveys still waiting out the delay, and the dashboard describes cancel events as "During your N second delay…".

@turnipdabeets turnipdabeets mentioned this pull request Nov 12, 2026
6 of 18 tasks

@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.

The earlier threading, registration, stale-attempt and queued-selection findings are resolved. The authoritative-state repair also passes an independent SDK-core regression.

The existing custom-delegate ownership concern remains: matching cancellation releases the attempt without cancelling the host’s UI or deferred work. Base/head SDK-core tests reproduce both variants; unrelated-event controls pass. Pending-only behavior must also protect custom deferred handoff.

AI-assisted review.

Comment thread .changeset/survey-cancel-events.md Resolved
@dustinbyrne
dustinbyrne requested a review from a team November 12, 2026 21:08
Preserve shown surveys and custom delegate ownership, including deferred presentations. Reuse the built-in pending-close path, document the contract, and classify the new capability as a minor release.
@marandaneto
marandaneto merged commit 86bb436 into PostHog:main Nov 13, 2026
52 checks passed
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.

Support survey cancellation events

4 participants