Skip to content

feat(ramp): make HeadlessHost invisible (Phase 9.5)#30104

Open
saustrie-consensys wants to merge 11 commits into
mainfrom
saustrie-consensys/headless-buy-phase-9.5
Open

feat(ramp): make HeadlessHost invisible (Phase 9.5)#30104
saustrie-consensys wants to merge 11 commits into
mainfrom
saustrie-consensys/headless-buy-phase-9.5

Conversation

@saustrie-consensys
Copy link
Copy Markdown
Contributor

@saustrie-consensys saustrie-consensys commented May 13, 2026

Description

Lands Phase 9.5 of the incremental Headless Buy plan (app/components/UI/Ramp/headless/PLAN.md). Independent of Phase 9 (#30103) in runtime. This PR is now based directly on main, so either PR can land first.

What ships

  1. HeadlessHost invisible (7be267ed). Strip header, spinner, "no session" / error text, and Cancel button from HeadlessHost.tsx. The Host renders a transparent View so it keeps acting as the routing landing pad and the nativeFlowError callback surface, while the consumer (TPC) renders the only user-visible loading UI for a headless buy. Phase 8's dismissal contract is preserved: useHeadlessSessionDismissal's unmount cleanup still fires onClose({ reason: 'user_dismissed' }). We additionally register a navigation.addListener('beforeRemove', ...) so the synchronous closeSession fire that used to live on the visible Cancel/Back buttons still happens before unmount on hardware back / iOS swipe-back. Orphaned headless_host.* i18n keys removed across all 14 locales.

  2. Fix Inject inpage js #1 — Transparent stack wrapper RAMP.HEADLESS_ENTRY (ba35d1f2). After the chrome strip, Goktug's May 12 testing showed the user still sees a solid-white screen because MainNavigator.js mounts the headless flow under RAMP.TOKEN_SELECTION, an opaque V2 stack card that sits between the consumer screen and the now-transparent Host. Fix: add a new outer slot RAMP.HEADLESS_ENTRY that mounts the same TokenListRoutes component, but wraps it in the JS-stack transparent-overlay preset already used by BridgeModals, EarnModals, MoneyModals, StakeModals, and PerpsModals:

    options={{
      ...clearStackNavigatorOptionsWithTransitionAnimation,
      presentation: 'transparentModal',
    }}

    useHeadlessBuy.startHeadlessBuy now navigates to HEADLESS_ENTRY instead of TOKEN_SELECTION; the nested-screen descriptor stays the same so it still lands directly on the Host. UB2's entry through TOKEN_SELECTION is untouched.

  3. Fix A — getUserLimits swallow → failSession (4f5ddfbd). useTransakRouting.checkUserLimits previously caught errors from getUserLimits, rethrew LimitExceededError, and swallowed everything else (network blips, malformed responses). In headless mode the consumer got no onError callback when this fired. Fix: gate the new behavior on headlessSessionId — route swallowed infrastructure errors through failSession(headlessSessionId, error) and rethrow so the caller's outer catch unwinds the flow. UB2's existing swallow stays in place; the existing test at useTransakRouting.test.ts:957-982 (which encodes the UB2 swallow as intent) continues to pass. Two new tests cover the headless paths.

  4. Cursor Bugbot fix — RESET-action guard in beforeRemove (1c5b205b). Bugbot flagged the beforeRemove listener as high-severity: useTransakRouting.reset() re-pins HEADLESS_HOST at the base when routing to VerifyIdentity / BasicInfo / Checkout / KycWebview. The reset fires beforeRemove on the OLD HEADLESS_HOST instance before re-pinning the new one — but the session is still in flight; closing it there prematurely fires onClose({ reason: 'user_dismissed' }) and breaks the flow. Fix: inspect e.data.action.type inside the listener and short-circuit on 'RESET'. Legitimate user-dismissal paths (GO_BACK, POP) still close the session synchronously; other unmount cases stay caught by useHeadlessSessionDismissal's unmount path with isHeadlessHostStillInNavigator.

Was originally in scope, moved to Phase 9 PR

  • The pasted-plan placeholder "Fix B (getProviderBuyLimit belt-and-braces pre-flight)" landed in #30103 as feat(ramp): pre-quote static bounds check + expose getProviderBuyLimit, with a more comprehensive implementation than this PR originally drafted (UB2-parity skip on amount <= 0, conservative "all candidates reject" rule, details.source discriminator, plus a public getProviderBuyLimit re-export). Phase 9.5 doesn't duplicate it.

Changelog

CHANGELOG entry: see CHANGELOG.md [Unreleased] for the three entries (Phase 9.5 invisible Host + RAMP.HEADLESS_ENTRY under Changed; Fix A + Cursor Bugbot RESET-guard under Fixed). Full text below.

### Changed
- Made the Ramp Headless Host invisible so consumer-rendered loading UI is visible during a headless buy; added a transparent RAMP.HEADLESS_ENTRY outer route so the headless flow no longer renders an opaque V2 stack card on top of the consumer screen (Phase 9.5).

### Fixed
- Route swallowed getUserLimits infrastructure errors through failSession when a headless Ramp session is active, so headless consumers receive an onError callback instead of a silent hang (Phase 9.5 Fix A).
- Skip the beforeRemove close path on RESET actions in the Ramp Headless Host so useTransakRouting's stack rebuilds (re-pinning HEADLESS_HOST when routing to VerifyIdentity / BasicInfo / Checkout / KycWebview) no longer prematurely fire onClose({ reason: 'user_dismissed' }) (Phase 9.5; Cursor Bugbot finding).

Related issues

Manual testing steps

Static / automated (verified locally):

  • yarn lint — clean on changed files.
  • yarn lint:tsc — clean (the only TS errors are pre-existing termsOfUseContent unrelated to this PR).
  • yarn jest app/components/UI/Ramp/headless/useHeadlessBuy.test.ts app/components/UI/Ramp/hooks/useTransakRouting.test.ts app/components/UI/Ramp/Views/HeadlessHost/HeadlessHost.test.tsx — 117/117 passing. Scoped coverage on the three changed files: 97% statements / 85% branches / 98% functions / 97% lines.

Manual — runtime, iOS + Android:

Feature: Phase 9.5 — invisible HeadlessHost + transparent entry

  Scenario: Consumer screen stays visible behind the headless flow (Fix #1)
    Given the Headless Playground (or a downstream MMPay flow) drives a headless buy
    When startHeadlessBuy navigates to RAMP.HEADLESS_ENTRY
    Then the user sees the consumer screen behind the Transak checkout sheet
    And there is no opaque white V2 card between them

  Scenario: User backs out via hardware back / iOS swipe-back
    Given an active headless session with the Host on top of the navigation stack
    When the user backs out
    Then the beforeRemove listener fires `onClose({ reason: 'user_dismissed' })` synchronously
    And the session is removed from the registry

  Scenario: useTransakRouting stack rebuild does NOT close the session (Bugbot fix)
    Given an in-flight headless session (e.g. transitioning to VerifyIdentity)
    When useTransakRouting calls navigation.reset() to re-pin HEADLESS_HOST
    Then the beforeRemove listener inspects e.data.action.type === 'RESET' and short-circuits
    And the session stays live; the consumer does NOT receive a premature onClose

  Scenario: Flaky getUserLimits in headless mode (Fix A)
    Given a headless session whose checkUserLimits hits a non-LimitExceededError from getUserLimits
    When the catch runs
    Then failSession(headlessSessionId, error) fires onError({ code: 'UNKNOWN' })
    And the rethrow unwinds the flow (no silent hang)

  Scenario: OTP failure surfaces as AUTH_FAILED (pre-existing)
    Given an in-flight headless session in the OTP loop
    When OtpCode resets back to the Host with nativeFlowError
    Then the consumer receives onError({ code: 'AUTH_FAILED' }) followed by onClose({ reason: 'unknown' })
    And no visible error UI is rendered by the Host

  Scenario: UB2 unaffected (Fix #1 / Fix A cross-check)
    When the user opens the regular UB2 Buy flow (Wallet Home → Buy)
    Then the V2 card still renders opaque
    And UB2's checkUserLimits swallow behavior is preserved (no new error toasts on flaky getUserLimits)

Screenshots/Recordings

Before

HeadlessHost rendered a header, spinner, loading text, and a Cancel button while the buy flow was in flight. The consumer screen was hidden behind a solid-white V2 card (Goktug's "empty screen.mov" report).

After

HeadlessHost is fully transparent AND its wrapping stack card is now a transparentModal, so the consumer's loading UI fills the screen behind the headless flow. Functional behaviour (orchestration, dismissal, nativeFlowError) is unchanged. useTransakRouting's stack rebuilds no longer close the session prematurely.

Screen.Recording.2026-05-14.at.1.57.37.PM.mov

Pre-merge author checklist

  • I've followed MetaMask Contributor Docs and MetaMask Mobile Coding Standards.
  • I've completed the PR template to the best of my ability
  • I've included tests if applicable (HeadlessHost / useHeadlessBuy / useTransakRouting suites cover the chrome strip, Fix Inject inpage js #1, Fix A, and the RESET-guard regression; 117 tests, ~97% line coverage on changed files)
  • I've documented my code using JSDoc format if applicable
  • I've applied the right labels on the PR (team-money-movement)

Performance checks (if applicable)

  • I've tested on Android
  • I've tested with a power user scenario
  • I've instrumented key operations with Sentry traces for production performance metrics

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Changes headless Ramp buy navigation and session lifecycle handling (dismissal, error propagation, touch-through) across multiple screens; mistakes could prematurely close sessions or leave users stuck in the flow.

Overview
Headless buy UI is now effectively invisible. HeadlessHost is stripped down to a transparent container view (no header/spinner/buttons/text) while preserving orchestration, adding a beforeRemove listener to synchronously close the session on user back (but skipping RESET actions).

Introduces a transparent stack entry for headless flows. Adds Routes.RAMP.HEADLESS_ENTRY in MainNavigator (mounted as a transparentModal) and updates useHeadlessBuy to navigate through it so the consumer screen stays visible behind the flow.

Hardens headless session flow behavior. Adds headlessEntryNavigation helpers (dismissHeadlessFlow, setHeadlessEntryCardTouchThrough) and updates Checkout/useTransakRouting to use them for consistent dismissal, prevent double-termination, treat empty callbacks as user dismissal, propagate getUserLimits infra errors via failSession in headless mode, and pass headlessSessionId through Checkout routing. Tests are updated/added, and the headless playground shows an in-button spinner for the active quote.

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

@saustrie-consensys saustrie-consensys requested a review from a team as a code owner May 13, 2026 13:22
@github-actions github-actions Bot added the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label May 13, 2026
@metamaskbotv2 metamaskbotv2 Bot added the team-money-movement issues related to Money Movement features label May 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbotv2 metamaskbotv2 Bot added the INVALID-PR-TEMPLATE PR's body doesn't match template label May 13, 2026
Comment thread app/components/UI/Ramp/Views/HeadlessHost/HeadlessHost.tsx
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.74%. Comparing base (4257d6a) to head (170b4a1).

Additional details and impacted files
@@                             Coverage Diff                             @@
##           saustrie-consensys/headless-buy-phase-9   #30104      +/-   ##
===========================================================================
- Coverage                                    81.75%   81.74%   -0.01%     
===========================================================================
  Files                                         5388     5388              
  Lines                                       143619   143607      -12     
  Branches                                     32803    32798       -5     
===========================================================================
- Hits                                        117410   117396      -14     
- Misses                                       18185    18191       +6     
+ Partials                                      8024     8020       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

saustrie-consensys added a commit that referenced this pull request May 13, 2026
…or Bugbot)

Phase 9.5's chrome-strip commit added a beforeRemove navigation listener on
HeadlessHost that fires closeSession({ reason: 'user_dismissed' }) on every
beforeRemove event. Cursor Bugbot caught a high-severity bug: useTransakRouting
uses navigation.reset() to re-pin HEADLESS_HOST at the base of the stack when
moving to VerifyIdentity / BasicInfo / Checkout / KycWebview. The reset action
fires beforeRemove on the OLD HEADLESS_HOST instance before re-pinning the new
one, but the session is still in flight — closing it here prematurely fires
onClose({ reason: 'user_dismissed' }) and breaks the consumer's flow.

Resolution: inspect e.data.action.type inside the listener and short-circuit on
'RESET'. Legitimate user-dismissal paths (GO_BACK, POP) still close the session
synchronously. Other unmount cases (real stack reset that does NOT re-pin the
Host, hot reload) remain caught by useHeadlessSessionDismissal's unmount
cleanup, which uses isHeadlessHostStillInNavigator to differentiate.

Adds a HeadlessHost.test.tsx case 'does NOT close the session when beforeRemove
fires for a RESET action (stack rebuild guard)'. Updates the existing test
helper to pass a non-RESET action in the event arg so the listener's new
signature stays exercised.

Bug report: #30104 (review)
@saustrie-consensys saustrie-consensys force-pushed the saustrie-consensys/headless-buy-phase-9.5 branch from 170b4a1 to 1c5b205 Compare May 13, 2026 16:13
@saustrie-consensys saustrie-consensys requested a review from a team as a code owner May 13, 2026 16:13
@github-actions github-actions Bot added size-L and removed size-M labels May 13, 2026
saustrie-consensys added a commit that referenced this pull request May 13, 2026
…or Bugbot)

Phase 9.5's chrome-strip commit added a beforeRemove navigation listener on
HeadlessHost that fires closeSession({ reason: 'user_dismissed' }) on every
beforeRemove event. Cursor Bugbot caught a high-severity bug: useTransakRouting
uses navigation.reset() to re-pin HEADLESS_HOST at the base of the stack when
moving to VerifyIdentity / BasicInfo / Checkout / KycWebview. The reset action
fires beforeRemove on the OLD HEADLESS_HOST instance before re-pinning the new
one, but the session is still in flight — closing it here prematurely fires
onClose({ reason: 'user_dismissed' }) and breaks the consumer's flow.

Resolution: inspect e.data.action.type inside the listener and short-circuit on
'RESET'. Legitimate user-dismissal paths (GO_BACK, POP) still close the session
synchronously. Other unmount cases (real stack reset that does NOT re-pin the
Host, hot reload) remain caught by useHeadlessSessionDismissal's unmount
cleanup, which uses isHeadlessHostStillInNavigator to differentiate.

Adds a HeadlessHost.test.tsx case 'does NOT close the session when beforeRemove
fires for a RESET action (stack rebuild guard)'. Updates the existing test
helper to pass a non-RESET action in the event arg so the listener's new
signature stays exercised.

Bug report: #30104 (review)
@saustrie-consensys saustrie-consensys force-pushed the saustrie-consensys/headless-buy-phase-9.5 branch from 1c5b205 to 32f24df Compare May 13, 2026 19:04
saustrie-consensys added a commit that referenced this pull request May 14, 2026
…or Bugbot)

Phase 9.5's chrome-strip commit added a beforeRemove navigation listener on
HeadlessHost that fires closeSession({ reason: 'user_dismissed' }) on every
beforeRemove event. Cursor Bugbot caught a high-severity bug: useTransakRouting
uses navigation.reset() to re-pin HEADLESS_HOST at the base of the stack when
moving to VerifyIdentity / BasicInfo / Checkout / KycWebview. The reset action
fires beforeRemove on the OLD HEADLESS_HOST instance before re-pinning the new
one, but the session is still in flight — closing it here prematurely fires
onClose({ reason: 'user_dismissed' }) and breaks the consumer's flow.

Resolution: inspect e.data.action.type inside the listener and short-circuit on
'RESET'. Legitimate user-dismissal paths (GO_BACK, POP) still close the session
synchronously. Other unmount cases (real stack reset that does NOT re-pin the
Host, hot reload) remain caught by useHeadlessSessionDismissal's unmount
cleanup, which uses isHeadlessHostStillInNavigator to differentiate.

Adds a HeadlessHost.test.tsx case 'does NOT close the session when beforeRemove
fires for a RESET action (stack rebuild guard)'. Updates the existing test
helper to pass a non-RESET action in the event arg so the listener's new
signature stays exercised.

Bug report: #30104 (review)
@saustrie-consensys saustrie-consensys force-pushed the saustrie-consensys/headless-buy-phase-9.5 branch from 32f24df to 2a70758 Compare May 14, 2026 16:32
@saustrie-consensys saustrie-consensys requested review from a team as code owners May 14, 2026 16:32
@saustrie-consensys saustrie-consensys changed the base branch from saustrie-consensys/headless-buy-phase-9 to main May 14, 2026 16:33
Comment thread CHANGELOG.md Outdated

### Changed

- Made the Ramp Headless Host invisible so consumer-rendered loading UI is visible during a headless buy; added a transparent `RAMP.HEADLESS_ENTRY` outer route so the headless flow no longer renders an opaque V2 stack card on top of the consumer screen (Phase 9.5).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe we should update Changelog only for user facing changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed the two internal implementation-detail entries from the [Unreleased] ### Fixed section (getUserLimits error routing and beforeRemove RESET guard) — only the user-facing invisible HeadlessHost change remains under ### Changed. Done in 2a0d75e.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot Remove all changes to changelog

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All CHANGELOG.md changes removed in d4669b8. The [Unreleased] section is now back to the original empty state.

Phase 9.5 (HeadlessHost goes invisible)
  - Strip header, spinner, "no session" / error text, and Cancel button
    from `HeadlessHost.tsx`. The Host renders a transparent `View` so it
    keeps acting as the routing landing pad and `nativeFlowError` surface
    while the consumer (TPC) renders the only user-visible loading UI.
  - Replace the visible Cancel/Back-button handlers with a
    `navigation.addListener('beforeRemove', ...)` so the synchronous
    `closeSession({ reason: 'user_dismissed' })` still fires before
    unmount on hardware back / iOS swipe-back.
    `useHeadlessSessionDismissal` (Phase 8) remains as
    defense-in-depth for paths that bypass `beforeRemove`.
  - Remove orphaned `headless_host.*` i18n keys across all 14 locales.

CHANGELOG entry: null
After the Phase 9.5 chrome strip made HeadlessHost invisible, Goktug's
testing showed the user still sees a solid-white screen because the
headless flow enters via RAMP.TOKEN_SELECTION — an opaque V2 stack card
that sits between the consumer screen and the now-transparent Host.

Resolution: add a new outer slot RAMP.HEADLESS_ENTRY in MainNavigator
that mounts the same TokenListRoutes component as TOKEN_SELECTION, but
wraps it in the JS-stack transparent-overlay preset that BridgeModals,
EarnModals, MoneyModals, StakeModals, and PerpsModals already use:

  options={{
    ...clearStackNavigatorOptionsWithTransitionAnimation,
    presentation: 'transparentModal',
  }}

useHeadlessBuy.startHeadlessBuy now navigates to HEADLESS_ENTRY instead
of TOKEN_SELECTION; the nested-screen descriptor that lands directly on
HEADLESS_HOST stays unchanged. UB2's entry through TOKEN_SELECTION is
untouched.

Slack thread:
https://consensys.slack.com/archives/C0AK3NXRM7W/p1778577403631269
…Phase 9.5 Fix A)

useTransakRouting.checkUserLimits previously caught errors from
getUserLimits, rethrew LimitExceededError, and swallowed every other
error (Logger.error). In headless mode the consumer got no onError
callback when this fired for an infrastructure failure (network blip,
malformed response), leaving the session hung.

Resolution: in the catch, gate the new behavior on headlessSessionId:

  if (error instanceof LimitExceededError) throw error;
  if (headlessSessionId) {
    failSession(headlessSessionId, error);
    throw error;
  }
  Logger.error(error as Error, 'Failed to check user limits');

UB2's existing swallow is preserved — the existing test
'logs error and returns when getUserLimits throws a non-limit error'
encodes the swallow as intent and continues to pass. Two new tests
cover (a) failSession invoked + rethrow propagates in headless mode and
(b) LimitExceededError short-circuits before failSession.

The parallel getUserLimits swallow in useDepositRouting.ts:137-180 is
deliberately left untouched (Deposit is not a headless route, and the
"ramps work is UB2-only" rule keeps us off the Deposit tree).
…or Bugbot)

Phase 9.5's chrome-strip commit added a beforeRemove navigation listener on
HeadlessHost that fires closeSession({ reason: 'user_dismissed' }) on every
beforeRemove event. Cursor Bugbot caught a high-severity bug: useTransakRouting
uses navigation.reset() to re-pin HEADLESS_HOST at the base of the stack when
moving to VerifyIdentity / BasicInfo / Checkout / KycWebview. The reset action
fires beforeRemove on the OLD HEADLESS_HOST instance before re-pinning the new
one, but the session is still in flight — closing it here prematurely fires
onClose({ reason: 'user_dismissed' }) and breaks the consumer's flow.

