Skip to content

chore: Replace Settings modals with design-system BottomSheets - #34847

Draft
georgewrmarshall wants to merge 7 commits into
mainfrom
cursor/settings-bottomsheets-e2f5
Draft

chore: Replace Settings modals with design-system BottomSheets#34847
georgewrmarshall wants to merge 7 commits into
mainfrom
cursor/settings-bottomsheets-e2f5

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Description

Converted Settings confirmation dialogs that were using deprecated / react-native-modal-based modals to BottomSheet from @metamask/design-system-react-native, mounted at the screen root so they properly overlay content.

Follow-up: migrated the new sheets to the most up-to-date MMDS usage patterns (string titles for BottomSheetHeader, Box/MMDS Text, and twClassName where applicable) and removed leftover custom modal styles.

Follow-up: ran yarn format:check and applied formatting fixes (no behavior changes).

Specifically:

  • Security & Privacy: Clear privacy approvals, clear browser history, clear cookies, and the recovery hint editor
  • Advanced Settings: Reset account confirmation
  • Notification toggle loading/error UI: replaced react-native-modal wrapper with a design-system BottomSheet

Changelog

CHANGELOG entry: Updated Settings confirmation dialogs to use BottomSheets instead of deprecated modals.

Related issues

Refs: N/A

Manual testing steps

Feature: Settings bottom sheets

  Scenario: User sees bottom sheet confirmations in Security & Privacy
    Given the user is on Settings > Security & privacy

    When the user taps "Clear privacy data"
    Then a bottom sheet confirmation is shown

    When the user dismisses the sheet (close button / swipe down / tap outside)
    Then the sheet closes and the screen remains usable

  Scenario: User sees bottom sheet confirmation in Advanced Settings
    Given the user is on Settings > Advanced

    When the user taps "Reset account"
    Then a bottom sheet confirmation is shown

    When the user taps the confirm button
    Then the sheet closes and the reset behavior is triggered

Screenshots/Recordings

Before

N/A

After

N/A

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

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.
Open in Web Open in Cursor 

cursoragent and others added 2 commits August 15, 2026 02:55
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@metamask-ci metamask-ci Bot added the team-design-system All issues relating to design system in Mobile label Aug 15, 2026
@metamask-ci

metamask-ci Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

PR template — items to address before "Ready for review"

Warnings — informational, address before merging:

  • Pre-merge author checklist has unchecked items (e.g. "I've documented my code using JSDoc format if applicable"). Every box must be consciously checked — see docs/readme/ready-for-review.md.

See docs/readme/ready-for-review.md for the full Definition of Ready for Review.

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

🧪 Flaky unit test detection

Run history flaky detection

View recent run history

Historical failure rate is a hint, not proof — review each suggestion in context. See the flaky-test-detection skill for the full pattern reference and manual audit workflow.

Failures / runs sampled per window:

File 7d 15d 30d
app/components/Views/Settings/AdvancedSettings/ResetAccountModal/ResetAccountModal.test.tsx 0/136 0/179 0/375

AI-detected flaky patterns

app/components/Views/Settings/AdvancedSettings/ResetAccountModal/ResetAccountModal.test.tsx

  • J3 — Missing jest.clearAllMocks() / jest.resetAllMocks() (high)
    • The test file uses jest.clearAllMocks() in the beforeEach block, which is good for clearing mock call counts. However, it does not use jest.resetAllMocks(), which would reset the implementations of mocks as well. This can lead to shared state between tests, causing intermittent failures if any test modifies the mock's implementation.
    • Suggested fix in app/components/Views/Settings/AdvancedSettings/ResetAccountModal/ResetAccountModal.test.tsx:12:
      -beforeEach(() => {
      -  jest.clearAllMocks();
      -  (
      -    selectSelectedInternalAccountFormattedAddress as unknown as jest.Mock
      -  ).mockReturnValue('0x123456789abcdef123456789abcdef123456789a');
      -  (selectChainId as unknown as jest.Mock).mockReturnValue('0x1');
      -});
      +beforeEach(() => {
      +  jest.clearAllMocks();
      +  jest.resetAllMocks(); // Reset implementations to avoid shared state
      +  (
      +    selectSelectedInternalAccountFormattedAddress as unknown as jest.Mock
      +  ).mockReturnValue('0x123456789abcdef123456789abcdef123456789a');
      +  (selectChainId as unknown as jest.Mock).mockReturnValue('0x1');
      +});

