Skip to content

Generalize PaywallComponentsScaffold for workflow reuse#3417

Merged
vegaro merged 2 commits into
mainfrom
cesar/wfl-46-paywall-scaffold
May 4, 2026
Merged

Generalize PaywallComponentsScaffold for workflow reuse#3417
vegaro merged 2 commits into
mainfrom
cesar/wfl-46-paywall-scaffold

Conversation

@vegaro

@vegaro vegaro commented Apr 30, 2026

Copy link
Copy Markdown
Member

Motivation

Workflow paywalls (multi-step) and single-page component paywalls share the same chrome — background, bottom sheet, fixed header overlay, and sticky footer — but differ in how the body is rendered (a single column vs. two sliding columns). Today LoadedPaywallComponents owns all of that scaffolding inline, so a workflow renderer would have to either duplicate it or wrap it awkwardly.

Description

Refactors PaywallComponentsScaffold into a pure layout primitive with three composable slots and a narrow value type for the painted background:

@Composable
internal fun PaywallComponentsScaffold(
    state: PaywallState.Loaded.Components,
    modifier: Modifier = Modifier,
    background: BackgroundStyle? = rememberBackgroundStyle(state.background),
    headerContent: (@Composable () -> Unit)? = null,
    footerContent: (@Composable () -> Unit)? = null,
    mainContent: @Composable () -> Unit,
)
  • state — primary state, used for the bottom sheet, locale, and the layout pass that measures the header overlay's height.
  • background — narrow BackgroundStyle? instead of a whole PaywallState.Loaded.Components. Defaults to rememberBackgroundStyle(state.background). Pass null to skip background painting (workflow paints per-step backgrounds inside its sliding surfaces).
  • headerContent — composable slot for the fixed header overlay. Standard caller derives it from state.header; workflow caller derives it from a workflow-selected step state to keep the from-step's header visible during a backward swipe. The slot is rendered as child 1 of HeaderOverlayLayout, which still measures it first to set state.headerHeightPx for body padding.
  • footerContent — composable slot for the sticky footer. Standard caller derives it from state.stickyFooter; workflow caller passes nothing because each step renders its own footer that slides with the body.
  • mainContent — the scrollable body.

Click handling and component-interaction tracking are no longer scaffold concerns — the scaffold has nothing to dispatch from once header/footer rendering moves to caller-owned slots, so clickHandler / componentInteractionTracker parameters are dropped. Each caller constructs its own onClick and threads it through the slot lambdas.

Pure refactor — single-page behavior is unchanged. Workflow PRs downstream consume the slots.

Checklist

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

Note

Medium Risk
Refactors paywall Compose scaffolding and background application, which could subtly affect layout/scrolling behavior across paywalls despite being intended as behavior-preserving. Adds a small workflow navigation snapshot change that reduces mutation risk.

Overview
Refactors PaywallComponentsScaffold into a reusable layout primitive with explicit mainContent, optional headerContent/footerContent slots, and an optional BackgroundStyle? so workflow paywalls can reuse the same chrome while controlling header/footer rendering and background painting.

Moves click handling/interaction tracking out of the scaffold into LoadedPaywallComponents, adds shouldWrapMainContentInVerticalScroll(...) helper (with new unit tests) to avoid double-verticalScroll crashes, and makes WorkflowNavigator.backStackSnapshot return an immutable list copy (toList()) instead of exposing the deque.

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

@vegaro vegaro force-pushed the cesar/wfl-46-workflow-state branch from 1371e22 to f0bc422 Compare April 30, 2026 08:50
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch 2 times, most recently from 7b4fbec to f3f173e Compare April 30, 2026 08:55
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-state branch from f0bc422 to 0c815c2 Compare April 30, 2026 08:55
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch 3 times, most recently from dc3b6e9 to 4991932 Compare April 30, 2026 09:27
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-state branch from 0c815c2 to bb79d4e Compare April 30, 2026 09:39
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch from 4991932 to 02501fc Compare April 30, 2026 09:39
@vegaro vegaro changed the base branch from cesar/wfl-46-workflow-state to cesar/wfl-46-prewarm April 30, 2026 09:39
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch 3 times, most recently from 080ad91 to 8c45648 Compare April 30, 2026 09:49
@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.45%. Comparing base (96b0418) to head (1cd2187).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3417   +/-   ##
=======================================
  Coverage   79.45%   79.45%           
=======================================
  Files         362      362           
  Lines       14539    14539           
  Branches     1976     1976           
=======================================
  Hits        11552    11552           
  Misses       2190     2190           
  Partials      797      797           

☔ 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 vegaro force-pushed the cesar/wfl-46-prewarm branch from 6127140 to 94a6c34 Compare April 30, 2026 10:12
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch from 8c45648 to 553bfb7 Compare April 30, 2026 10:12
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch 2 times, most recently from e4e3bc9 to fbf8377 Compare April 30, 2026 11:31
@vegaro vegaro marked this pull request as ready for review April 30, 2026 11:59
@vegaro vegaro requested a review from a team as a code owner April 30, 2026 11:59
@vegaro vegaro changed the title refactor: generalize PaywallComponentsScaffold for workflow reuse Generalize PaywallComponentsScaffold for workflow reuse Apr 30, 2026
@vegaro vegaro force-pushed the cesar/wfl-46-prewarm branch 2 times, most recently from 51e1e9f to ff264e9 Compare April 30, 2026 13:16
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch 2 times, most recently from d61577a to e83165a Compare April 30, 2026 13:21
@vegaro vegaro force-pushed the cesar/wfl-46-prewarm branch 2 times, most recently from 3abb26a to 0c63590 Compare April 30, 2026 13:36
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch 2 times, most recently from 0a37d7a to 9a6d39b Compare April 30, 2026 14:00

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

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 9a6d39b4b9fc71be94612be240e5b7e4ceabb338. Configure here.

* Returns `false` when the root stack already scrolls vertically (overflow = SCROLL on a vertical
* dimension), because two vertical scroll modifiers on the same axis crash at runtime.
*/
@JvmSynthetic

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.

Redundant @JvmSynthetic on file-level synthetic function

Low Severity

The @JvmSynthetic annotation on shouldWrapMainContentInVerticalScroll is redundant because the file already has @file:JvmSynthetic on line 1, which applies to all top-level declarations. Additionally, the function is internal, so the annotation provides no additional value.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9a6d39b4b9fc71be94612be240e5b7e4ceabb338. Configure here.

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.

fixed in followup

) {
WithOptionalBackgroundOverlay(state, background = background) {
Column {
HeaderOverlayLayout(
state = state,
modifier = Modifier.weight(1f),
) {
// Child 0: caller-supplied main content (scrollable body or slide container).
// Child 0: caller-supplied main content (scrollable body).

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.

isn't this the slide container now? based on the pr desc

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.

fixed in followup

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

thanks for splitting into small chunks 💪

@vegaro vegaro force-pushed the cesar/wfl-46-prewarm branch from 2c45061 to 46bf10d Compare April 30, 2026 14:14
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch from 9a6d39b to 6a0bb78 Compare April 30, 2026 14:14
@vegaro vegaro force-pushed the cesar/wfl-46-prewarm branch from 46bf10d to 59037fa Compare April 30, 2026 14:32
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch from 6a0bb78 to 313c2c8 Compare April 30, 2026 14:32
@vegaro vegaro force-pushed the cesar/wfl-46-prewarm branch from 950054c to a661ac3 Compare May 4, 2026 11:07
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch from 313c2c8 to ac38b8f Compare May 4, 2026 11:07
Base automatically changed from cesar/wfl-46-prewarm to main May 4, 2026 11:57
vegaro and others added 2 commits May 4, 2026 14:38
PaywallComponentsScaffold now accepts optional headerState, backgroundState,
and renderStickyFooter parameters (all default to single-page behaviour) so
LoadedWorkflowPaywall can pin a stable header during a slide transition,
opt out of the scaffold-level background, and skip the sticky footer when
each step renders its own.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The property was returning the live ArrayDeque reference, letting callers
observe future mutations through the List handle. toList() produces an
actual snapshot consistent with the property name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro vegaro force-pushed the cesar/wfl-46-paywall-scaffold branch from ac38b8f to 1cd2187 Compare May 4, 2026 12:40
@vegaro vegaro added this pull request to the merge queue May 4, 2026
Merged via the queue into main with commit dbd69ef May 4, 2026
41 checks passed
@vegaro vegaro deleted the cesar/wfl-46-paywall-scaffold branch May 4, 2026 13:21
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request Jun 5, 2026
**This is an automatic release.**

## RevenueCat SDK
### ✨ New Features
* Add optional support for setting obfuscated account id to product
changes (RevenueCat#3428) via Mark Villacampa (@MarkVillacampa)

## RevenueCatUI SDK
### Paywallv2
#### ✨ New Features
* Add slide transition to workflow paywalls (RevenueCat#3418) via Cesar de la Vega
(@vegaro)
* Workflow state & ViewModel infrastructure (RevenueCat#3416) via Cesar de la Vega
(@vegaro)
#### 🐞 Bugfixes
* Fix paywall layout direction for RTL locale overrides (PWENG-39)
(RevenueCat#3425) via Monika Mateska (@MonikaMateska)
* Apply ripple shape clip on a sibling Box to avoid clipping content
(RevenueCat#3395) via Toni Rico (@tonidero)

### 🔄 Other Changes
* build(deps): bump fastlane-plugin-revenuecat_internal from `21e02ec`
to `af7bb5c` (RevenueCat#3442) via dependabot[bot] (@dependabot[bot])
* Abstract workflow page transition animation behind sealed class
(RevenueCat#3430) via Cesar de la Vega (@vegaro)
* Add `single_step_fallback_id` field to `PublishedWorkflow` (RevenueCat#3436) via
Cesar de la Vega (@vegaro)
* build(deps): bump fastlane-plugin-revenuecat_internal from `2d11430`
to `21e02ec` (RevenueCat#3429) via dependabot[bot] (@dependabot[bot])
* Generalize `PaywallComponentsScaffold` for workflow reuse (RevenueCat#3417) via
Cesar de la Vega (@vegaro)
* perf: pre-warm workflow paywall step states off-thread (RevenueCat#3420) via
Cesar de la Vega (@vegaro)
* Update baseline profiles (RevenueCat#3427) via RevenueCat Git Bot (@RCGitBot)
* build(deps): bump fastlane-plugin-revenuecat_internal from `d24ab26`
to `2d11430` (RevenueCat#3426) via dependabot[bot] (@dependabot[bot])
* Replace unauthenticated SDKMAN install with SHA-pinned orb command
(RevenueCat#3407) via Rick (@rickvdl)
* Auto load paywall in paywall tester via local.properties (RevenueCat#3405) via
Cesar de la Vega (@vegaro)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: this is a version/release cut that mainly updates version
strings, changelogs, and doc deployment targets with no functional logic
changes beyond version identifiers.
> 
> **Overview**
> Cuts the `10.4.0` release by removing `-SNAPSHOT` across the project
(core `VERSION_NAME`, `Config.frameworkVersion`, sample/test app
dependency versions, and the root `.version` file).
> 
> Updates release collateral and publishing to point at `10.4.0`,
including changelogs (`CHANGELOG.md`/`CHANGELOG.latest.md`), docs
redirect (`docs/index.html`), and the CircleCI `docs-deploy` S3 sync
path (from `10.4.0-SNAPSHOT` to `10.4.0`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f7b3604. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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