Skip to content

feat(workflows): Enable workflow resolution for PaywallView#6887

Merged
facumenzella merged 7 commits into
mainfrom
codex/codex-20260602-155608
Jun 4, 2026
Merged

feat(workflows): Enable workflow resolution for PaywallView#6887
facumenzella merged 7 commits into
mainfrom
codex/codex-20260602-155608

Conversation

@facumenzella

@facumenzella facumenzella commented Jun 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Resolve workflow paywall data for PaywallView .offering and .defaultOffering content when workflowsEndpointEnabled is on.
  • Keep the legacy cached/offering-only path gated behind workflowsEndpointEnabled == false.
  • Skip UIKit legacy exit-offer prefetch while workflow context owns exit-offer resolution. ==> good thing is that we will delete code from UIKit once this is out
  • Note that the presentation now just works tapping (no long press needed). Remove the explicit workflow actions
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-06-02.at.16.33.26.mov

I didn't want to do this just now until the flag was removed, but I think we'll do that in a separate branch


Note

Medium Risk
Changes paywall offering resolution and initial render behavior behind a feature flag; UIKit exit offers on dismiss are intentionally not wired for workflows yet.

Overview
When workflows are enabled, PaywallView now resolves workflow-backed paywall data for .offering and .defaultOffering (not only offering identifiers), including mapped paywall components and a WorkflowContext. Synchronous cached offering is always nil in that mode so the UI does not flash the wrong paywall before async resolution.

resolveWorkflowContext no longer gates on the flag internally (callers decide) and can reuse an already-fetched Offerings snapshot. UIKit skips legacy exit-offer prefetch while workflows own that path (noted as a follow-up for swipe-to-dismiss). Tests cover both flag states; PaywallsTester drops separate workflow demo modes because normal presentation now uses workflows.

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

AI session context

AI Context

Metadata

  • PR: 6887
  • Branch: codex/codex-20260602-155608
  • Author / human owner: facumenzella (Facundo Menzella)
  • Agent(s): Codex (earlier commits); Claude Code (Opus 4.8) for the review-comment refactor
  • Session source: current conversation (refactor pass); earlier authoring session Not captured
  • Generated: 2026-06-04
  • Context document version: 1

Goal

Enable workflow resolution for PaywallView: when workflowsEndpointEnabled is on, resolve workflow paywall data for .offering and .defaultOffering content (not just offering identifiers), keep the legacy cached/offering-only path gated behind the flag being off, and skip the UIKit legacy exit-offer prefetch while workflow context owns exit-offer resolution. Presentation now works on tap (no long press).

Agent Contribution

  • Analyzed reviewer thread r3355069412 and confirmed the two switch content blocks in cachedInitialOffering were byte-for-byte duplicates.
  • Refactor (commit b728b635e): collapsed the duplicated switch into a single injectable overload cachedInitialOffering(for:workflowsEndpointEnabled:); made the public cachedInitialOffering(for:) a thin wrapper that resolves the flag (ProcessInfo on non-tvOS, false on tvOS) and delegates. Net +24 / -35 in that file.
  • Earlier commits (Codex) added the workflow resolution path, removed workflow-only tester modes, and moved a test helper into the #if !os(tvOS) guard.

Human Decisions

  • Reviewer requested the dedup and approved the PR.
  • Author chose to keep the injectable flag overload (rather than reading ProcessInfo inline in a single function) so tests cover both flag states deterministically; agreed to a follow-up (feat(workflows): synchronously seed workflow paywall from warm cache #6905) for synchronous seeding from the warm cache.
  • UIKit exit-offer load left as a follow-up; comment updated to reflect that SwiftUI owns exit offers.

Key Implementation Decisions

  • Decision: keep a separate injectable cachedInitialOffering(for:workflowsEndpointEnabled:) overload as the single home of the switch; public overload delegates.
    • Rationale: ProcessInfo.workflowsEndpointEnabled is defined only inside #if !os(tvOS) (extension in WorkflowContext.swift), so tvOS cannot read it; and two tests inject the flag to cover both states without depending on the -EnableWorkflowsEndpoint launch arg.
    • Rejected: reading ProcessInfo inline in one function (the literal review suggestion) would not compile on tvOS and would break deterministic test coverage.
  • Decision: resolveWorkflowPaywallViewData intentionally re-resolves the workflow screen's base offering rather than reusing the passed offering.
    • Rationale: the rendered offering/paywall comes from the workflow screen, not the passed offering; seeding the passed offering would render a non-workflow paywall then swap.

Files / Symbols Touched

  • RevenueCatUI/Purchasing/PurchaseHandler.swift
    • Why: workflow resolution path + cachedInitialOffering dedup
    • Symbols: cachedInitialOffering(for:), cachedInitialOffering(for:workflowsEndpointEnabled:), resolvePaywallViewData(for:workflowsEndpointEnabled:), resolveWorkflowPaywallViewData, resolveWorkflowContext
    • Review relevance: confirm the single switch behaves identically for both flag states and on tvOS (flag forced false)
  • RevenueCatUI/UIKit/PaywallViewController.swift
    • Why: skip legacy exit-offer prefetch when workflows are enabled
    • Review relevance: swipe-to-dismiss bridging is a noted follow-up
  • Tests/RevenueCatUITests/Data/PaywallViewConfigurationTests.swift
    • Why: cover cached vs async resolution and both flag states via injection
    • Symbols: testCachedInitialOfferingReturnsNilForAllContentWhenWorkflowsEndpointEnabled, testCachedInitialOfferingUsesCachedOfferingsWhenWorkflowsEndpointDisabled
  • Tests/RevenueCatUITests/Purchasing/WorkflowContextTests.swift
    • Why: dropped obsolete "flag off throws" expectation for resolveWorkflowContext
  • Tests/TestingApps/PaywallsTester/.../PaywallViewMode+Extensions.swift
    • Why: removed workflow-only tester presentation modes

Dependencies / Config / Migrations

  • Feature flag: -EnableWorkflowsEndpoint launch argument (ProcessInfo.workflowsEndpointEnabled, #if !os(tvOS) only). Temporary rollout gate.

Validation

  • Commands run:
    • swift build --target RevenueCatUI: success (12.66s) after the refactor.
  • Manual verification:
    • Not run (refactor is behavior-preserving; the two affected tests call an overload whose body is unchanged).
  • CI:
    • Prior to the refactor commit: all required checks green; full-test workflow on hold awaiting approval. Re-run on b728b635e Not captured at time of writing.

Validation Gaps

  • RevenueCatUI test suite not executed locally for the refactor (requires simulator). Relying on swift build + unchanged test target behavior.
  • tvOS compile path not exercised by swift build (host is macOS); reasoned via #if/flag analysis.

Review Focus

  • Does cachedInitialOffering(for:) produce identical results to the previous split implementation for all three Content cases on both tvOS and non-tvOS?
  • Is keeping the injectable overload (vs inline ProcessInfo) acceptable given the tvOS availability + test-determinism constraints?
  • UIKit: with workflows on, where are exit offers loaded? (SwiftUI preference key path; UIKit follow-up noted.)

Risks / Reviewer Notes

  • Risk: behavior drift between the merged switch and the two prior copies.
    • Evidence: the merged switch is the same code; only the flag early-return moved to a guard.
    • Mitigation: covered by the both-flag-state tests in PaywallViewConfigurationTests.

Non-goals / Out of Scope

Omitted Context

  • Raw transcript, unrelated exploration, sensitive details, repetitive attempts, and chain-of-thought-style content were omitted.

@facumenzella facumenzella changed the title [codex] Enable workflow resolution for paywall offerings Enable workflow resolution for PaywallView Jun 2, 2026
@facumenzella facumenzella requested a review from vegaro June 2, 2026 14:24
@facumenzella facumenzella marked this pull request as ready for review June 2, 2026 14:25
@facumenzella facumenzella requested review from a team as code owners June 2, 2026 14:25
@facumenzella facumenzella changed the title Enable workflow resolution for PaywallView feat(workflows): Enable workflow resolution for PaywallView Jun 2, 2026

@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 6f7a8f6. Configure here.

Comment thread Tests/RevenueCatUITests/Data/PaywallViewConfigurationTests.swift
It's only used by tvOS-excluded tests, so defining it outside the guard
left it unused on tvOS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread RevenueCatUI/UIKit/PaywallViewController.swift
Comment thread RevenueCatUI/Purchasing/PurchaseHandler.swift Outdated
Comment thread RevenueCatUI/Purchasing/PurchaseHandler.swift Outdated
Comment thread RevenueCatUI/Purchasing/PurchaseHandler.swift Outdated
Comment thread Tests/RevenueCatUITests/Data/PaywallViewConfigurationTests.swift
facumenzella and others added 2 commits June 3, 2026 11:32
- Clarify UIKit prefetchExitOffer comment: under workflows the exit offer is
  emitted by WorkflowPaywallView and consumed by the SwiftUI presentation layer;
  the UIKit controller doesn't bridge it yet (follow-up for swipe-to-dismiss).
- Reuse the offerings snapshot in the .defaultOffering path instead of fetching
  offerings() twice; thread it through resolveWorkflowContext.
- Drop the always-true workflowsEndpointEnabled param from
  resolveWorkflowPaywallViewData/resolveWorkflowContext and remove the dead guard;
  callers already gate on the flag.
- Document why cachedInitialOffering returns nil under workflows.
- Remove ProcessInfo-dependent cachedInitialOffering tests, now covered
  deterministically by the explicit-flag matrix tests.

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

Copy link
Copy Markdown
Member Author

I think we can move forward with this one @vegaro, and discuss and tackle #6887 (comment) in the follow-up

@facumenzella facumenzella requested a review from vegaro June 4, 2026 09:39

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

one comment about some cleanup, otherwise it looks good

Comment thread RevenueCatUI/Purchasing/PurchaseHandler.swift
Collapse the duplicated content switch into a single injectable overload.
The public cachedInitialOffering(for:) now just resolves the workflow flag
(reading ProcessInfo on non-tvOS, false on tvOS where the endpoint isn't
available) and delegates, so the switch lives in one place. Keeps the
workflowsEndpointEnabled overload so tests can cover both flag states
deterministically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@facumenzella facumenzella enabled auto-merge (squash) June 4, 2026 10:25
@facumenzella facumenzella merged commit 5e621e1 into main Jun 4, 2026
18 of 20 checks passed
@facumenzella facumenzella deleted the codex/codex-20260602-155608 branch June 4, 2026 10:36
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