Skip to content

Fix workflow exit offer presentation across sheet boundaries#6804

Merged
facumenzella merged 4 commits into
mainfrom
facundo/workflow-exit-offer-fix
May 18, 2026
Merged

Fix workflow exit offer presentation across sheet boundaries#6804
facumenzella merged 4 commits into
mainfrom
facundo/workflow-exit-offer-fix

Conversation

@facumenzella

@facumenzella facumenzella commented May 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Root cause: `WorkflowPaywallView` emits the exit offer via `WorkflowExitOfferPreferenceKey`, but SwiftUI preference keys don't propagate reliably across sheet/fullScreenCover presentation boundaries, so `PresentingPaywallModifier` never received the value.
  • Fix: Add a `workflowExitOfferOfferingBinding` environment value (`Binding<Offering?>`). `PresentingPaywallModifier` injects its `$exitOfferOffering` binding into the environment; `WorkflowPaywallView` writes the resolved exit offer directly to it on appear and on every step change via `.onChange(of: navigator.currentStepId)`. This works across sheet boundaries.
  • Step-aware gating: The binding uses `exitOfferContext(for:currentStepId:)` (not `context.exitOfferOffering`). `exitOfferOffering` is not step-aware — it returns non-nil whenever an exit offer is configured, regardless of current step. `exitOfferContext` mirrors Android's `shouldTriggerExitOfferForCurrentStep` guard: it returns nil when the user is not on the triggering step, so navigating away from the exit-offer step before closing suppresses the exit offer correctly.
  • Testability: Because the step-gating logic lives in the static `exitOfferContext(for:currentStepId:)` helper, it is directly unit-testable without hosting a SwiftUI view. A new test (`testExitOfferOfferingIsNotStepAware`) documents the footgun — `context.exitOfferOffering` is non-nil on any step — and acts as a regression guard against reverting to the step-unaware path.
  • Tester: Replace the old "Workflow" context menu entry with two new modes — "Workflow - Sheet" and "Workflow - Full" — that both present workflows through the full modifier stack so exit offers work.

Test plan

  • Open a workflow offering via "Workflow - Sheet" or "Workflow - Full" in PaywallsTester (with `-EnableWorkflowsEndpoint`)
  • Close the workflow without purchasing while on the exit-offer triggering step — confirm the exit offer sheet appears
  • Navigate to a non-triggering step, then close — confirm no exit offer appears (Android parity)

🤖 Generated with Claude Code


Note

Medium Risk
Changes how workflow exit-offer offerings are propagated during paywall presentation, which can affect when/if exit offers appear on dismissal across sheets/full-screen covers. Logic is localized but touches core paywall presentation flow and adds new environment plumbing that could regress exit-offer triggering if miswired.

Overview
Fixes workflow exit-offer presentation when a workflow paywall is hosted inside a .sheet/.fullScreenCover by adding an environment-injected Binding<Offering?> (workflowExitOfferOfferingBinding) and having WorkflowPaywallView write the step-resolved exit offer directly to it on appear and on step changes.

PresentingPaywallModifier now injects its exitOfferOffering state into the environment, bypassing unreliable SwiftUI preference propagation across presentation boundaries while keeping exit-offer resolution step-aware via exitOfferContext(for:currentStepId:).

Adds a regression test documenting that context.exitOfferOffering is not step-aware, and updates the PaywallsTester app to split workflow presentation into “Workflow - Sheet” and “Workflow - Full” paths that exercise the full modifier stack and manual exit-offer presentation.

Reviewed by Cursor Bugbot for commit 0211a88. Bugbot is set up for automated code reviews on this repo. Configure here.

Uses an environment Binding<Offering?> injected by PresentingPaywallModifier
so WorkflowPaywallView can write the resolved exit offer directly, bypassing
the preference-key path which does not propagate reliably across sheet
presentation boundaries.

Also adds "Workflow - Sheet" and "Workflow - Full" tester modes that present
workflows through the full modifier stack (including exit offer handling).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@facumenzella facumenzella requested review from a team as code owners May 15, 2026 14:23
@emerge-tools

