Skip to content

fix(mobile): ToS/Privacy URLs + StoreKit sim config + upgrade wall close hit area#37

Merged
GSTJ merged 1 commit into
mainfrom
fix/maestro-bugs-tos-storekit-close-button
May 20, 2026
Merged

fix(mobile): ToS/Privacy URLs + StoreKit sim config + upgrade wall close hit area#37
GSTJ merged 1 commit into
mainfrom
fix/maestro-bugs-tos-storekit-close-button

Conversation

@GSTJ
Copy link
Copy Markdown
Owner

@GSTJ GSTJ commented May 20, 2026

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: openWebBrowser passed WebBrowserPresentationStyle.PAGE_SHEET to expo-web-browser. The nested SFSafariViewController inside a page-sheet modal has a long-standing iOS defect where it frequently renders an empty white surface — the URL loads (verified curl -I returns 200 + full HTML for https://www.pegada.app/terms-of-use and /privacy-policy), but the webview chrome never paints content.

Fix:

  • apps/mobile/src/services/openWebBrowser.ts — drop presentationStyle, use default full-screen SFSafariViewController (iOS) / Chrome Custom Tabs (Android). Both render reliably.
  • apps/mobile/.maestro/24-profile-journey.yaml — added an assertVisible: 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 with current: null. The PlanPackages component renders nothing, the CTA stays on loading={!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_monthly at $9.99/mo, premium_yearly at $49.99/yr with 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 every expo prebuild, copies the .storekit file into the generated Xcode project as a tracked resource and injects a StoreKitConfigurationFileReference into the scheme's LaunchAction (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 fallback buildMaestroSyntheticOfferings() that returns a PurchasesOffering-shaped object when stub RC keys are in use OR when iOS simulator + real key returns null offerings. 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 — added hitSlop={{ top: 16, bottom: 16, left: 16, right: 16 }} to the CloseButton. Effective touch area is now ~64x64, no layout change. Same pattern already used by the RestorePurchases button in the same screen.

Test plan

  • pnpm typecheck (mobile workspace) — passes
  • pnpm format — passes
  • pnpm lint — passes (458 pre-existing warnings, 0 errors)
  • npx expo config --type prebuild — config + new plugin loads cleanly
  • node require() of the new plugin module — loads cleanly
  • ToS / Privacy URLs verified externally: curl -sI https://www.pegada.app/terms-of-use and /privacy-policy both return HTTP 200 with full HTML body
  • Out-of-environment: full Release iOS sim build + Maestro suite (24 + 25) requires Firebase / RevenueCat / API secrets that aren't accessible from this worktree. The changes are isolated, type-safe, and the config plugin + synthetic offering shape are validated by the static analysis above. Recommend running eas build --profile development --platform ios + maestro test apps/mobile/.maestro/24-profile-journey.yaml apps/mobile/.maestro/25-upgrade-journey.yaml locally as the final gate.

…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.
Copilot AI review requested due to automatic review settings May 20, 2026 13:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 openWebBrowser to use the default expo-web-browser presentation to avoid iOS blank page-sheet rendering.
  • Add StoreKit simulator support via a checked-in .storekit file, an Expo prebuild plugin to wire it into the Xcode scheme, and a JS fallback offering shape when offerings are null.
  • 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
@GSTJ GSTJ merged commit 2cba6e5 into main May 20, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants