chore(runway): cherry-pick feat(predict): Enable Bottom Sheet via Explore page#30535
Merged
Conversation
Predict market cards on the Explore feed now open the Buy/Sell preview
as an in-place bottom sheet (matching the dedicated Predict feed) when
the `predictBottomSheet` feature flag is on, instead of routing to the
full-page bet slip.
1. **Mounted `PredictPreviewSheetProvider` at the `HomeTabs` level** in
`app/components/Nav/Main/MainNavigator.js`, wrapping the
`Tab.Navigator`. The provider was previously only mounted inside
`PredictScreenStack`, so triggering `openBuySheet`/`openSellSheet` from
any other tab fell back to navigation. Mounting at `HomeTabs` makes the
sheet usable from Explore (and any future tab that needs it) while
keeping the existing in-Predict behavior untouched (`PredictScreenStack`
still mounts its own provider; the inner one shadows the outer for
usage).
2. **Why mount at `HomeTabs` and not inside the tab itself?**
`BottomSheet` from `@metamask/design-system-react-native` uses `absolute
inset-0` for its container. If the provider is mounted inside an
individual tab's content area, the sheet's parent is smaller than the
viewport and the sheet gets clipped at the top of the screen and below
by the bottom tab bar. Mounting at `HomeTabs` puts the sheet's parent at
the full-viewport `Home` Stack.Screen card, which is the smallest level
above the tab bar that gives correct dimensions.
3. **Fixed a duplicate-toast and stale-suppression bug** that the new
placement exposes. With both `HomeTabs` and `PredictScreenStack`
providers now mounted simultaneously while the user is inside the
Predict stack, both used to:
- independently fire the state-based "Try again" failure toast on
`activeOrder.error` transitions (no dedup in `ToastService`);
- increment the same `_providerSheetModeCount` counter that gates
`shouldSuppressLegacyOrderFailureToast()`, which then swallowed the
legacy failure toast in tabs/flows where the active provider could not
actually fire its own toast (e.g. Wallet/Trade/Money/Rewards, or
HomepageDiscoveryTabs which mounts in `disableBottomSheet` mode).
Replaced the module-level counter with a registration **stack**
(`_sheetModeProviders`). Each entry holds the provider's id and a
`hasBuyParams()` getter. The topmost (most recently mounted, innermost
in the tree) entry is the only "active" one:
- The state-based toast effect in `PredictPreviewSheetContext.tsx` bails
out unless the current provider is active — so only the innermost
provider fires the Retry toast.
- `shouldSuppressLegacyOrderFailureToast()` now consults the active
entry's `hasBuyParams()`, so the legacy toast is only suppressed when
the active provider will actually surface its own toast.
4. **Test coverage** for the multi-provider scenario in
`PredictPreviewSheetContext.test.tsx`:
- Topmost provider fires the failure toast exactly once when both are
mounted.
- Outer provider becomes active again after the inner unmounts.
- Outer (sheet-mode) provider still fires when the inner provider is
mounted with `disableBottomSheet`.
- `shouldSuppressLegacyOrderFailureToast` correctly tracks the topmost
provider across mount/unmount.
5. Added `PredictPreviewSheetProvider` to the Predict barrel
(`app/components/UI/Predict/index.ts`) for consistency, and a rationale
comment in `MainNavigator.js` explaining why the wrap lives at
`HomeTabs` (so a future maintainer doesn't move it back inside a tab).
- `app/components/Nav/Main/MainNavigator.js`
- `app/components/Nav/Main/MainNavigator.test.tsx` (mock update)
- `app/components/UI/Predict/contexts/PredictPreviewSheetContext.tsx`
-
`app/components/UI/Predict/contexts/PredictPreviewSheetContext.test.tsx`
- `app/components/UI/Predict/index.ts`
CHANGELOG entry: Added in-place buy/sell bottom sheet to Predict market
cards on the Explore feed when the `predictBottomSheet` feature flag is
enabled.
Fixes:
```gherkin
Feature: Predict bottom sheet on Explore feed
Scenario: open buy sheet from Explore with feature flag ON
Given the predictBottomSheet feature flag is enabled
And the user is on the Explore tab
And the Explore feed is showing Predict market cards
When the user taps "Yes" on a Predict market card
Then a buy preview bottom sheet opens in place
And the sheet is anchored above the bottom tab bar (no clipping at the top or bottom)
And the user can swipe the sheet down to dismiss it
Scenario: navigation fallback when feature flag is OFF
Given the predictBottomSheet feature flag is disabled
And the user is on the Explore tab
When the user taps "Yes" on a Predict market card
Then the app navigates to the full-page bet slip (legacy behavior, unchanged)
Scenario: Predict tab behavior is unchanged
Given the predictBottomSheet feature flag is enabled
And the user is on the Predict tab
When the user taps an outcome on a market card
Then the buy preview bottom sheet opens in place (existing behavior)
And there is exactly one Retry toast if the order subsequently fails
Scenario: only the topmost provider fires the failure toast
Given the predictBottomSheet feature flag is enabled
And the user opened and dismissed a buy sheet from Explore
And the user navigated to the Predict tab and opened/dismissed another buy sheet
And the user is now on the Predict tab with both sheets dismissed
When the active Predict order transitions to a failed state
Then the user sees exactly one "Try again" toast (not two)
And tapping Retry reopens the most recently used sheet's market context
```
<!-- Recording: tapping Yes on an Explore market card navigates to the
full-page bet slip (or, with the earlier broken fix, opens a clipped
sheet). -->
<!-- Recording: tapping Yes on an Explore market card opens the bottom
sheet in place, anchored above the tab bar, with no clipping. -->
https://github.com/user-attachments/assets/6bcb24e9-b81c-4c8f-b193-295440cd5805
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
- [ ] I've tested on Android
- [x] I've tested with a power user scenario
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
> _Performance checks N/A: this PR only repositions an existing React
provider higher in the tree and refactors a module-level counter into a
registration stack. No new subscriptions/renders on hot paths; the
failure-toast effect now does strictly less work in non-active
providers._
- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] 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.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches top-level navigation composition and refactors module-scoped
toast suppression/dedup logic for Predict order failures; regressions
could affect bottom sheet rendering or toast behavior across tabs.
>
> **Overview**
> Enables Predict market cards opened outside the Predict tab (e.g.
Explore) to use the in-place Buy/Sell preview bottom sheet by mounting
`PredictPreviewSheetProvider` above the home `Tab.Navigator`.
>
> Refactors `PredictPreviewSheetContext` to handle multiple
simultaneously-mounted providers via a registration stack: only the
topmost sheet-mode provider can fire the state-driven Retry toast, and
legacy order-failure toast suppression now depends on the active
provider having remembered buy params (reducing stale suppression).
Tests are updated/added to cover multi-provider dedup and the new
suppression behavior, and `PredictPreviewSheetProvider` is exported from
the Predict barrel.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
51b7817. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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. |
Contributor
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - PR targets a release branch (release/*) All E2E tests pre-selected. |
|
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.



Description
Predict market cards on the Explore feed now open the Buy/Sell preview
as an in-place bottom sheet (matching the dedicated Predict feed) when
the
predictBottomSheetfeature flag is on, instead of routing to thefull-page bet slip.
What changed
Mounted
PredictPreviewSheetProviderat theHomeTabslevel inapp/components/Nav/Main/MainNavigator.js, wrapping theTab.Navigator. The provider was previously only mounted insidePredictScreenStack, so triggeringopenBuySheet/openSellSheetfromany other tab fell back to navigation. Mounting at
HomeTabsmakes thesheet usable from Explore (and any future tab that needs it) while
keeping the existing in-Predict behavior untouched (
PredictScreenStackstill mounts its own provider; the inner one shadows the outer for
usage).
Why mount at
HomeTabsand not inside the tab itself?BottomSheetfrom@metamask/design-system-react-nativeusesabsolute inset-0for its container. If the provider is mounted inside anindividual tab's content area, the sheet's parent is smaller than the
viewport and the sheet gets clipped at the top of the screen and below
by the bottom tab bar. Mounting at
HomeTabsputs the sheet's parent atthe full-viewport
HomeStack.Screen card, which is the smallest levelabove the tab bar that gives correct dimensions.
Fixed a duplicate-toast and stale-suppression bug that the new
placement exposes. With both
HomeTabsandPredictScreenStackproviders now mounted simultaneously while the user is inside the
Predict stack, both used to:
activeOrder.errortransitions (no dedup inToastService);_providerSheetModeCountcounter that gatesshouldSuppressLegacyOrderFailureToast(), which then swallowed thelegacy failure toast in tabs/flows where the active provider could not
actually fire its own toast (e.g. Wallet/Trade/Money/Rewards, or
HomepageDiscoveryTabs which mounts in
disableBottomSheetmode).Replaced the module-level counter with a registration stack
(
_sheetModeProviders). Each entry holds the provider's id and ahasBuyParams()getter. The topmost (most recently mounted, innermostin the tree) entry is the only "active" one:
PredictPreviewSheetContext.tsxbailsout unless the current provider is active — so only the innermost
provider fires the Retry toast.
shouldSuppressLegacyOrderFailureToast()now consults the activeentry's
hasBuyParams(), so the legacy toast is only suppressed whenthe active provider will actually surface its own toast.
PredictPreviewSheetContext.test.tsx:mounted.
mounted with
disableBottomSheet.shouldSuppressLegacyOrderFailureToastcorrectly tracks the topmostprovider across mount/unmount.
PredictPreviewSheetProviderto the Predict barrel(
app/components/UI/Predict/index.ts) for consistency, and a rationalecomment in
MainNavigator.jsexplaining why the wrap lives atHomeTabs(so a future maintainer doesn't move it back inside a tab).Files touched
app/components/Nav/Main/MainNavigator.jsapp/components/Nav/Main/MainNavigator.test.tsx(mock update)app/components/UI/Predict/contexts/PredictPreviewSheetContext.tsxapp/components/UI/Predict/contexts/PredictPreviewSheetContext.test.tsxapp/components/UI/Predict/index.tsChangelog
CHANGELOG entry: Added in-place buy/sell bottom sheet to Predict market
cards on the Explore feed when the
predictBottomSheetfeature flag isenabled.
Related issues
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
bottomSheetExplore.mov
Pre-merge author checklist
Docs and MetaMask Mobile
Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Performance checks (if applicable)
performance metrics
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Note
Medium Risk
Touches top-level navigation composition and refactors module-scoped
toast suppression/dedup logic for Predict order failures; regressions
could affect bottom sheet rendering or toast behavior across tabs.
Overview
Enables Predict market cards opened outside the Predict tab (e.g.
Explore) to use the in-place Buy/Sell preview bottom sheet by mounting
PredictPreviewSheetProviderabove the homeTab.Navigator.Refactors
PredictPreviewSheetContextto handle multiplesimultaneously-mounted providers via a registration stack: only the
topmost sheet-mode provider can fire the state-driven Retry toast, and
legacy order-failure toast suppression now depends on the active
provider having remembered buy params (reducing stale suppression).
Tests are updated/added to cover multi-provider dedup and the new
suppression behavior, and
PredictPreviewSheetProvideris exported fromthe Predict barrel.
Reviewed by Cursor Bugbot for commit
51b7817. Bugbot is set up for automated
code reviews on this repo. Configure
here.