Skip to content

Propagate default package across workflow steps#3431

Merged
vegaro merged 19 commits into
mainfrom
cesar/2026-05-03-workflow-package-context
May 7, 2026
Merged

Propagate default package across workflow steps#3431
vegaro merged 19 commits into
mainfrom
cesar/2026-05-03-workflow-package-context

Conversation

@vegaro

@vegaro vegaro commented May 4, 2026

Copy link
Copy Markdown
Member

Motivation

In a multipage workflow paywall, PackageComponents only live on the terminal step. Early "info" screens have no package components, so selectedPackageInfo is null on them. Price/period template variables like {{ price }} and {{ price_per_year }} can't resolve on those screens.

Description

Adds a default-package fallback. The terminal step is identified by singleStepFallbackId on the workflow (added in #3436). When any step state is built, the terminal step's selectedPackageInfo is applied as the default. Steps with their own PackageComponents ignore the default; steps without one resolve variables against it.

The default is set once per step and never overwritten, so back navigation always shows the same content as the initial render, even if the user changed the selection on the terminal step.

Changes:

  • PaywallState.Loaded.Components: add defaultPackageInfo and setDefaultPackage(info). setDefaultPackage is idempotent (set once, never overwritten). selectedPackageInfo is now ownSelection ?: defaultPackageInfo.
  • PaywallViewModel: in updateStateFromWorkflow, pre-compute the singleStepFallbackId step first so its default is in cache before any other step loads. On every step build, apply the fallback step's selectedPackageInfo (idempotency means it only takes effect on the first visit). Log a warning if singleStepFallbackId points to a missing step.
  • Tests: default-package application on packageless steps, back-navigation preserving the initial default, own selection winning over the default, graceful handling when singleStepFallbackId is missing. Plus extra WorkflowNavigator cases (deep backstack, non-linear navigation).

Note

Medium Risk
Touches workflow state construction/caching and selection precedence, which can subtly affect navigation/backstack behavior and displayed pricing context across steps.

Overview
Adds a default-package fallback to multi-step workflow paywalls: PaywallState.Loaded.Components now stores an idempotent defaultPackageInfo, and selectedPackageInfo resolves as own selection falling back to that default.

PaywallViewModel now precomputes the singleStepFallbackId step to seed the cache, applies the fallback step’s selectedPackageInfo as a default on every step build/prewarm (without overwriting on revisits), and logs when the fallback step ID is missing.

Expands tests to cover context propagation on packageless steps, back-navigation stability, selection precedence, missing-fallback safety, plus additional WorkflowNavigator backstack/loop scenarios.

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

@vegaro vegaro changed the title fix: propagate selected package across workflow steps and expand test coverage Propagate selected package across workflow steps and expand test coverage May 4, 2026
@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.47%. Comparing base (67a763d) to head (a9a862d).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3431   +/-   ##
=======================================
  Coverage   79.47%   79.47%           
=======================================
  Files         362      362           
  Lines       14547    14547           
  Branches     1977     1977           
=======================================
  Hits        11561    11561           
  Misses       2190     2190           
  Partials      796      796           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

vegaro and others added 9 commits May 4, 2026 21:21
Introduce setContextPackage()/contextPackageInfo on Components so the
ViewModel can supply a default package for steps that have no own
package components; selectedPackageInfo falls back to contextPackageInfo
when selectedPackageUniqueId is null. Remove selectPackageIfExists and
its call site in PaywallViewModel as part of migrating to the new
context-based propagation approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rkflow steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…kage approach

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stead of per-step default_package_id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro vegaro changed the title Propagate selected package across workflow steps and expand test coverage Propagate selected package across workflow steps May 5, 2026
vegaro and others added 6 commits May 5, 2026 13:47
…ckage idempotent

- Restore deleted JSON parsing tests for single_step_fallback_id in WorkflowDetailResolverTest
- Move idempotency guard into setContextPackage so the set-once contract is self-enforcing
- Add Logger.w when singleStepFallbackId points to a missing step
- Add test for graceful degradation when singleStepFallbackId references a non-existent step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m:RevenueCat/purchases-android into cesar/2026-05-03-workflow-package-context
…o all steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro vegaro marked this pull request as ready for review May 6, 2026 12:46
@vegaro vegaro requested a review from a team as a code owner May 6, 2026 12:46
@vegaro vegaro requested a review from facumenzella May 6, 2026 12:46
facumenzella

This comment was marked as resolved.

@facumenzella

Copy link
Copy Markdown
Member

I had a few comments that gh does not let me make 😢

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

rejecting till GitHub lets me put my comments 🙏

Comment on lines +787 to +793
if (newState is PaywallState.Loaded.Components) {
val defaultPackage = workflow.singleStepFallbackId
?.let { workflowStepStateCache[it]?.selectedPackageInfo }
if (defaultPackage != null) {
newState.setDefaultPackage(defaultPackage)
}
}

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.

I wonder if we could do this at the prewarm stage instead? 🤔

@vegaro vegaro May 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, but it needs to be here as well for the first step. So the prewarm of the following steps have this (it's idempotent only happens once) so when they load it's ready. Basically:

Prewarmed steps don’t have their defaultPackageInfo set until the user navigates to them. If stepStates is observed by the UI between prewarm completing and actual navigation, those states would have defaultPackageInfo = null. The initial step is built synchronously before prewarm even starts, so the initial step would lose its setDefaultPackage call. So we need to add the setDefaultPackage call inside preWarmWorkflowStepCache but keep it in buildStateFromStep for the initial step.

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

I think it looks good. Just one nit open for debate 👍

@vegaro vegaro enabled auto-merge May 7, 2026 10:16
@vegaro vegaro added this pull request to the merge queue May 7, 2026
Merged via the queue into main with commit 175c252 May 7, 2026
38 checks passed
@vegaro vegaro deleted the cesar/2026-05-03-workflow-package-context branch May 7, 2026 10:51
@vegaro vegaro changed the title Propagate selected package across workflow steps Propagate default package across workflow steps May 7, 2026
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request Jun 5, 2026
**This is an automatic release.**

## RevenueCat SDK
### 🐞 Bugfixes
* fix: url encode query prameters (RevenueCat#3451) via Jacob Rakidzich
(@JZDesign)

## RevenueCatUI SDK
### 🐞 Bugfixes
* Fix: dismiss was called before onPurchaseComplete callback invocation
(RevenueCat#3353) via Jacob Rakidzich (@JZDesign)
* Propagate default package across workflow steps (RevenueCat#3431) via Cesar de
la Vega (@vegaro)
### Paywallv2
#### ✨ New Features
* feat: Allow disabling of automatic font scaling (RevenueCat#3438) via Jacob
Rakidzich (@JZDesign)

### 🔄 Other Changes
* Extract `PaywallComponentsImagePreDownloader` (RevenueCat#3448) via Cesar de la
Vega (@vegaro)
* Simplify `WorkflowTransitionState` with explicit from/to step fields
(RevenueCat#3441) via Cesar de la Vega (@vegaro)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk release bookkeeping: primarily flips version strings from
`10.5.0-SNAPSHOT` to `10.5.0` and updates docs/changelogs, with no
functional code changes beyond the reported version constant.
> 
> **Overview**
> Cuts the `10.5.0` release by switching the project from
`10.5.0-SNAPSHOT` to `10.5.0` across build metadata (`.version`,
`gradle.properties`, sample/test app `libs.versions.toml`, and
`Config.frameworkVersion`).
> 
> Updates release artifacts and documentation pointers: CircleCI docs
deploy now syncs the `10.5.0` docs folder to S3, `docs/index.html`
redirects to `10.5.0`, and changelogs are rolled forward with the
`10.5.0` entries in `CHANGELOG.md`/`CHANGELOG.latest.md`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
48537d6. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request Jun 5, 2026
…at#3445)

## Summary

Follow-up to RevenueCat#3431.

During review of RevenueCat#3431 I noticed that when `singleStepFallbackId` points
to the same step as `initialStepId`, the pre-computation guard
(`stepWithPackages.id != initialStep.id`) correctly skips re-building
the step, but there was no test covering this path.

In this edge case `setDefaultPackage` is effectively called on the step
with its own `selectedPackageInfo` as input (a self-referential read
from the cache). The test verifies that:
- No crash occurs
- `ownSelection` takes precedence — MONTHLY (the default-by-config
package) is still returned correctly

## Test plan
- [x] New test passes locally: `singleStepFallbackId equal to
initialStepId does not crash and own selection is used`
- [x] Full `PaywallViewModelWorkflowTest` suite passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: adds a unit test only, with no production code changes; the
main impact is tighter regression coverage around workflow default
package selection.
> 
> **Overview**
> Adds a new `PaywallViewModelWorkflowTest` case covering the edge
scenario where `workflow.singleStepFallbackId` equals `initialStepId`.
> 
> The test asserts `updateStateFromWorkflow` doesn’t crash and that the
initial step’s *own* default package selection still wins (e.g. MONTHLY)
rather than being overridden by a self-referential fallback/default
package lookup.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fd9659d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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