emerge-tools Bot commented May 15, 2026

Copy link
Copy Markdown

4 builds increased size

Name Version Download Change Install Change Approval
RevenueCat
com.revenuecat.PaywallsTester
1.0 (1) 18.0 MB ⬆️ 23.3 kB (0.13%) 64.9 MB ⬆️ 153.2 kB (0.24%) N/A
BinarySizeTest
com.revenuecat.binary-size-test.local-source
1.0 (1) 4.1 MB ⬆️ 3.8 kB (0.09%) 12.4 MB ⬆️ 12.6 kB (0.1%) N/A
BinarySizeTest
com.revenuecat.binary-size-test.cocoapods
1.0 (1) 6.2 MB ⬆️ 6.5 kB (0.11%) 27.3 MB ⬆️ 49.8 kB (0.18%) ⏳ Needs approval
BinarySizeTest
com.revenuecat.binary-size-test.spm
1.0 (1) 4.2 MB ⬆️ 3.7 kB (0.09%) 10.8 MB ⬆️ 8.4 kB (0.08%) N/A

RevenueCat 1.0 (1)
com.revenuecat.PaywallsTester

⚖️ Compare build
⏱️ Analyze build performance

Total install size change: ⬆️ 153.2 kB (0.24%)
Total download size change: ⬆️ 23.3 kB (0.13%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 87.7 kB
Code Signature ⬆️ 4.2 kB
DYLD.Exports ⬆️ 1.1 kB
Other ⬆️ 60.3 kB
View Treemap

Image of diff

BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.local-source

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 12.6 kB (0.1%)
Total download size change: ⬆️ 3.8 kB (0.09%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 5.4 kB
SwiftUI.ModifiedContent ⬆️ 1.0 kB
SwiftUI.View.View ⬆️ 960 B
RevenueCatUI.View.onChangeOf(perform) ⬆️ 660 B
Code Signature ⬆️ 624 B
View Treemap

Image of diff

BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.cocoapods

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 49.8 kB (0.18%)
Total download size change: ⬆️ 6.5 kB (0.11%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 36.3 kB
SwiftUI.ModifiedContent ⬆️ 1.1 kB
Code Signature ⬆️ 1.0 kB
SwiftUI.View.View ⬆️ 960 B
RevenueCatUI.View.onChangeOf(perform) ⬆️ 660 B
View Treemap

Image of diff

BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.spm

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 8.4 kB (0.08%)
Total download size change: ⬆️ 3.7 kB (0.09%)

Largest size changes

Item Install Size Change
SwiftUI.ModifiedContent ⬆️ 1.0 kB
SwiftUI.View.View ⬆️ 960 B
RevenueCatUI.View.onChangeOf(perform) ⬆️ 660 B
SwiftUI.Environment ⬆️ 528 B
Other ⬆️ 5.2 kB
View Treemap

Image of diff


🛸 Powered by Emerge Tools

Comment trigger: Size diff threshold of 100.00kB exceeded

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1960a71. Configure here.

Comment thread RevenueCatUI/Templates/V2/WorkflowPaywallView.swift
@facumenzella facumenzella requested a review from vegaro May 15, 2026 14:54
Use exitOfferContext(for:currentStepId:) instead of context.exitOfferOffering
when syncing the binding, and update on every step change via onChange so
the binding is nil when the user is not on the triggering step — matching
Android's shouldTriggerExitOfferForCurrentStep guard.

Adds testExitOfferOfferingIsNotStepAware as a regression guard documenting
that context.exitOfferOffering is not step-aware and must not be used here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
facumenzella and others added 2 commits May 18, 2026 13:02
…ailure

Replace onChange(of:perform:) (deprecated in visionOS 1.0) with the
project's onChangeOf backport extension.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@facumenzella facumenzella enabled auto-merge (squash) May 18, 2026 19:06
@facumenzella facumenzella merged commit 40842be into main May 18, 2026
16 of 19 checks passed
@facumenzella facumenzella deleted the facundo/workflow-exit-offer-fix branch May 18, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants