Skip to content

Commit a90c1ed

Browse files
docs(ramp): add Phase 9.5 (Host visual treatment) and Phase 9 timeout open question
Captures decisions and open questions from the May 6 2026 design thread (https://consensys.slack.com/archives/C0AK3NXRM7W/p1778072992397499) on how MetaMask Pay consumes the headless flow. - New Phase 9.5: HeadlessHost visual treatment. Pedro confirmed the Host must stay mounted (routing landing pad + nativeFlowError surface) but doesn't have to be visible. Two shapes evaluated: transparent overlay with consumer-rendered spinner (Pedro's pick) or bottom-sheet with the Host's own spinner. Final shape pending Lucas's design rec (May 13). Phase 8's dismissal contract is unaffected — back-press still fires onClose(user_dismissed). - Phase 9 Update: append the timeout open question Barbara raised (reply 36). Two API shapes worth considering during Phase 9 implementation: timeoutMs on awaitOrderTerminalState, or a registry-side per-session timeout that fires onError(TIMED_OUT) + onClose. Not blocking for v1. - Resolve a naming conflict: the auto-select-best-provider utility was tentatively listed as "Phase 9.5"; renamed to "follow-up phase" so the new visual-treatment phase can own the 9.5 number.
1 parent 76ebcb8 commit a90c1ed

1 file changed

Lines changed: 38 additions & 1 deletion

File tree

  • app/components/UI/Ramp/headless

app/components/UI/Ramp/headless/PLAN.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [x] **Phase 7** — Extract UI-coupled error/limit surfacing; route errors through `onError` as typed `HeadlessBuyError`
1818
- [x] **Phase 8** — Cancellation + `onClose` semantics (including user-dismissed detection)
1919
- [ ] **Phase 9** — Expose `getOrder` / `refreshOrder` from hook and show in playground (now an MVP requirement — see Phase 9 Update)
20+
- [ ] **Phase 9.5** — HeadlessHost visual treatment (transparent or bottom-sheet) — driven by the May 6 design thread
2021
- [ ] **Phase 10** — Implement deferred Phase 5b + playground polish
2122

2223
---
@@ -553,7 +554,9 @@ Phase 9 is now an MVP requirement, not playground polish. MetaMask Pay's `Transa
553554
554555
The original Phase 9 surface (`getOrder`, `refreshOrder` + a "Refresh order" playground button) doesn't fit a controller consumer. The Phase 9 API should add an **imperative `awaitOrderTerminalState(orderId)` Promise** so TPC can `await` settlement instead of polling itself. Polling and the playground button stay as additional surfaces; the Promise is the load-bearing API.
555556
556-
The Apr 28 progress sync also called out a missing **auto-select-best-provider utility** ("Need utility function to auto-select best provider rather than requiring explicit provider ID"). Either fold into Phase 9 alongside `getOrder`, or split as Phase 9.5 — to be decided during Phase 9 implementation.
557+
The Apr 28 progress sync also called out a missing **auto-select-best-provider utility** ("Need utility function to auto-select best provider rather than requiring explicit provider ID"). Either fold into Phase 9 alongside `getOrder` or split as a follow-up phase — to be decided during Phase 9 implementation.
558+
559+
**Open question** (Barbara, [May 6 design thread](https://consensys.slack.com/archives/C0AK3NXRM7W/p1778072992397499)): does ramps need an internal timeout — distinct from the registry's 1-hour GC — so the consumer isn't pinned to a "loading forever" state when a quote stalls? Two shapes worth considering during Phase 9 implementation: (i) `awaitOrderTerminalState(orderId, { timeoutMs })` rejects with a timed-out error so the consumer can decide what to surface; (ii) the registry grows a per-session timeout that fires `onError({ code: 'TIMED_OUT' })` + `onClose` if no terminal event arrives within N seconds. Pedro's reply 37 in the same thread noted Phase 3's `cancel()` is today's escape hatch; not blocking for v1 but worth resolving before MMPay launch.
557560
558561
Goal: complete the hook surface.
559562
@@ -564,6 +567,40 @@ Goal: complete the hook surface.
564567
565568
---
566569
570+
## Phase 9.5 — HeadlessHost visual treatment
571+
572+
Goal: make production HeadlessHost stop rendering user-visible chrome (header, spinner, cancel button, error text). The Host stays mounted — it is still the routing landing pad and the `nativeFlowError` surface from Phase 4b — but becomes either fully transparent or a bottom-sheet, so the consumer (TPC / MMPay) renders the only user-visible loading UI during a headless buy.
573+
574+
Driver: [May 6 2026 design thread](https://consensys.slack.com/archives/C0AK3NXRM7W/p1778072992397499). Pedro confirmed the Host can't be removed (it's the routing base and Phase 7's error surface) but it doesn't have to be visible. Two shapes were evaluated: (a) transparent overlay with consumer-rendered spinner, (b) bottom-sheet with the Host's own spinner and cancel action. Pedro favored (a). Final shape pending Lucas's design recommendation (deadline May 13).
575+
576+
### Settled in the same thread
577+
578+
- **Back navigation stays available during loading** (Lucas, May 8 16:54 reply 39). Phase 8's `useHeadlessSessionDismissal` already fires `onClose({ reason: 'user_dismissed' })` on unmount, so the consumer's back-press still produces a terminal callback regardless of Host visibility.
579+
- Latency expectation: 1–2 seconds typical (Lorenzo, reply 19).
580+
- Cancel-session (Phase 3 `cancel()`) is the escape hatch for stuck quotes (Pedro, reply 37).
581+
582+
### Implementation sketch
583+
584+
- Strip `HeaderCompactStandard`, `ActivityIndicator`, the loader/no-session/error `Text` blocks, and the bottom `Cancel` button from [HeadlessHost.tsx](../Views/HeadlessHost/HeadlessHost.tsx).
585+
- The wrapping `SafeAreaView` becomes either fully transparent (option a) or sized to the bottom-sheet spec (option b, per Lucas's design).
586+
- The local `errorMessage` state becomes redundant once the consumer renders all UI; remove it. `failSession` / `closeSession` calls stay — the consumer receives the error via `onError`.
587+
- Keep the orchestration `useEffect`, the `nativeFlowError` handler, and `useHeadlessSessionDismissal` untouched — they are behavior, not chrome.
588+
- Update `HeadlessHost.test.tsx`: drop the visual-presence assertions, keep all orchestration / dismissal / `nativeFlowError` tests.
589+
590+
### Out of scope
591+
592+
- The MMPay consumer-side loading UI (TPC PR [`MetaMask/core#8628`](https://github.com/MetaMask/core/pull/8628) and downstream MMPay UI work).
593+
- Internal Host timeout — see the Open question in the Phase 9 Update.
594+
595+
### References
596+
597+
- [Money Account Figma](https://www.figma.com/design/XKZ8hRqSn2iTiuzmlQLuYQ/Money-Account?node-id=3041-13117) (Lorenzo, reply 16).
598+
- [UB2 loading-states reference](https://www.figma.com/design/ItZzm9CzSAjOWQTUKsOdSk/BUY?node-id=4347-3909) (Lorenzo, reply 23) for design parity.
599+
600+
Deliverable: HeadlessHost is invisible (or bottom-sheet) and MMPay's TPC renders the only user-visible loading UI during a headless buy. Phase 8's dismissal contract continues to work unchanged.
601+
602+
---
603+
567604
## Phase 10 — Implement deferred Phase 5b + playground polish
568605
569606
> Goal 1 (primary): implement the deferred Phase 5b — `startHeadlessBuy({ assetId, amount, paymentMethodId, providerId? })` raw-params start mode, where the Host fetches quotes itself and auto-picks one. See the existing "Phase 5b (deferred)" section above for the full spec.

0 commit comments

Comments
 (0)