Skip to content

fix(perps): eliminate trade screen slider lag and flicker - #33590

Merged
aganglada merged 11 commits into
mainfrom
fix/TAT-3543_perps-slider-lag
Jul 30, 2026
Merged

fix(perps): eliminate trade screen slider lag and flicker#33590
aganglada merged 11 commits into
mainfrom
fix/TAT-3543_perps-slider-lag

Conversation

@aganglada

@aganglada aganglada commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

fix(perps): eliminate trade screen slider lag and flicker

Description

The amount slider on the Perps trade screen (and the Close Position screen) was lagging and flickering while dragging, and would visibly "snap" to a different position on release (TAT-3543).

Root cause: the custom PerpsSlider component called runOnJS(updateValue) on every pan onUpdate frame (up to 60+/sec while dragging). On PerpsOrderView, that callback called setAmount, which updates orderForm.amount in PerpsOrderContext — the sole context consumed by the ~2,900-line PerpsOrderView. Every drag frame therefore re-rendered the whole view and re-ran usePerpsOrderFees (which awaits calculateFees(), applyFeeDiscount(), and handlePointsEstimation() — all RewardsController calls) plus usePerpsEstimatedSlippage and other memoized calculations, flooding the JS thread far faster than it could keep up. PerpsClosePositionView had the identical problem via closePercentage. Separately, PerpsSlider built its pan/tap gesture objects directly in the component body (no useMemo), so every one of those re-renders also tore down and rebuilt the native gesture recognizer mid-drag, independently causing stutter and an inconsistent final position.

Solution: rewrote PerpsSlider as a thin wrapper around the MetaMask Design System Slider (@metamask/design-system-react-native), which already memoizes its gesture and exposes two callbacks — onValueChange (fired every drag tick, for cheap display-only updates) and onDragEnd (fired once on release, for expensive side effects). This is the same pattern already used by BatchSellReviewTokenRow. PerpsOrderView and PerpsClosePositionView now split their slider-driven state into a cheap live-display value (updates every frame) and a committed value (updates once per drag, on onDragEnd) that continues to drive fees/rewards/slippage/validation. PerpsAdjustMarginView only feeds cheap synchronous math, so it needed no call-site changes — it benefits from the gesture-memoization fix for free.

Update from main

Forward-merged main into this branch (merge commit, no rebase) to resolve conflicts created by main's independent evolution of the old PerpsSlider since this branch forked: a variant?: 'default' | 'compact' prop and a showPercentageMarkers prop (both now used by the new PerpsProOrderForm compact slider), plus a progressColor/quickValues prop pair that no real caller used and was dropped. Both new props were ported onto the DS-wrapper rewrite above (variant="compact" maps to trackInset={0} + a dense twClassName; showPercentageMarkers maps to the DS Slider's showRangeDots, decoupled from the range-label prop), so PerpsProOrderForm, PerpsAdjustMarginView, PerpsOrderView, and PerpsClosePositionView all keep working unchanged.

Also bumped @metamask/design-system-react-native to ^0.38.1, which includes MetaMask/metamask-design-system#1397 — a separate, complementary fix for a stale controlled-value echo in useSliderGesture that caused the DS Slider's thumb to visibly rewind/flicker on rapid taps or fast pans.

Changelog

CHANGELOG entry: Fixed the Perps trade and close-position amount sliders lagging, flickering, and jumping to a different value on release

Related issues

Refs: TAT-3543

Manual testing steps

Feature: Perps trade screen amount slider responsiveness

  Background:
    Given I am logged into MetaMask Mobile
    And I have a Perps account with available balance

  Scenario: user drags the amount slider on the trade (order) screen
    Given I am on the Perps trade screen for any market
    And the amount slider is visible

    When user presses and drags the slider thumb quickly back and forth
    Then the USD amount, token-size subtitle, and thumb position should track the finger with no visible delay or flicker

    When user releases the slider
    Then the slider thumb and amount should NOT jump or snap to a different value
    And the fee/margin/slippage rows should update shortly after release

  Scenario: user drags the amount slider on the close position screen
    Given I have an open Perps position
    And I navigate to the Close Position screen for that position

    When user presses and drags the amount slider quickly back and forth
    Then the USD amount and token-amount subtitle should track the finger with no visible delay or flicker

    When user releases the slider
    Then the amount should NOT jump or snap to a different value
    And the summary (margin, fees, receive amount) should update shortly after release

  Scenario: user drags the amount slider on the adjust margin screen
    Given I have an open Perps position
    And I navigate to Adjust Margin (add or remove) for that position

    When user presses and drags the amount slider quickly back and forth
    Then the amount should track the finger smoothly with no stutter

  Scenario: percentage dots and haptics still work
    Given I am on the Perps trade screen with the slider visible

    When user taps the 25%, 50%, 75%, or 100% label below the slider
    Then the slider should jump to that value immediately
    And a haptic tick should fire when crossing a labeled threshold while dragging

Screenshots/Recordings

Before

N/A - recording not captured in this session (no device/simulator available). This is a runtime drag-responsiveness fix, best verified live per the manual testing steps above rather than a static screenshot; author to add a before recording prior to marking ready for review.

After

N/A - recording not captured in this session (no device/simulator available). Author to add an after recording prior to marking ready for review.

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
    • Not yet done in this session (no device/simulator available) - please verify before marking ready for review
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
    • Not yet done in this session - please verify before marking ready for review
  • I've instrumented key operations with Sentry traces for production performance metrics
    • N/A - this change removes per-frame JS work rather than adding new async operations; existing tracing on usePerpsOrderFees/order submission is untouched

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.

Note

Medium Risk
Changes how Perps order and close sizes are committed at submit time (drag-end vs per-frame), which could affect edge cases around cancelled gestures and rapid confirm taps, though guards and new tests target those paths.

Overview
Fixes Perps trade and close-position amount sliders that lagged, flickered, and could snap on release because every drag frame updated committed state and re-ran fees, slippage, and validation across large views.

PerpsSlider is now a thin wrapper around @metamask/design-system-react-native Slider (bumped to ^0.38.1), forwarding onDragEnd and haptics while dropping the old gesture/reanimated implementation and PerpsSlider.styles.ts.

PerpsOrderView and PerpsClosePositionView split live drag display from committed amount/percentage: onValueChange updates UI only; onDragEnd (and a shared commitAmount / commitClosePercentage funnel) updates context/state that drives fees and submission. Place order / confirm close flush an in-flight drag instead of submitting stale values; leverage confirm clamps against the live drag amount; onTouchCancel handles cancelled gestures; keypad commits can skip USD string resync to preserve mid-typing values like 2..

Tests were expanded for the commit funnel; PerpsSlider unit tests now assert DS prop mapping.

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

Rewrite PerpsSlider as a thin wrapper around the MetaMask Design
System Slider instead of the custom react-native-gesture-handler +
reanimated implementation, which flooded the JS thread with
runOnJS calls and expensive fee/rewards recomputes on every drag
frame (TAT-3543). PerpsOrderView and PerpsClosePositionView now
split slider state into a cheap live display value and a value
committed only on drag end.

Co-authored-by: Cursor <cursoragent@cursor.com>
@aganglada aganglada self-assigned this Jul 21, 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.

@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 Jul 21, 2026
@metamask-ci metamask-ci Bot added the team-social-ai Social & AI team label Jul 21, 2026
@metamask-ci

metamask-ci Bot commented Jul 21, 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 tested on Android"). 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.

@aganglada aganglada added team-perps Perps team and removed team-social-ai Social & AI team labels Jul 21, 2026
aganglada and others added 2 commits July 28, 2026 11:13
…ider-lag

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx
#	app/components/UI/Perps/components/PerpsSlider/PerpsSlider.styles.ts
#	app/components/UI/Perps/components/PerpsSlider/PerpsSlider.test.tsx
#	app/components/UI/Perps/components/PerpsSlider/PerpsSlider.tsx
Picks up MetaMask/metamask-design-system#1397, a fix for a stale
controlled-value echo bug in useSliderGesture that caused the DS
Slider's thumb to visibly rewind/flicker on rapid taps or fast pans.
Complementary to this branch's PerpsSlider rewrite, which fixes the
JS-thread-flooding root cause of the perps trade-screen slider lag.

Co-authored-by: Cursor <cursoragent@cursor.com>
@socket-security

socket-security Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​metamask/​design-system-react-native@​0.38.0 ⏵ 0.38.19810084100 +1100

View full report

…omponent-flow integration harness

The DS Slider's useSliderGesture calls Gesture.Pan().onStart(...), which
this harness's react-native-gesture-handler mock didn't provide (it only
covered the old custom PerpsSlider's onBegin/onUpdate/onEnd/onFinalize
chain), crashing componentFlow.integration.test.tsx when PerpsOrderView
renders the real PerpsSlider -> DS Slider.

Co-authored-by: Cursor <cursoragent@cursor.com>
@aganglada aganglada removed 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 Jul 28, 2026
@aganglada
aganglada marked this pull request as ready for review July 28, 2026 10:28
@aganglada
aganglada requested a review from a team as a code owner July 28, 2026 10:28
@aganglada
aganglada enabled auto-merge July 28, 2026 10:29
@github-actions github-actions Bot added the risk:low AI analysis: low risk label Jul 28, 2026
Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx Outdated

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

Please address the inline comments.

}}
value={parseFloat(displayAmount || '0')}
onValueChange={handleSliderValueChange}
onDragEnd={handleSliderDragEnd}

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.

A cancelled gesture skips onDragEnd, leaving the displayed amount ahead of the committed/submitted amount. Please commit or reset on cancellation.

setDisplayClosePercentage(value);
};

const handleSliderDragEnd = (value: number) => {

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.

Please memoize both slider callbacks; each drag tick currently recreates the slider gestures mid-drag.

// slippage recompute pipeline (usePerpsOrderFees et al.).
const [displayAmount, setDisplayAmount] = useState(orderForm.amount);

useEffect(() => {

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.

This syncs after paint, so non-slider edits briefly render stale amounts. Please derive from the committed amount when not dragging or update both states synchronously.

- Derive displayAmount/displayClosePercentage from a dragging flag
  instead of syncing via useEffect, so keypad/percentage/max/clamp/
  leverage edits render the committed value immediately instead of
  lagging a render behind (Bugbot + review comment).
- Memoize PerpsClosePositionView's slider callbacks with useCallback
  so they no longer rebuild mid-drag (review comment).
- Reset the dragging flag on onTouchCancel in both views so a gesture
  cancelled before onDragEnd fires falls back to the last committed
  value instead of leaving the display stuck ahead of it (review
  comment).

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot removed the risk:low AI analysis: low risk label Jul 28, 2026
Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx Outdated
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.25000% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.82%. Comparing base (a3ca1a6) to head (f7c229a).
⚠️ Report is 18 commits behind head on main.

Files with missing lines Patch % Lines
...s/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx 45.00% 8 Missing and 3 partials ⚠️
.../PerpsClosePositionView/PerpsClosePositionView.tsx 50.00% 8 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #33590      +/-   ##
==========================================
+ Coverage   84.80%   84.82%   +0.01%     
==========================================
  Files        6254     6256       +2     
  Lines      168765   168778      +13     
  Branches    41306    41300       -6     
==========================================
+ Hits       143126   143167      +41     
+ Misses      15870    15839      -31     
- Partials     9769     9772       +3     

☔ View full report in Codecov by Harness.
📢 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.

…e touch-cancel

react-native-gesture-handler owns the touch outside RN's responder system, so
a gesture cancelled by competing-gesture arbitration (e.g. a parent
ScrollView taking over mid-drag) does not reliably bubble an RN touch-cancel
event, leaving isDraggingSlider stuck true and the display ahead of the
committed amount. Replace the onTouchCancel-only reset with a restart-on-tick
stall timer that commits the last live value once ticks stop arriving,
keeping onTouchCancel as a best-effort immediate signal.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx Outdated
… reset

Bugbot correctly flagged the stall-timeout safety net as unsound: ticks only
arrive when the stepped value changes, so a normal mid-drag pause (or a hold
on the same step) is indistinguishable from a cancelled gesture, causing
commitDragAmount/commitDragClosePercentage to fire while the finger is still
down and kicking off the fee/rewards/slippage pipeline early.

Replace it with a single commit funnel (commitAmount /
commitClosePercentage) that every input path — slider drag end, keypad,
percentage, max, and leverage clamp — routes through, unconditionally
clearing isDraggingSlider. This removes the false-positive risk entirely: a
stuck flag from a gesture that skips onDragEnd now self-heals the instant the
user does anything else, with no heuristic guessing about gesture state.
onTouchCancel remains as a best-effort immediate signal. Also guard
place-order/confirm-close submission itself: if isDraggingSlider is somehow
still true at submit time, flush the live value and bail rather than risk
submitting a stale committed amount.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
… clamps

commitAmount only ran when the new leverage required clamping the amount
down, so a leverage change that didn't need clamping left isDraggingSlider
(and the comments describing it) inaccurate: the live drag value kept
shadowing the correct, unchanged orderForm.amount (Bugbot).

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
…iscard it

setIsDraggingSlider(false) alone discarded a pending liveDragAmount back to
the stale pre-drag orderForm.amount, and the clamp check read that same stale
value — unlike handleSliderDragCancel and the place-order guard, which both
flush the live value forward instead of dropping it (Bugbot).

Co-authored-by: Cursor <cursoragent@cursor.com>
…lamp paths

SonarCloud's new-code coverage gate failed (55% vs 80% required) because the
commit-funnel logic added for the slider lag/flicker fix (drag/cancel/guard
handling in PerpsOrderView and PerpsClosePositionView, plus the leverage
confirm flush/clamp fix) had no direct test coverage. Adds targeted tests
that drive the real onValueChange/onDragEnd/onTouchCancel wiring and assert
on the resulting commits instead of only checking that components render.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ab95373. Configure here.

Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx Outdated
@github-actions

github-actions Bot commented Jul 29, 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/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.test.tsx 0/216 0/342 0/363
app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx 0/216 0/342 0/363

AI-detected flaky patterns

app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.test.tsx

  • J4 — waitFor without a real assertion inside (high)
    • queryByTestId returns null when the element is absent — it never throws. Asserting .toBeDefined() on a null value passes because null is defined in JavaScript. This means the waitFor callback resolves immediately and always passes, even when the elements are not actually present in the tree. The subsequent test logic therefore races against async state updates rather than waiting for them. Use toBeOnTheScreen() (which calls expect(element).not.toBeNull() internally) or switch to getByTestId (which throws when absent) to give waitFor a real condition to poll against.
    • Suggested fix in app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.test.tsx:1480:
      -      await waitFor(() => {
      -        expect(queryByTestId('perps-amount-display')).toBeDefined();
      -        expect(
      -          queryByTestId(
      -            PerpsClosePositionViewSelectorsIDs.CLOSE_POSITION_CONFIRM_BUTTON,
      -          ),
      -        ).toBeDefined();
      -      });
      +      await waitFor(() => {
      +        expect(
      +          queryByTestId('perps-amount-display'),
      +        ).toBeOnTheScreen();
      +        expect(
      +          queryByTestId(
      +            PerpsClosePositionViewSelectorsIDs.CLOSE_POSITION_CONFIRM_BUTTON,
      +          ),
      +        ).toBeOnTheScreen();
      +      });

app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx

  • J9 — Module-level mutable let bindings not reset in beforeEach (high)
    • Three module-level mutable let bindings — mockIsPayQuoteLoading, mockPayTotals, and mockPayRequiredTokens — are declared at module scope and mutated inside individual tests (e.g. mockIsPayQuoteLoading = true in the 'emits PERPS_TRADE_QUOTE_RECEIVED' test). The top-level beforeEach only resets mockPerpsAdvancedChartEnabled, mockSliderDragValue, and mockLeverageConfirmValue. mockIsPayQuoteLoading and mockPayTotals are only reset inside the nested beforeEach of the 'transaction considered + trade quote received' describe block. If test execution order changes (e.g. --randomize), a mutation made in one test bleeds into a later test that runs outside that describe block, causing intermittent assertion failures. The pay amount readiness describe block's afterEach resets mockPayRequiredTokens and mockUseIsPerpsBalanceSelected but not mockIsPayQuoteLoading or mockPayTotals.
    • Suggested fix in app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx:490:
      -// Controllable pay-quote state for the trade-quote-received coverage tests.
      -let mockIsPayQuoteLoading = false;
      -let mockPayTotals: unknown;
      -let mockPayRequiredTokens: { amountRaw: string; skipIfBalance: boolean }[] = [];
      +// Controllable pay-quote state for the trade-quote-received coverage tests.
      +let mockIsPayQuoteLoading = false;
      +let mockPayTotals: unknown;
      +let mockPayRequiredTokens: { amountRaw: string; skipIfBalance: boolean }[] = [];
      +
      +// In the top-level beforeEach, add resets for all three:
      +beforeEach(() => {
      +  jest.clearAllMocks();
      +  mockPerpsAdvancedChartEnabled = false;
      +  mockSliderDragValue = 0;
      +  mockLeverageConfirmValue = 3;
      +  // Add these three resets:
      +  mockIsPayQuoteLoading = false;
      +  mockPayTotals = undefined;
      +  mockPayRequiredTokens = [];
      +  // ... rest of existing beforeEach setup
      +});
  • J8 — jest.useFakeTimers() combined with waitFor (high)
    • jest.useFakeTimers() is called inside the test body without a corresponding jest.useRealTimers() in an afterEach. This means fake timers remain active for all subsequent tests in the suite. Any test that follows this one and uses waitFor() (which polls via real setTimeout internally) will hang or time out silently because the fake timer environment prevents waitFor's internal polling from advancing. The file has many async tests using waitFor after this describe block. The fix is to add afterEach(() => jest.useRealTimers()) inside the 'transaction considered + trade quote received' describe block, or move jest.useFakeTimers() into a beforeEach paired with afterEach(() => jest.useRealTimers()).
    • Suggested fix in app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx:4700:
      -it('emits PERPS_TRANSACTION_CONSIDERED once the filled order form settles (1s debounce)', () => {
      -  jest.useFakeTimers();
      -  render(<PerpsOrderView />, { wrapper: TestWrapper });
      -  act(() => {
      -    jest.advanceTimersByTime(999);
      -  });
      -  // ...
      -});
      +describe('transaction considered + trade quote received', () => {
      +  let captured: { eventName: unknown; props: Record<string, unknown> }[];
      +
      +  beforeEach(() => {
      +    jest.useFakeTimers();
      +    captured = [];
      +    mockIsPayQuoteLoading = false;
      +    mockPayTotals = undefined;
      +    mockCreateEventBuilder.mockImplementation((eventName?: unknown) => {
      +      const builder: { addProperties: jest.Mock; build: jest.Mock } = {
      +        addProperties: jest.fn((props: Record<string, unknown>) => {
      +          captured.push({ eventName, props });
      +          return builder;
      +        }),
      +        // ...
      +      };
      +      return builder;
      +    });
      +  });
      +
      +  afterEach(() => {
      +    jest.useRealTimers();
      +  });
      +
      +  // Remove jest.useFakeTimers() from individual test bodies
      +  it('emits PERPS_TRANSACTION_CONSIDERED once the filled order form settles (1s debounce)', () => {
      +    render(<PerpsOrderView />, { wrapper: TestWrapper });
      +    act(() => {
      +      jest.advanceTimersByTime(999);
      +    });
      +    // ...
      +  });
      +});
  • J9 — Module-level mutable let bindings not reset in beforeEach (high)
    • The newly added mockSliderDragValue and mockLeverageConfirmValue module-level let bindings (introduced in this PR) are correctly reset in the top-level beforeEach. However, mockSliderDragValue is also mutated directly inside individual test bodies within the 'Slider drag commit funnel' describe block (e.g. mockSliderDragValue = 42;, mockSliderDragValue = 77;, mockSliderDragValue = 88;) without a nested beforeEach reset. If tests within that describe block run in a different order (e.g. --randomize), the mutated value from one test bleeds into the next test in the same describe block. The top-level beforeEach does reset it to 0, so cross-describe contamination is handled — but within the describe block itself, test order matters.
    • Suggested fix in app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx:600:
      -let mockPerpsAdvancedChartEnabled = false;
      -let mockSliderDragValue = 0;
      -let mockLeverageConfirmValue = 3;
      +describe('Slider drag commit funnel', () => {
      +  beforeEach(() => {
      +    mockSliderDragValue = 0; // reset before each slider test
      +  });
      +
      +  it('shows the live drag value instead of the stale committed amount while dragging', () => {
      +    mockSliderDragValue = 42;
      +    render(<PerpsOrderView />, { wrapper: TestWrapper });
      +    fireEvent.press(screen.getByTestId('perps-slider-drag'));
      +    expect(screen.getByText('Slider Value: 42')).toBeOnTheScreen();
      +  });
      +
      +  it('commits the live value on drag end', () => {
      +    const mockSetAmount = jest.fn();
      +    (usePerpsOrderContext as jest.Mock).mockReturnValue(
      +      buildOrderContextMock({ setAmount: mockSetAmount }),
      +    );
      +    mockSliderDragValue = 77;
      +    // ...
      +  });
      +});

This check is informational only and does not block merging.

…lines

Use toBeOnTheScreen() instead of toBeDefined() for element-presence
assertions, and select the leverage row by its stable testID instead of the
i18n-able "Leverage" label (Bugbot).

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePreps
  • Risk Level: medium
  • AI Confidence: 90%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR makes significant changes to the Perps slider interaction system:

  1. PerpsSlider.tsx - Complete rewrite: replaced a custom 433-line implementation (using react-native-gesture-handler, react-native-reanimated, LinearGradient) with a thin ~80-line wrapper around the design system Slider from @metamask/design-system-react-native. Added onDragEnd prop support.

  2. PerpsClosePositionView.tsx - Added live drag state management (liveDragClosePercentage, isDraggingSlider, displayClosePercentage) to decouple UI display updates (every drag frame) from expensive computation (only on drag end via commitClosePercentage). This changes how the close position slider interacts with fee/validation pipelines.

  3. PerpsOrderView.tsx - Same pattern applied to the order placement view: liveDragAmount, isDraggingSlider, commitAmount funnel, and onDragEnd handler. Also changes how the amount display and position size are computed during dragging.

  4. package.json - Bumped @metamask/design-system-react-native from ^0.38.0 to ^0.38.1 to get the new Slider component.

  5. PerpsSlider.styles.ts - Deleted (styling now handled by the DS Slider).

Tag selection rationale:

  • SmokePerps: Direct impact - the slider is a core interaction in both the order placement flow (PerpsOrderView) and close position flow (PerpsClosePositionView). The refactored slider and new drag-end commit pattern need validation in E2E tests.
  • SmokeWalletPlatform: Required by SmokePerps tag description (Perps is a section inside Trending tab).
  • SmokeConfirmations: Required by SmokePerps tag description (Add Funds deposits are on-chain transactions).

The changes are scoped to Perps UI components only - no changes to core controllers, navigation, or shared infrastructure that would affect other test areas.

Performance Test Selection:
The PerpsSlider was completely rewritten from a custom gesture-handler/reanimated implementation to a design system Slider wrapper. The new architecture separates display updates (every drag frame via onValueChange) from expensive computation (only on drag end via onDragEnd). This architectural change could affect perps trading performance - specifically the add funds flow and position management which involve slider interactions. The @PerformancePreps tag covers perps market loading, position management, add funds flow, and order execution - all of which use the refactored slider.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Performance Test Results

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

All tests passed · 2 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

✅ Passed Tests (2)
Test Platform Device Duration Team Recording
Perps add funds Android Google Pixel 8 Pro (v14.0) 6.91s @mm-perps-engineering-team 📹 Watch
Perps open position and close it Android Google Pixel 8 Pro (v14.0) 18.69s @mm-perps-engineering-team 📹 Watch

Branch: fix/TAT-3543_perps-slider-lag · Build: Normal · Commit: b238779 · View full run

@abretonc7s
abretonc7s self-requested a review July 30, 2026 10:58
@aganglada
aganglada added this pull request to the merge queue Jul 30, 2026
Merged via the queue into main with commit f20263c Jul 30, 2026
136 of 138 checks passed
@aganglada
aganglada deleted the fix/TAT-3543_perps-slider-lag branch July 30, 2026 11:23
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 30, 2026
@metamask-ci metamask-ci Bot added the release-8.6.0 Issue or pull request that will be included in release 8.6.0 label Jul 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-8.6.0 Issue or pull request that will be included in release 8.6.0 risk:medium AI analysis: medium risk size-XL team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants