Reconcile package selection when a window size rule hides the selected package - #4198
Draft
joshdholtz wants to merge 5 commits into
Draft
joshdholtz wants to merge 5 commits into
joshdholtz wants to merge 5 commits into
Conversation
2 tasks
📸 Snapshot Test632 unchanged
🛸 Powered by Emerge Tools |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4198 +/- ##
=======================================
Coverage 83.70% 83.70%
=======================================
Files 456 456
Lines 18636 18636
Branches 2813 2813
=======================================
Hits 15599 15599
Misses 2134 2134
Partials 903 903 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
joshdholtz
force-pushed
the
feat/window-size-selection-reconcile
branch
from
September 8, 2026 20:43
307fa8a to
728a0ff
Compare
…d package Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both paths restore selections that were resolved before the bounds were known, so a window-hidden package could become selected without a bounds change to trigger the reconcile effect. The effect now also keys on the locally computed bounds inside the measurer, avoiding stale captures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
joshdholtz
force-pushed
the
feat/window-size-selection-reconcile
branch
from
September 11, 2026 16:52
2fc1831 to
712faad
Compare
- Keep-check now evaluates the current package as selected, in the active tab's context, under the screen condition the measured width implies — so packages visible via Medium/Expanded/Selected overrides are no longer deselected on resize, and a visible copy on an inactive tab no longer masks a hidden copy on the active one. - Replacement resolution shares the sheet-dismiss priority (tab's authored default first) and skips candidates hidden at the current size class. - peekDefaultPackageUniqueIdAfterSheetDismiss evaluates at the measured bounds, including remembered ids, so sheet analytics and the reset land on the same package. - MeasurePaywallBounds' reconcile effect is keyed on the states list, so prewarmed workflow steps joining after first measure reconcile too. - Reconcile also runs when a tab switch keeps an outside-tabs selection. - The nothing-visible test now uses the direct-constructor state, which has no synthesized always-visible duplicates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ 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 301466a. Configure here.
- Reconcile and peek now use the app-window screen condition (set by MeasurePaywallBounds next to the bounds) instead of deriving one from the paywall bounds — the two can fall in different size classes in a sheet or pane. - A selection is kept while ANY on-screen copy of its uniqueId renders visible, so a hidden outside-tabs copy no longer masks a visible copy on the active tab (and vice versa). - The sheet-dismiss peek prefers a bounds-visible outside-tabs package before falling back to the unchecked init-time id. - FakePaywallState no longer synthesizes a duplicate always-visible component for packages the test already declares — the duplicates defeated visibility-rule assertions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
joshdholtz
marked this pull request as draft
September 15, 2026 17:53
aelaguiz
pushed a commit
to funcountry/purchases-android
that referenced
this pull request
Sep 19, 2026
…t#4193) ### Checklist - [x] If applicable, unit tests - [x] If applicable, create follow-up issues for `purchases-ios` and hybrids (iOS: RevenueCat/purchases-ios#7645; hybrids need no changes) ### Motivation Let one paywall adapt its layout to the window it renders in (tablets, foldables, multi-window) — the Android side of upcoming responsive paywall rules in the dashboard. ### Description - New override conditions: `WindowWidthRule`, `WindowHeightRule`, `WindowAspectRatioRule` (`window_width_condition` etc.) — operators `>=` `>` `<=` `<` `=`, values in dp, ratio is width / height (>1 = landscape) - Conditions AND together (`width >= 700` + `height >= 480` = large windows, not landscape phones) - Evaluated against the paywall's own measured bounds (same as iOS): a paywall in a sheet, dialog, or pane matches its own size. An unbounded axis (fit-content sheet height) falls back to the window dimension. Re-evaluates live on rotation/resize/multi-window - Older SDKs decode them as `Unsupported` → default layout; unknown window size never matches - Snapshot coverage twice over: Compose previews (CI snapshot pipeline) + Paparazzi goldens — phone portrait stacked, phone landscape stacked (height floor), tablets side by side⚠️ Reviewer notes: - Selection gap (a rule hiding the currently selected package leaves it selected and purchasable) is fixed in the stacked follow-up RevenueCat#4198, mirroring iOS's reconcile-on-resize. - Earlier revisions evaluated the app window; after review feedback this now measures the paywall's own bounds, matching iOS. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Wide changes to override resolution and layout roots across paywall rendering, though behavior is gated on new dashboard rules and covered by tests; package selection still ignores window-size rules (follow-up noted for hidden-selection edge cases). > > **Overview** > Adds **responsive paywall overrides** driven by measured paywall size (not the full app window), aligned with iOS: width, height, and aspect-ratio rules with `>=` / `>` / `<=` / `<` / `=` on dp values, AND-combined within an override. > > The **purchases** layer gains `ComparisonOperator` and three new serialized condition types (`window_width_condition`, etc.), marked as rules so bad payloads still fall back safely. **RevenueCat UI** measures bounds via `MeasurePaywallBounds` into `paywallBoundsDp`, threads that through `ConditionContext` into every component state, and evaluates comparisons (including epsilon for `=`) while size is unknown → no match until the first measure. > > Standard and **workflow** paywalls wrap their scaffolds in the measurer so headers see bounds on frame one. Previews, Paparazzi snapshots, and broad unit tests cover deserialization, operator edge cases, and a split-layout fixture (stacked vs side-by-side at 700×480dp). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a25cc1a. 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 Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacked on #4193. Closes the selection gap flagged in review: a package hidden by a window size rule could stay selected and purchasable.
What
reconcileSelectionForWindowSizeonPaywallState.Loaded.Components: when the measured paywall bounds change and the selected package no longer resolves visible, selection moves to the authored default if it renders, else the first visible package. A visible selection is never moved; if nothing is visible, selection stays put.MeasurePaywallBounds, keyed on the measured bounds — also covers the initial selection, which is resolved before the bounds are known.resolvesVisibleand the default-selection helpers take an optional measured size; the initial-selection path is unchanged (window unknown).Design note
When nothing resolves visible at the current size, the hidden selection is kept — matching iOS. Rules that hide every package at some size are an authoring error; the plan is to guard against that in the dashboard (validation when window rules can leave zero visible packages) rather than guess at runtime.
Scope note for reviewers
Size-class (screen condition) rules have the same gap and keep their existing pinned-to-COMPACT behavior — reconciling those changes behavior of already-shipped paywalls, so it's left as a deliberate follow-up decision. Mirrors iOS
reconcileSelection(window-size scope).Tests
WindowSizeSelectionReconcileTests: moves off a hidden package, keeps a visible one, no-op on unknown size, keeps selection when nothing is visible, prefers a visible authored default, no flapping on resize-back, height rules.PaywallStateLoadedComponentsPackageSelectionTests: tab switch onto a hidden remembered default, sheet-dismiss reset restoring a hidden default, user-selected package hidden by a rule.🤖 Generated with Claude Code
Note
Medium Risk
Changes which package is selected for purchase across resize, tabs, and sheet dismiss, but behavior is guarded by extensive tests and explicit iOS parity rules.
Overview
Fixes a gap where a package hidden by window width/height or size-class visibility rules could remain selected and purchasable after bounds change, tab switch, or sheet reset—initial selection still runs before measure.
reconcileSelectionForWindowSizeonPaywallState.Loaded.Componentsre-checks whether the current selection would render visible (using measured paywall bounds for dimension rules andwindowScreenConditionfrom the app window for compact/medium/expanded). If not, selection moves to the tab’s authored default, then other visible fallbacks; if nothing is visible, selection stays put (iOS parity). Replacement candidates are evaluated unselected; the keep-check uses selected view state soselected-condition rules don’t oscillate.MeasurePaywallBoundsnow writeswindowScreenConditionand runs reconciliation in aLaunchedEffectkeyed on bounds, screen condition, and state list (including prewarmed workflow states). Tabupdate,resetToDefaultPackage, andpeekDefaultPackageUniqueIdAfterSheetDismissuse the same visibility-aware helpers so analytics and dismiss match reset behavior.resolvesVisible/ default-selection helpers accept optional measured size and screen condition;FakePaywallStatestops synthesizing extra always-visible package components that broke visibility tests. New unit and Compose window-resize tests cover resize, tabs, medium/selected rules, and late-added states.Reviewed by Cursor Bugbot for commit 1763d7b. Bugbot is set up for automated code reviews on this repo. Configure here.