Resolution: inspect e.data.action.type inside the listener and short-circuit on
'RESET'. Legitimate user-dismissal paths (GO_BACK, POP) still close the session
synchronously. Other unmount cases (real stack reset that does NOT re-pin the
Host, hot reload) remain caught by useHeadlessSessionDismissal's unmount
cleanup, which uses isHeadlessHostStillInNavigator to differentiate.

Adds a HeadlessHost.test.tsx case 'does NOT close the session when beforeRemove
fires for a RESET action (stack rebuild guard)'. Updates the existing test
helper to pass a non-RESET action in the event arg so the listener's new
signature stays exercised.

Bug report: #30104 (review)
@amitabh94
Copy link
Copy Markdown
Contributor

@copilot resolve conflicts

@saustrie-consensys saustrie-consensys force-pushed the saustrie-consensys/headless-buy-phase-9.5 branch from 9d33fba to 7811cf2 Compare May 15, 2026 19:52
auto-merge was automatically disabled May 15, 2026 19:55

Head branch was pushed to by a user without write access

Copilot AI requested a review from amitabh94 May 15, 2026 19:55
Copy link
Copy Markdown
Contributor

Copilot AI commented May 15, 2026

@copilot resolve conflicts

The branch is already cleanly on top of main — no merge conflicts exist. The merge base is the tip of origin/main (dafc42c), and git merge origin/main reports "Already up to date." No conflict markers are present in any changed files.

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeMoney, SmokeWalletPlatform
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 82%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are focused on the Ramp/fiat on-ramp headless buy flow:

  1. New RampHeadlessEntry route added to Routes.ts, NavigationService/types.ts, and MainNavigator.js - this is a new transparent modal navigation entry point for the headless buy flow. The NavigationService/types.ts is marked critical but the change is minimal (one new route type).

  2. headlessEntryNavigation.ts (new file) - New utility for managing touch-through and dismissal of the headless flow overlay.

  3. HeadlessHost.tsx - Major refactor: removed all UI elements (header, loader, error states, cancel button), now renders as an invisible transparent container. Navigation and session management logic updated.

  4. useHeadlessBuy.ts - Updated to navigate to the new RAMP.HEADLESS_ENTRY route instead of RAMP.TOKEN_SELECTION.

  5. useTransakRouting.ts - Updated to use dismissHeadlessFlow instead of navigation.getParent()?.pop(), improved session handling for headless flows including limit exceeded errors.

  6. Checkout.tsx - Added headless session management: touch-through control during loading, proper session termination on unmount, using dismissHeadlessFlow.

  7. HeadlessPlayground.tsx - UI improvements for the developer playground (spinner, quote index tracking).

SmokeMoney is the primary tag as all changes are within the Ramp/fiat on-ramp feature area. The existing E2E tests cover onramp unified buy, deeplinks to buy/sell flows, and card flows.

SmokeWalletPlatform is selected because the SmokeMoney tag description states 'When changes touch wallet home or actions entry to buy/sell, also select SmokeWalletPlatform' - the headless buy flow is entered via wallet actions.

SmokeSwap and SmokeConfirmations are NOT selected because these changes are specifically to the Transak/headless fiat on-ramp flow, not to swap or confirmation flows. The headless buy flow uses a WebView-based checkout (Transak), not the native confirmation UI.

The changes don't affect core navigation infrastructure broadly (only adds a new route), so other test suites are not at risk.

Performance Test Selection:
The changes are focused on the headless buy flow navigation and session management within the Ramp feature. While there are UI changes (HeadlessHost now renders as invisible container, Checkout touch-through management), these are not performance-sensitive operations. The changes don't affect account list rendering, app startup, login flows, or other performance-critical paths. No performance tests are warranted.

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size-L team-money-movement issues related to Money Movement features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants