Skip to content

Add PaywallsV2 header component#6548

Merged
facumenzella merged 13 commits intomainfrom
feat/paywalls-v2-header-component
Apr 9, 2026
Merged

Add PaywallsV2 header component#6548
facumenzella merged 13 commits intomainfrom
feat/paywalls-v2-header-component

Conversation

@facumenzella
Copy link
Copy Markdown
Member

@facumenzella facumenzella commented Apr 2, 2026

Summary

  • Adds PaywallComponent.HeaderComponent (SPI-only, mirrors StickyFooterComponent pattern) with encode/decode support
  • Wires components_config.header into PaywallComponentsConfig, cache warming, RootViewModel, and RootView
  • Keeps root hero-image handling independent from header safe-area handling
  • Makes the paywall extend into the top safe area whenever a header exists, while keeping header content below the notch unless the header itself starts with hero media
  • Unsupported-condition traversal covers the header stack
  • Splits ViewModelFactory tests so badge-only coverage stays in ViewModelFactoryBadgeTests and shared/root/header coverage lives in ViewModelFactoryTests
Header Header
Screenshot 2026-04-09 at 16 43 59 Screenshot 2026-04-09 at 16 43 50
- -
Screenshot 2026-04-09 at 16 43 36 Screenshot 2026-04-09 at 16 43 21
- -

Spec & cross-platform

Test plan

  • swift test --filter HeaderComponentTests passes
  • swift test --filter PaywallComponentsConfigHeaderTests passes
  • swiftlint lint RevenueCatUI/Templates/V2/Components/Header/HeaderComponentView.swift RevenueCatUI/Templates/V2/Components/Header/HeaderComponentViewModel.swift RevenueCatUI/Templates/V2/PaywallsV2View.swift RevenueCatUI/Templates/V2/ViewModelHelpers/ViewModelFactory.swift Tests/RevenueCatUITests/PaywallsV2/ViewModelFactoryBadgeTests.swift Tests/RevenueCatUITests/PaywallsV2/ViewModelFactoryTests.swift passes
  • xcodebuild test -project RevenueCat.xcodeproj -scheme RevenueCatUITests -destination 'platform=iOS Simulator,name=iPhone 16' -only-testing:RevenueCatUITests/ViewModelFactoryBadgeTests -only-testing:RevenueCatUITests/ViewModelFactoryTests passes
  • Verified that header backgrounds extend through the top safe area while header content stays below the notch unless the header begins with hero media

Note

Medium Risk
Adds a new components_config.header branch that affects paywall decoding, view model construction, cache warming, and top safe-area handling, which could change Paywalls V2 layout/rendering in production. Scope is contained to Paywalls V2/SPI APIs with extensive new unit/UI tests mitigating regression risk.

Overview
Adds a new optional Paywalls V2 header component (PaywallComponent.HeaderComponent) that decodes/encodes from components_config.header and can be rendered above the main stack.

Wires header support through PaywallComponentsConfig, ViewModelFactory.toRootViewModel, cache warming URL collection, and RootView layout (overlaying the header in a top-aligned ZStack and updating safe-area handling for the header independently from root hero-image handling).

Refactors/expands test coverage by moving unsupported-condition/global discardRules tests into a new ViewModelFactoryTests, and adding new unit tests for header component and config decoding.

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

Implements components_config.header support — model, decoding, cache
warming, view/view-model, and root wiring — following the same pattern
as StickyFooterComponent. HeaderComponent is SPI-only (@_spi(Internal))
to avoid a source-breaking enum-case addition to PaywallComponent.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
facumenzella and others added 7 commits April 2, 2026 15:09
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Register .header case in PaywallComponent enum and ComponentType
- Add .header to PaywallComponentViewModel and ViewModelFactory.toViewModel
- Add .header dispatch in ComponentsView
- Add .header to containsUnsupportedConditions in PresentedPartials
- Add .header to collectAllImageURLs/VideoURLs in PaywallV2CacheWarming
- Add spec layout tests (header+footer, header only, footer only, body only)
- Add "as PaywallComponent" deserialization test

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Mirrors Android's ContainsUnsupportedConditionTests: verifies that
unsupported conditions inside a HeaderComponent's stack are detected
both when called directly on the component and when the header is
embedded as a child inside a stack.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Remove additionalPadding(top: headerHeight) from body stack so the header
  overlays content instead of pushing it down
- Fix findFirstItemIgnoresSafeAreaInfo to fall through to the body stack check
  when the header exists but doesn't start with a full-width image, so
  edgesIgnoringSafeArea(.top) is correctly applied

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@facumenzella facumenzella marked this pull request as ready for review April 7, 2026 16:57
@facumenzella facumenzella requested review from a team as code owners April 7, 2026 16:57
The bottom padding should apply whenever there is no sticky footer,
regardless of header presence. The sticky footer handles its own bottom
safe area; the scroll content needs the inset in all other cases.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copy link
Copy Markdown

@cursor cursor bot left a comment

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 7f76cf4. Configure here.

@facumenzella facumenzella requested a review from vegaro April 8, 2026 10:07
@emerge-tools
Copy link
Copy Markdown

emerge-tools bot commented Apr 9, 2026

4 builds increased size

Name Version Download Change Install Change Approval
RevenueCat
com.revenuecat.PaywallsTester
1.0 (1) 17.5 MB ⬆️ 67.8 kB (0.39%) 62.5 MB ⬆️ 266.3 kB (0.43%) N/A
BinarySizeTest
com.revenuecat.binary-size-test.local-source
1.0 (1) 3.9 MB ⬆️ 5.2 kB (0.14%) 11.7 MB ⬆️ 21.7 kB (0.19%) N/A
BinarySizeTest
com.revenuecat.binary-size-test.cocoapods
1.0 (1) 5.9 MB ⬆️ 10.0 kB (0.17%) 26.0 MB ⬆️ 46.7 kB (0.18%) ⏳ Needs approval
BinarySizeTest
com.revenuecat.binary-size-test.spm
1.0 (1) 4.0 MB ⬆️ 5.5 kB (0.14%) 10.2 MB ⬆️ 12.9 kB (0.13%) N/A

RevenueCat 1.0 (1)
com.revenuecat.PaywallsTester

⚖️ Compare build
⏱️ Analyze build performance

Total install size change: ⬆️ 266.3 kB (0.43%)
Total download size change: ⬆️ 67.8 kB (0.39%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 45.5 kB
Code Signature ⬆️ 6.8 kB
DYLD.Exports ⬆️ 1.7 kB
📝 RevenueCat.PaywallComponent.HeaderComponent.HeaderComponent ⬆️ 592 B
Other ⬆️ 211.9 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: ⬆️ 21.7 kB (0.19%)
Total download size change: ⬆️ 5.2 kB (0.14%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 1.5 kB
DYLD.String Table ⬆️ 1.4 kB
Code Signature ⬆️ 936 B
RevenueCatUI.RootView.body ⬆️ 768 B
DYLD.Exports ⬆️ 728 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: ⬆️ 46.7 kB (0.18%)
Total download size change: ⬆️ 10.0 kB (0.17%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 14.2 kB
DYLD.String Table ⬆️ 8.0 kB
Code Signature ⬆️ 1.1 kB
Code Signature ⬆️ 936 B
RevenueCatUI.RootView.body ⬆️ 768 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: ⬆️ 12.9 kB (0.13%)
Total download size change: ⬆️ 5.5 kB (0.14%)

Largest size changes

Item Install Size Change
RevenueCatUI.RootView.body ⬆️ 768 B
DYLD.Exports ⬆️ 736 B
📝 RevenueCatUI.HeaderComponentView.body ⬆️ 632 B
📝 RevenueCatUI.HeaderComponentView.value witness ⬆️ 628 B
RevenueCatUI.ViewModelFactory.toRootViewModel(componentsConfig,of... ⬆️ 568 B
View Treemap

Image of diff


🛸 Powered by Emerge Tools

Comment trigger: Size diff threshold of 100.00kB exceeded

Copy link
Copy Markdown
Member

@vegaro vegaro left a comment

Choose a reason for hiding this comment

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

I am still testing it

@facumenzella facumenzella requested a review from vegaro April 9, 2026 15:25
@facumenzella facumenzella enabled auto-merge (squash) April 9, 2026 15:29
Copy link
Copy Markdown
Member

@vegaro vegaro left a comment

Choose a reason for hiding this comment

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

Looks great. If you can add some screenshots to the description it would be great!

@facumenzella facumenzella merged commit a309106 into main Apr 9, 2026
13 of 16 checks passed
@facumenzella facumenzella deleted the feat/paywalls-v2-header-component branch April 9, 2026 16:20
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