This check is informational only and does not block merging.

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: LoaderModal close race condition
    • Guarded stale close completions against reopen and reopened the sheet on visibility=true to avoid calling onCancel or unmounting during a programmatic close.

Create PR

Or push these changes by commenting:

@cursor push c978ef0a47
Preview (c978ef0a47)
diff --git a/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx b/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
--- a/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
+++ b/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
@@ -16,17 +16,30 @@
   const [isMounted, setIsMounted] = useState(isVisible);
   const sheetRef = useRef<BottomSheetRef>(null);
   const closingDueToVisibilityRef = useRef(false);
+  // Track latest visibility to disambiguate stale close callbacks.
+  const isVisibleRef = useRef(isVisible);
+  useEffect(() => {
+    isVisibleRef.current = isVisible;
+  }, [isVisible]);
 
   useEffect(() => {
     if (isVisible) {
+      // Reset programmatic-close marker on explicit reopen.
       closingDueToVisibilityRef.current = false;
       setIsMounted(true);
+      // Ensure the sheet is opened in case a previous close finished.
+      sheetRef.current?.onOpenBottomSheet();
       return;
     }
 
     if (isMounted) {
       closingDueToVisibilityRef.current = true;
       sheetRef.current?.onCloseBottomSheet(() => {
+        // If visibility flipped back to true while the close was animating,
+        // ignore this stale completion to avoid cancel/unmount flicker.
+        if (isVisibleRef.current) {
+          return;
+        }
         setIsMounted(false);
         closingDueToVisibilityRef.current = false;
       });
@@ -34,9 +47,16 @@
   }, [isMounted, isVisible]);
 
   const handleSheetClosed = useCallback(() => {
+    // If the parent wants it visible again, ignore this stale close event.
+    if (isVisibleRef.current) {
+      return;
+    }
     setIsMounted(false);
     if (!closingDueToVisibilityRef.current) {
       onCancel();
+    } else {
+      // Programmatic close finished as intended; reset flag.
+      closingDueToVisibilityRef.current = false;
     }
   }, [onCancel]);

You can send follow-ups to the cloud agent here.

Comment thread app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
…ancel and flicker by guarding close callbacks with current visibility and reopening sheet on visibility true
Comment on lines 84 to 86
<Text style={styles.modalTitle} variant={TextVariant.HeadingMD}>
{strings('app_settings.reset_account_modal_title')}
</Text>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@cursoragent remove the wrapping text BottomSheet header should be standardized font size and is handled by the component

Suggested change
<Text style={styles.modalTitle} variant={TextVariant.HeadingMD}>
{strings('app_settings.reset_account_modal_title')}
</Text>
{strings('app_settings.reset_account_modal_title')}

@cursor cursor Bot Aug 15, 2026

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.

Updated ResetAccountModal to match the review suggestion by removing the wrapped Text inside BottomSheetHeader and passing just:

Suggested change
<Text style={styles.modalTitle} variant={TextVariant.HeadingMD}>
{strings('app_settings.reset_account_modal_title')}
</Text>
{strings('app_settings.reset_account_modal_title')}

Pushed the fix to the existing PR: #34847.

Open in Web Open in Cursor 

cursoragent and others added 2 commits August 15, 2026 03:27
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

@cursor push c978ef0

…ancel and flicker by guarding close callbacks with current visibility and reopening sheet on visibility true

Applied via @cursor push command

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Dismiss blocked while visible
    • Updated handleSheetClosed to gate the early return behind programmatic-close state so interactive dismiss still triggers onCancel even when isVisible is true.

Create PR

Or push these changes by commenting:

@cursor push 8928d9ad1e
Preview (8928d9ad1e)
diff --git a/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx b/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
--- a/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
+++ b/app/components/UI/Notification/SwitchLoadingModal/LoaderModal.tsx
@@ -47,17 +47,19 @@
   }, [isMounted, isVisible]);
 
   const handleSheetClosed = useCallback(() => {
-    // If the parent wants it visible again, ignore this stale close event.
-    if (isVisibleRef.current) {
+    // If a programmatic close finished but the parent reopened mid-animation,
+    // ignore this stale completion to avoid cancel/unmount flicker.
+    if (closingDueToVisibilityRef.current && isVisibleRef.current) {
       return;
     }
     setIsMounted(false);
-    if (!closingDueToVisibilityRef.current) {
-      onCancel();
-    } else {
-      // Programmatic close finished as intended; reset flag.
+    if (closingDueToVisibilityRef.current) {
+      // Programmatic close finished as intended; reset flag and do not cancel.
       closingDueToVisibilityRef.current = false;
+      return;
     }
+    // Interactive dismiss: notify parent so it can clear visibility.
+    onCancel();
   }, [onCancel]);
 
   if (!isMounted) {

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 6020eff. Configure here.

// If the parent wants it visible again, ignore this stale close event.
if (isVisibleRef.current) {
return;
}

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.

Dismiss blocked while visible

Medium Severity

The new isVisibleRef early return in handleSheetClosed skips onCancel whenever the parent still wants the sheet visible. That blocks intentional swipe/backdrop dismiss on an isInteractable sheet, so close events never sync parent state and the sheet can stay closed while isVisible remains true.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6020eff. Configure here.

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeSwap, SmokeStake, SmokeWalletPlatform, SmokeMoney, SmokePerps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, SmokeBrowser, SmokeSnaps, SmokeMMConnect
  • Selected Performance tags: @PerformanceAccountList, @PerformanceOnboarding, @PerformanceLogin, @PerformanceSwaps, @PerformanceLaunch, @PerformanceAssetLoading, @PerformancePredict, @PerformancePreps, @PerformanceMoney
  • Risk Level: high
  • AI Confidence: %
click to see 🤖 AI reasoning details

E2E Test Selection:
Fallback: AI analysis did not complete successfully. Running all tests.

Performance Test Selection:
Fallback: AI analysis did not complete successfully. Running all performance tests.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
51.7% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Performance Test Results

ℹ️ Performance test results are currently non-blocking and will not block this PR.

3 tests failed · 22 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

❌ Failed Tests (3)

🔬 App profiling vs main is included under each failed scenario that has a prior baseline.

@metamask-onboarding-team

Fresh SRP wallet creation performance

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) Quality gates exceeded 📹 Watch

🔬 App profiling check · Current run 31894865929 · Baseline (last run on main (scenario also failing)) run 30897750395 @ 67486d2

⚠️ No green baseline on main — comparing against the latest usable profiling.

Summary: ⚠️ 4 metrics over +10%: CPU avg (+1.41 (+23.7%)), Memory avg (+125.02 (+21.3%)), Memory max (+205.28 (+28.4%)), Slow frames (+6.6 (+263%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 5.95% 7.36% +1.41 (+23.7%) ⚠️
CPU max 19.17% 19.11% -0.06 (-0.3%)
Memory avg 586.2 MB 711.22 MB +125.02 (+21.3%) ⚠️
Memory max 722.61 MB 927.89 MB +205.28 (+28.4%) ⚠️
Slow frames 2.51% 9.11% +6.6 (+263%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 1 1 0 (0%)
App size 328.67 MB 329.53 MB +0.86 (+0.3%)

Seedless Onboarding: Apple Login New User

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) Quality gates exceeded 📹 Watch

🔬 App profiling check · Current run 31894865929 · Baseline (last run on main (scenario also failing)) run 30897750395 @ 67486d2

⚠️ No green baseline on main — comparing against the latest usable profiling.

Summary: ⚠️ 5 metrics over +10%: CPU max (+7.41 (+39.5%)), Memory avg (+55.41 (+10.3%)), Memory max (+358.48 (+59.3%)), Slow frames (+12.64 (+414.4%)), Issues (+1 (+100%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 13.93% 5.04% -8.89 (-63.8%)
CPU max 18.74% 26.15% +7.41 (+39.5%) ⚠️
Memory avg 540.28 MB 595.69 MB +55.41 (+10.3%) ⚠️
Memory max 604.4 MB 962.88 MB +358.48 (+59.3%) ⚠️
Slow frames 3.05% 15.69% +12.64 (+414.4%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 1 2 +1 (+100%) ⚠️
Critical issues 1 1 0 (0%)
App size 328.67 MB 329.53 MB +0.86 (+0.3%)

@mm-perps-engineering-team

Perps open position and close it

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch
✅ Passed Tests (19)
Test Platform Device Duration Team Recording
Aggregated Balance Loading Time, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 8.34s @assets-dev-team 📹 Watch
Cross-chain swap flow - ETH to SOL - 50+ accounts, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 4.83s @swap-bridge-dev-team 📹 Watch
Asset View, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 2.19s @assets-dev-team 📹 Watch
Swap flow - ETH to LINK, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 1.24s @swap-bridge-dev-team 📹 Watch
Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3 Android Google Pixel 8 Pro (v14.0) 3.80s @Accounts-team 📹 Watch
Cold Start: Measure ColdStart To Login Screen Android Google Pixel 8 Pro (v14.0) 3.62s @metamask-mobile-platform 📹 Watch
Measure Warm Start: Login To Wallet Screen Android Google Pixel 8 Pro (v14.0) 1.46s @metamask-mobile-platform 📹 Watch
Measure Warm Start: Warm Start to Login Screen Android Google Pixel 8 Pro (v14.0) 0.18s @metamask-mobile-platform 📹 Watch
Perps add funds Android Google Pixel 8 Pro (v14.0) 9.80s @mm-perps-engineering-team 📹 Watch
Predict Available Balance - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 0.84s @team-predict 📹 Watch
Predict Deposit - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 11.24s @team-predict 📹 Watch
Predict Market Details - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 2.79s @team-predict 📹 Watch
Measure Cold Start To Onboarding Screen Android Google Pixel 8 Pro (v14.0) 2.83s @metamask-mobile-platform 📹 Watch
Onboarding Import SRP with +50 accounts, SRP 3 Android Google Pixel 8 Pro (v14.0) 4.55s @metamask-onboarding-team 📹 Watch
Money Home after fresh wallet creation with empty balance Android Google Pixel 8 Pro (v14.0) 2.17s @mm-earn-team 📹 Watch
Money Home after importing SRP with funded balance Android Google Pixel 8 Pro (v14.0) 3.32s @mm-earn-team 📹 Watch
Account creation after fresh install Android Google Pixel 8 Pro (v14.0) 1.80s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Google Login New User Android Google Pixel 8 Pro (v14.0) 2.08s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Telegram Login New User Android Google Pixel 8 Pro (v14.0) 6.19s @metamask-onboarding-team 📹 Watch

Branch: cursor/settings-bottomsheets-e2f5 · Build: E2E · Commit: a1bbfc2 · View full run

@georgewrmarshall georgewrmarshall changed the title Replace Settings modals with design-system BottomSheets chore: Replace Settings modals with design-system BottomSheets Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size-L team-design-system All issues relating to design system in Mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants