Skip to content

docs(surveys): specify the survey popup delay - #121

Open
posthog[bot] wants to merge 3 commits into
mainfrom
posthog/spec-survey-popup-delay
Open

posthog[bot] wants to merge 3 commits into
mainfrom
posthog/spec-survey-popup-delay

Conversation

@posthog

@posthog posthog Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Why

Every surveys-capable SDK honors appearance.surveyPopupDelaySeconds, but the surveys spec never mentions it. The spec's nearby rules ("allow only one active prompt", "a survey should be marked active before its shown/response/closed callbacks can mutate response state") are written for a survey that is already on screen, so the window between deciding to show a survey and the survey appearing was unspecified — and SDKs diverged there.

What changed in the SDKs

  • posthog-android#849 — Android marked a survey active only once it was shown, so nothing was reserved while the delay ran: each repeat trigger event re-rendered the survey and restarted the delegate's timer, and trigger events firing faster than the delay kept the survey from showing at all. It now reserves the survey (pendingPresentation) before renderSurvey, matching iOS (scheduleSurveyDisplay sets pendingSurvey before the delay) and the browser (_addSurveyToFocus before the timer in handlePopoverSurvey). The delegate contract now requires reporting back, because a silently dropped survey would hold the reservation.
  • posthog-flutter#627 — plumbs the delay into its own bottom sheet and had to rediscover the same rules from the native code: zero, negative, or missing values show immediately, and a survey dropped during the wait is not marked seen or dismissed.

What this PR does

Adds a Survey popup delay requirement to openspec/specs/surveys/spec.md (proposed, applied, and archived on this branch per the repo workflow):

  • A positive delay waits before display; missing, zero, or negative shows immediately, and a negative value never means "never show".
  • The survey is reserved as the pending prompt before the wait, so a repeat trigger or another eligibility pass neither restarts the wait nor shows a second copy — the wait is measured from the first trigger.
  • A survey that never reached the screen is not seen and emits no survey shown / survey dismissed; the reservation is released when it is shown, dropped, or on reset.
  • A delegating host UI layer must report back (shown or closed), since the reservation gates every later survey.
  • The browser-only refinements (resume from the trigger's activation timestamp; an explicit display call bypassing the delay) are recorded as allowed, not required.

Uncertain / worth a look

  • Eligibility re-check on elapse is stated as SHOULD, not SHALL. The browser re-checks the full display predicate when the timer fires; the native SDKs do not. Writing it as SHALL would make iOS and Android non-conforming today, so I kept it weaker — tighten it if that's the intended contract.
  • posthog-react-native reads surveyPopupDelaySeconds in its survey provider, but I did not verify whether it reserves the survey before the wait. The Impact section says so rather than claiming conformance.

Created with PostHog Desktop

🤖 Generated with Claude Code

Adds a Survey popup delay requirement covering the window between deciding
to show a survey and the survey reaching the screen: the delay source and
its non-positive fallback, reserving the survey before the wait so repeat
triggers do not restart it, and what happens to a pending survey dropped
before it is shown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 8b8b7451-6b5a-4d7e-a81a-a8ab45933aca
@turnipdabeets turnipdabeets self-assigned this Oct 20, 2026
…eact-native claims

react-native does not implement the delay yet (commented-out TODO in
PostHogSurveyProvider.tsx); fix proposal/design accordingly and pass strict validation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 20, 2026 19:47
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 20, 2026 19:47

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


- **posthog-js** (browser) conforms: `handlePopoverSurvey` claims focus before arming the timer,
re-checks eligibility when the timer fires, and releases focus when the survey is dropped.
- **posthog-ios** conforms: `scheduleSurveyDisplay` sets `pendingSurvey` before the delay and

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.

blocking: Qualify iOS conformance for a close before first display — The blanket iOS conformance claim misses a valid pre-show delegate-close path: the integration reserves activeSurvey before calling the delegate, but handleSurveyClosed marks that still-current presentation seen and emits survey dismissed even when its delegate never called onSurveyShown. That contradicts this PR’s new no-seen/no-dismissal requirement and its instruction that an unable-to-display host report closed; normal teardown avoids the problem only by invalidating active state first. Record this as an implementation gap or qualify the conformance statement rather than documenting the stronger callback contract as already satisfied. Reproduction: reproduced — swift -module-cache-path "$V/.swift-module-cache" "$V/ios-cancellation-probe.swift" executes the unchanged cited close handler for an unshown current presentation and exits 1 with seen=true and survey dismissed recorded.

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.

Fixed in d45e483: iOS now listed as a gap for close-before-shown.

- **posthog-ios** conforms: `scheduleSurveyDisplay` sets `pendingSurvey` before the delay and
`dismissPendingSurveyIfNeeded` releases it through the closed handler without marking it seen.
- **posthog-android** conforms as of #849 (`pendingPresentation` reserved before `renderSurvey`).
- **posthog-flutter** conforms as of #627 for its own bottom sheet; it relies on the native

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.

blocking: Do not certify Flutter’s cancelled-delay report-back path — Flutter #627 does not satisfy the newly required host report-back guarantee: its _cancelPopupDelay deliberately suppresses onClosed, and showSurvey returns when that cancelled wait resolves false. Relying on native reservation does not implement the missing callback; a cancelled bottom-sheet presentation therefore cannot be described as conforming to the requirement that every handed-off survey report shown or closed. Native reset/teardown may independently clear its reservation, so this is not a claim that ordinary reset necessarily deadlocks; qualify the documented conformance and track the cancellation gap instead. Reproduction: reproduced — DART_SUPPRESS_ANALYTICS=true dart "$V/flutter-cancellation-probe.dart" executes the unchanged cited asynchronous display/cancellation methods and exits 1 with onShownCalls=0; onClosedCalls=0.

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.

Fixed in d45e483: Flutter now listed as a gap for cancelled-delay report-back.

@marandaneto
marandaneto requested a review from a team October 21, 2026 14:29
iOS marks a survey seen and emits survey dismissed when the delegate
closes it before it was shown, and Flutter's cancelled delay reports
neither shown nor closed. Qualify the conformance claims instead of
certifying both SDKs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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