Skip to content

fix(workflows): compute intro eligibility for inherited packages on packageless workflow screens#6978

Merged
facumenzella merged 3 commits into
mainfrom
facu/workflow-intro-eligibility-inherited-packages
Jun 11, 2026
Merged

fix(workflows): compute intro eligibility for inherited packages on packageless workflow screens#6978
facumenzella merged 3 commits into
mainfrom
facu/workflow-intro-eligibility-inherited-packages

Conversation

@facumenzella

@facumenzella facumenzella commented Jun 11, 2026

Copy link
Copy Markdown
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-android and hybrids

Motivation

On Paywalls V2 workflows, intro_offer_condition / promo_offer_condition overrides on a workflow step that has no package components never fire, even when the user is eligible and the step inherits a selected package from another step. Eligibility is computed only over the screen's own package components, which is empty on such a step, so the rules can't resolve.

Description

Compute eligibility over the inherited workflow package context (workflowPackages) in addition to the screen's own packages:

  • Intro: introEligibilityPackages(...) unions the on-screen and inherited packages (order-preserving, deduped).
  • Promo: the inherited packages' Apple promo offer product codes are carried through the workflow package context (WorkflowPackageContext.promoOfferCodesByPackageId, populated from each package component's applePromoOfferProductCode) and unioned into the promo eligibility computation, so promos and intros behave the same.

Standalone paywalls are unaffected: workflowPackages is nil there, so both helpers return the on-screen packages unchanged.

Note: a screen that already hosts its package components (e.g. packages in a sticky footer) was never affected, it always computed eligibility over those packages.

Verification

A/B in PaywallsTester on the packageless first step, with the user genuinely intro-eligible:

  • Without fix: compute for=[] -> [] → the step's tabs stay visible (rule can't fire).
  • With fix: compute for=[$rc_annual,$rc_monthly] -> true → tabs hide as intended.
AI session context

Generated with Claude Code. Evidence-first decision log; no secrets included.

Task. Investigate a suspected bug on an iOS Paywalls V2 workflow where an intro-eligibility rule didn't take effect, then fix it.

Investigation (decision log).

  • Pulled the workflow config (mafdet workflow content). Found intro_offer_condition overrides (hide-when-eligible): a carousel on a screen with packages, and tabs on a screen without package components. Only intro_offer_condition + selected conditions exist, so the global discardRules trap is not triggered.
  • Traced the iOS path: eligibility is computed in PaywallsV2View.task over paywallState.packages, populated by PackageValidator, which only collects package components present on the screen (ViewModelFactory.swift). A packageless screen => empty list => isEligible(_:) always false => override can't fire.
  • Confirmed the sticky-footer packages on the carousel screen are collected, so that screen was not the bug; corrected an earlier wrong assumption that it was.

Native A/B (PaywallsTester, instrumented IntroOfferEligibilityContext with temporary logs, since reverted), eligibility genuinely true:

  • Without fix: packageless step logs compute for=[] -> []; tabs stay visible.
  • With fix: packageless step logs compute for=[$rc_annual,$rc_monthly] -> true; tabs hide.
  • Earlier runs showed false only because the StoreKit config lacked a valid intro offer / wasn't applied under simctl launch.

Review follow-up (vegaro). Asked whether promos should behave the same as intros. They should, and the promo offer product code is reachable (PackageComponent.applePromoOfferProductCode in collectPackages). Extended the fix to thread it through WorkflowPackageContext and union it into the promo eligibility computation; the earlier "promo stays on-screen only" framing was wrong and is gone.

Honesty caveat. The originally-reported symptom (carousel not hiding on the screen that does have packages) was an eligibility-state issue, not an SDK bug: that screen hides correctly once the user is genuinely eligible, with or without this change. This fix's real effect is the packageless workflow step.

Verification. swift build ok; unit tests for introEligibilityPackages + promoEligibilityPackageInfos pass; SwiftLint clean; native PaywallsTester A/B as above. Added the new test file to RevenueCat.xcodeproj to satisfy the Danger project-sync check.

Not done / follow-ups. No end-to-end snapshot test for the full render path (unit tests cover the package-selection decision only).


Note

Medium Risk
Touches paywall purchase-path eligibility and workflow package context; behavior change is scoped to workflow steps without local package components, with unit tests covering the merge logic.

Overview
Fixes Paywalls V2 workflow steps that have no package components but inherit packages from another step: intro_offer_condition and promo_offer_condition overrides could not resolve because eligibility ran only over on-screen packages (empty on those steps).

PaywallsV2View now unions inherited workflowPackages (and their promo codes) into intro and promo eligibility via introEligibilityPackages and promoEligibilityPackageInfos, with deduplication and on-screen values winning for promos. WorkflowPackageContext gains promoOfferCodesByPackageId, populated from package components’ applePromoOfferProductCode in WorkflowContext.collectPackages, and threaded through WorkflowPaywallView. Standalone paywalls are unchanged when workflowPackages is nil.

Adds PaywallsV2ViewTests for the merge/dedupe helpers and registers the file in the Xcode project.

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

@facumenzella facumenzella marked this pull request as ready for review June 11, 2026 13:51
@facumenzella facumenzella requested review from a team as code owners June 11, 2026 13:51
…ackageless workflow screens

Intro-offer (`intro_offer_condition`) overrides never fired on a workflow step
that has no package components, because intro eligibility was computed only over
the screen's own package components (empty on such a step). Compute it over the
inherited workflow package context as well, so a step that inherits a selected
package from another step can resolve the rule.

Standalone paywalls are unaffected: `workflowPackages` is nil there, so the
helper returns the on-screen packages unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@facumenzella facumenzella force-pushed the facu/workflow-intro-eligibility-inherited-packages branch from 99701ac to e4caa9c Compare June 11, 2026 13:55
@facumenzella facumenzella requested a review from a team as a code owner June 11, 2026 13:55
@facumenzella facumenzella requested a review from vegaro June 11, 2026 14:22
Comment thread RevenueCatUI/Templates/V2/PaywallsV2View.swift Outdated
Mirror the intro-offer fix for `promo_offer_condition`: carry each inherited
package's Apple promo offer product code through the workflow package context
(`WorkflowPackageContext.promoOfferCodesByPackageId`) and union it into the promo
eligibility computation, so promos and intros behave the same on a packageless
workflow step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@facumenzella

Copy link
Copy Markdown
Member Author

@vegaro this was indeed a bug, however, I am also adding some changes to the rc mobile app.

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

this looks good but I made a suggestion to prevent duplicated packages in cesar/facu-suggestion

Comment thread RevenueCatUI/Templates/V2/PaywallsV2View.swift Outdated
Comment thread RevenueCatUI/Templates/V2/PaywallsV2View.swift Outdated
@facumenzella facumenzella enabled auto-merge (squash) June 11, 2026 15:12
@facumenzella facumenzella disabled auto-merge June 11, 2026 15:12
@facumenzella facumenzella enabled auto-merge (squash) June 11, 2026 15:15
@facumenzella facumenzella merged commit 227c0e9 into main Jun 11, 2026
18 of 20 checks passed
@facumenzella facumenzella deleted the facu/workflow-intro-eligibility-inherited-packages branch June 11, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants