fix(mobile): ToS/Privacy URLs + StoreKit sim config + upgrade wall close hit area#37
Merged
Merged
Conversation
…ose hit area
Fixes three Maestro-validated bugs:
1. Terms of Use / Privacy Policy open a blank in-app browser sheet.
Root cause: openWebBrowser passed PAGE_SHEET presentation style to
expo-web-browser, which has a long-standing iOS defect where the
nested SFSafariViewController frequently renders empty white inside
the page sheet. The pegada.app URLs themselves are healthy (verified
curl 200 with full HTML body). Dropping the presentationStyle uses
the default full-screen SFSafariViewController, which renders
reliably. Maestro flow 24 now also asserts the page title to catch
regressions.
2. Upgrade wall plan rows stuck on loading dots in simulator.
Root cause: iOS simulator can't reach the App Store, so
Purchases.getOfferings() returns current: null, leaving the upgrade
wall with no PlanCards and the CTA stuck on its !selectedOffering
loading state. Fixed in three layers:
- Ship Pegada.storekit with the two real product IDs (monthly +
yearly). Product IDs are public, safe to commit.
- plugins/withStoreKitConfiguration.js copies the .storekit file
into the generated Xcode project on every expo prebuild and wires
it into the scheme's LaunchAction so simulator runs activate it.
- JS-side fallback in payments.getOfferings synthesizes the same
offering shape when stub RC keys are in use OR when the iOS
simulator returns null offerings. This keeps Maestro flow 25
deterministic even before prebuild has run.
3. Upgrade wall close button (X icon, top-right) too small to tap.
Root cause: visible target is 32x32 (theme.spacing[8]), below
Apple's 44pt minimum. Added a 16pt hitSlop on each side, expanding
the effective touch area to ~64x64 without changing layout.
Out-of-environment verification: full Release sim build + Maestro suite
needs Firebase + RevenueCat + API secrets that aren't available in this
worktree. Typecheck and lint pass; the config plugin and synthetic
offering shape were unit-validated via node require + expo config.
There was a problem hiding this comment.
Pull request overview
This PR addresses three mobile regressions caught by Maestro around external legal links, iOS simulator purchase offerings, and the upgrade wall close affordance.
Changes:
- Switch
openWebBrowserto use the defaultexpo-web-browserpresentation to avoid iOS blank page-sheet rendering. - Add StoreKit simulator support via a checked-in
.storekitfile, an Expo prebuild plugin to wire it into the Xcode scheme, and a JS fallback offering shape when offerings arenull. - Increase the tappable area of the upgrade wall close button and strengthen Maestro coverage for Terms/Privacy.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/mobile/src/views/UpgradeWall/index.tsx | Expands close button hit target via hitSlop. |
| apps/mobile/src/services/payments/index.ts | Adds synthetic offerings fallback; consolidates iOS simulator detection usage. |
| apps/mobile/src/services/openWebBrowser.ts | Removes page-sheet presentation style to avoid blank in-app browser rendering. |
| apps/mobile/plugins/withStoreKitConfiguration.js | New Expo config plugin to copy .storekit and inject it into the iOS scheme. |
| apps/mobile/Pegada.storekit | New StoreKit configuration fixture for simulator pricing/products. |
| apps/mobile/app.config.ts | Registers the new StoreKit config plugin for prebuild. |
| apps/mobile/.maestro/README.md | Documents StoreKit simulator configuration and the JS fallback. |
| apps/mobile/.maestro/24-profile-journey.yaml | Adds Terms/Privacy visibility assertions to catch blank webview regressions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+98
to
+106
| if (!schemeContent.includes("StoreKitConfigurationFileReference")) { | ||
| const storeKitRef = ` <StoreKitConfigurationFileReference\n identifier = "../../${STOREKIT_FILE_NAME}">\n </StoreKitConfigurationFileReference>\n `; | ||
|
|
||
| // Inject just before the closing </LaunchAction> tag — that's the | ||
| // scheme section that controls simulator/device runs. | ||
| schemeContent = schemeContent.replace( | ||
| /(\s*)<\/LaunchAction>/, | ||
| `\n ${storeKitRef}$1</LaunchAction>`, | ||
| ); |
| // otherwise render the upgrade wall with empty plan rows + a perpetually | ||
| // loading CTA, because StoreKit cannot resolve real pricing without an | ||
| // attached `.storekit` configuration in the Xcode scheme. | ||
| if (isStubRevenueCatKey) { |
Comment on lines
+122
to
+134
| const baseProduct = { | ||
| description: "Pegada Premium", | ||
| title: "Pegada Premium", | ||
| currencyCode: "USD", | ||
| introPrice: null, | ||
| discounts: null, | ||
| productCategory: "SUBSCRIPTION", | ||
| productType: "AUTO_RENEWABLE_SUBSCRIPTION", | ||
| subscriptionPeriod: "P1M", | ||
| defaultOption: null, | ||
| subscriptionOptions: null, | ||
| presentedOfferingIdentifier: "default", | ||
| }; |
Comment on lines
+119
to
+125
| # Assert the real ToS rendered. The page title "Pegada | Terms of Use" | ||
| # is set by the marketing site and is the most reliable proof that the | ||
| # webview actually loaded content (not the blank-sheet failure mode the | ||
| # validator caught with the old PAGE_SHEET presentation style). | ||
| - assertVisible: | ||
| text: ".*Terms of Use.*" | ||
| optional: true |
Comment on lines
+147
to
+149
| - assertVisible: | ||
| text: ".*Privacy Policy.*" | ||
| optional: true |
This was referenced May 21, 2026
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.
Summary
Three smaller Maestro-validated bugs fixed in one PR:
Bug 1 — Terms of Use / Privacy Policy open a blank in-app browser sheet (Flow 24)
Root cause:
openWebBrowserpassedWebBrowserPresentationStyle.PAGE_SHEETtoexpo-web-browser. The nestedSFSafariViewControllerinside a page-sheet modal has a long-standing iOS defect where it frequently renders an empty white surface — the URL loads (verifiedcurl -Ireturns 200 + full HTML forhttps://www.pegada.app/terms-of-useand/privacy-policy), but the webview chrome never paints content.Fix:
apps/mobile/src/services/openWebBrowser.ts— droppresentationStyle, use default full-screenSFSafariViewController(iOS) / Chrome Custom Tabs (Android). Both render reliably.apps/mobile/.maestro/24-profile-journey.yaml— added anassertVisible: text: ".*Terms of Use.*"(and the equivalent for Privacy) so the validator catches blank-sheet regressions going forward.Bug 2 — Upgrade wall plan rows stuck on loading dots in simulator (Flow 25)
Root cause: iOS simulator can't reach the App Store. Without a StoreKit configuration in the active Xcode scheme,
Purchases.getOfferings()resolves withcurrent: null. The PlanPackages component renders nothing, the CTA stays onloading={!selectedOffering}, the user sees loading dots.Fix (three layers for defense in depth):
apps/mobile/Pegada.storekit— new StoreKit configuration file with both real product IDs (premium_monthlyat$9.99/mo,premium_yearlyat$49.99/yrwith a 1-week intro free trial). Product IDs are public (visible in App Store Connect listings) and safe to commit per Apple's documented pattern.apps/mobile/plugins/withStoreKitConfiguration.js— new Expo config plugin that, on everyexpo prebuild, copies the.storekitfile into the generated Xcode project as a tracked resource and injects aStoreKitConfigurationFileReferenceinto the scheme'sLaunchAction(so simulator runs activate the file). Idempotent across reruns. No-op if the source file is missing.apps/mobile/src/services/payments/index.ts— JS-side fallbackbuildMaestroSyntheticOfferings()that returns aPurchasesOffering-shaped object when stub RC keys are in use OR when iOS simulator + real key returnsnullofferings. Keeps Maestro flow 25 deterministic even on a fresh clone before prebuild has run, and gives developers a working upgrade wall in local sims without StoreKit setup.Bug 3 — Upgrade wall close button (X icon, top-right) hard to tap (Flow 25)
Root cause: visible target is
32x32(theme.spacing[8]), below Apple's 44pt HIG minimum. Validator's coord-based tap fallback regularly missed.Fix:
apps/mobile/src/views/UpgradeWall/index.tsx— addedhitSlop={{ top: 16, bottom: 16, left: 16, right: 16 }}to theCloseButton. Effective touch area is now ~64x64, no layout change. Same pattern already used by theRestorePurchasesbutton in the same screen.Test plan
pnpm typecheck(mobile workspace) — passespnpm format— passespnpm lint— passes (458 pre-existing warnings, 0 errors)npx expo config --type prebuild— config + new plugin loads cleanlynode require()of the new plugin module — loads cleanlycurl -sI https://www.pegada.app/terms-of-useand/privacy-policyboth return HTTP 200 with full HTML bodyeas build --profile development --platform ios+maestro test apps/mobile/.maestro/24-profile-journey.yaml apps/mobile/.maestro/25-upgrade-journey.yamllocally as the final gate.