Skip to content

chore(runway): cherry-pick feat(predict): Enable Bottom Sheet via Explore page#30535

Merged
chloeYue merged 1 commit into
release/7.78.0from
cherry-pick-7-78-0-22e0934
May 21, 2026
Merged

chore(runway): cherry-pick feat(predict): Enable Bottom Sheet via Explore page#30535
chloeYue merged 1 commit into
release/7.78.0from
cherry-pick-7-78-0-22e0934

Conversation

@runway-github

@runway-github runway-github Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

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 predictBottomSheet feature flag is on, instead of routing to the
full-page bet slip.

What changed

  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.
  1. 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.
  1. 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).

Files touched

  • 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

CHANGELOG entry: Added in-place buy/sell bottom sheet to Predict market
cards on the Explore feed when the predictBottomSheet feature flag is
enabled.

Related issues

Fixes:

Manual testing steps

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

Screenshots/Recordings

Before

After

bottomSheetExplore.mov

Pre-merge author checklist

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

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.

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
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.

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

[22e0934](https://github.com/MetaMask/metamask-mobile/commit/22e0934089cb0e0d49fdfc53274e40fc652a12c9)

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 -->
@runway-github runway-github Bot requested a review from a team as a code owner May 21, 2026 16:04
@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 team-bots Bot team (for MetaMask Bot, Runway Bot, etc.) label May 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

⏭️ Smart E2E selection skipped - PR targets a release branch (release/*)

All E2E tests pre-selected.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

@chloeYue chloeYue left a comment

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.

LGTM

@chloeYue chloeYue added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label May 21, 2026
@chloeYue chloeYue merged commit f392344 into release/7.78.0 May 21, 2026
299 of 306 checks passed
@chloeYue chloeYue deleted the cherry-pick-7-78-0-22e0934 branch May 21, 2026 18:10
@github-actions github-actions Bot locked and limited conversation to collaborators May 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

size-M skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-bots Bot team (for MetaMask Bot, Runway Bot, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants