Uh oh!
There was an error while loading. Please reload this page.
feat(perps): persist Pro positions sort and side filter prefs - #9838
Conversation
Add positionsSideFilter and positionsSortConfig to proLayoutPreferences so mobile can persist Positions/Orders panel preferences across markets and restarts.
Satisfy CI changelog link requirements and keep PerpsController method action docs in sync after the setProLayoutPreferences signature/docs change.
aganglada
left a comment
There was a problem hiding this comment.
Focused on the merge helper and whether existing perps patterns apply. No approval/block — inline notes on specific lines.
| orderBookPosition: 'left' | 'right'; | ||
| orderFormPosition: 'left' | 'right'; | ||
| positionsSideFilter: ProPositionsSideFilter; | ||
| positionsSortConfig: ProPositionsSortConfig; |
There was a problem hiding this comment.
Nesting positionsSortConfig is the only reason we need a custom merge helper — the original proLayoutPreferences bag from #9550 was intentionally flat.
Closest in-package precedent is marketFilterPreferences, which stores sort as two top-level scalars (optionId + direction) with a simple pref ?? defaults getter and an atomic setter. Consider flattening to positionsSortField + positionsSortDirection here so getter/setter/selector can stay on shallow spread and you can drop mergeProLayoutPreferences entirely.
If Mobile's UI state is nested, mapping at the boundary is a small cost for a much simpler persisted shape.
| * @param prefs - Partial preferences from persisted state or a setter patch. | ||
| * @returns A fully-populated `ProLayoutPreferences` object. | ||
| */ | ||
| export const mergeProLayoutPreferences = ( |
There was a problem hiding this comment.
If you keep the nested object, this helper only deep-merges positionsSortConfig over defaults, not over successive layers (persisted state → patch). That forces the setter to re-implement nested merge before calling in.
Consider a variadic signature that folds layers in one place:
exportconstmergeProLayoutPreferences=(
...layers: Array<ProLayoutPreferencesPatch|null|undefined>): ProLayoutPreferences=>{/* merge top-level + accumulate positionsSortConfig across layers */};Then:
- getter:
mergeProLayoutPreferences(this.state.proLayoutPreferences) - setter:
mergeProLayoutPreferences(state.proLayoutPreferences, patch)
No need for deepmerge (devDependency only here) — a ~15-line loop is enough for one nested key.
| ...state.proLayoutPreferences, | ||
| ...patch, | ||
| }; | ||
| ...(patch.positionsSortConfig |
There was a problem hiding this comment.
This manual positionsSortConfig spread exists because { ...state, ...patch } clobbers the nested object when the patch only supplies field or direction. The logic belongs inside mergeProLayoutPreferences (see comment on the helper) so the setter can be:
state.proLayoutPreferences=mergeProLayoutPreferences(state.proLayoutPreferences,patch,);Alternative if you want to avoid deep merge altogether: keep nested shape but add an atomic setPositionsSortConfig(config) (like saveMarketFilterPreferences(optionId, direction)) and restrict setProLayoutPreferences to shallow top-level patches.
| PerpsMode, | ||
| DEFAULT_PERPS_MODE, | ||
| DEFAULT_PRO_LAYOUT_PREFERENCES, | ||
| mergeProLayoutPreferences, |
There was a problem hiding this comment.
Do consumers need mergeProLayoutPreferences on the public surface? Getter/setter/selector already wrap it. Exporting adds API surface for an internal persistence detail — I'd keep it module-private unless Mobile has a concrete out-of-controller hydration path that needs it.
Store positionsSortField and positionsSortDirection as top-level scalars like marketFilterPreferences, dropping the nested sort config and merge helper.
Uh oh!
There was an error while loading. Please reload this page.
## Explanation In Pro mode, mobile's Positions/Orders panel has **independent** sort configs, and the same should be true for side filter: a user can keep Long on Positions and Short on Orders. [MetaMask#9838](MetaMask#9838) persisted the Positions sort and side filter on `proLayoutPreferences`, but Orders sort/filter still lived in local React state and reset on remount (e.g. market navigation via stack push). This extends the existing network-independent flat `proLayoutPreferences` bag with: - `ordersSideFilter` — default `'all'` - `ordersSortField` — default `'time'` - `ordersSortDirection` — default `'desc'` Field unions match mobile's Orders panel: side filter `'all' | 'long' | 'short'`, sort `'orderValue' | 'size' | 'price' | 'time'`. Values are stored as top-level scalars (same shape as the positions fields), so getter/setter/selector stay on shallow spread with no custom merge helper. Callers continue to use `getProLayoutPreferences()` / `setProLayoutPreferences(patch)`. `ordersSideFilter` is independent of `positionsSideFilter`. **TypeScript breaking:** consumers that construct a full `ProLayoutPreferences` object must include the new required fields. Runtime persisted state is forward-compatible via merge-over-defaults. ## References * Fixes https://consensyssoftware.atlassian.net/browse/TAT-3644 * Related: MetaMask#9838 * Related: MetaMask/metamask-mobile#34667 ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Preference schema extension with defaults merge; breaking only for code that manually constructs a full `ProLayoutPreferences` object without the new fields. > > **Overview** > **BREAKING:** Extends flat `ProLayoutPreferences` with **`ordersSideFilter`**, **`ordersSortField`**, and **`ordersSortDirection`** (defaults `'all'`, `'time'`, `'desc'`) so Pro **Orders** panel filter/sort persist separately from Positions across markets and restarts, using the same `getProLayoutPreferences()` / `setProLayoutPreferences(patch)` flow. > > Adds exported types `ProOrdersSideFilter`, `ProOrdersSortField`, and `ProOrdersSortDirection`; updates `DEFAULT_PRO_LAYOUT_PREFERENCES`, package exports, changelog, and tests (including independence from positions prefs and merge-over-defaults for older persisted state). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ceb550e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Explanation
In Pro mode, mobile needs Positions/Orders panel sort and side-filter preferences to persist across markets and app restarts. Today those values live in local React state and reset on remount (e.g. market navigation via stack push).
This extends the existing network-independent flat
proLayoutPreferencesbag (already used forchartExpanded) with:positionsSideFilter— default'all'positionsSortField— default'positionValue'positionsSortDirection— default'desc'Sort is stored as two top-level scalars (same shape as
marketFilterPreferences), so getter/setter/selector stay on shallow spread with no custom merge helper. Callers continue to usegetProLayoutPreferences()/setProLayoutPreferences(patch).TypeScript breaking: consumers that construct a full
ProLayoutPreferencesobject must include the new required fields. Runtime persisted state is forward-compatible via merge-over-defaults.Mobile consumer PR: MetaMask/metamask-mobile#34667 (TAT-3644).
References
Checklist
Note
Low Risk
UI preference persistence only; runtime remains forward-compatible via merge-over-defaults, with a TypeScript-only break for full-object constructors.
Overview
Extends the flat
ProLayoutPreferencesbag so Pro Positions/Orders side filter and sort survive market navigation and app restarts via the existinggetProLayoutPreferences()/setProLayoutPreferences(patch)API.Adds three required fields with defaults:
positionsSideFilter('all'),positionsSortField('positionValue'), andpositionsSortDirection('desc'), plus exported typesProPositionsSideFilter,ProPositionsSortField, andProPositionsSortDirection.Breaking for TypeScript: callers that construct a full
ProLayoutPreferencesobject must include the new fields. Older persisted state stays valid at runtime because the getter andselectProLayoutPreferencesstill merge overDEFAULT_PRO_LAYOUT_PREFERENCES.Reviewed by Cursor Bugbot for commit 6ec9a8f. Bugbot is set up for automated code reviews on this repo. Configure here.