From cbf60d3f85b6abb696d96b1c6b0a39a36b939d63 Mon Sep 17 00:00:00 2001 From: Michal Szorad Date: Wed, 12 Aug 2026 12:33:19 +0200 Subject: [PATCH 1/3] feat(perps): persist Pro positions sort and side filter prefs Add positionsSideFilter and positionsSortConfig to proLayoutPreferences so mobile can persist Positions/Orders panel preferences across markets and restarts. --- packages/perps-controller/CHANGELOG.md | 1 + .../perps-controller/src/PerpsController.ts | 34 ++++++--- .../src/constants/perpsConfig.ts | 71 +++++++++++++++++-- packages/perps-controller/src/index.ts | 5 ++ packages/perps-controller/src/selectors.ts | 8 +-- .../src/PerpsController.configuration.test.ts | 67 ++++++++++++++++- .../tests/src/selectors.test.ts | 28 ++++++++ 7 files changed, 195 insertions(+), 19 deletions(-) diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index aa75828e2ee..a9e986ee7b9 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `positionsSideFilter` and `positionsSortConfig` to `ProLayoutPreferences` (defaults `'all'` and `{ field: 'positionValue', direction: 'desc' }`) so Pro Positions/Orders panel sort and side-filter preferences persist across markets and app restarts via the existing `getProLayoutPreferences()` / `setProLayoutPreferences(patch)` API; export `ProPositionsSideFilter`, `ProPositionsSortField`, `ProPositionsSortConfig`, `ProLayoutPreferencesPatch`, and `mergeProLayoutPreferences` (deep-merges nested `positionsSortConfig` over defaults for predated persisted state) - **BREAKING:** Add strategy placement order types to `OrderType`: `twap`, `scale`, and `chase`, placeable through `placeOrder` alongside the existing `market`, `limit`, and trigger types ([#9832](https://github.com/MetaMask/core/pull/9832)) - `OrderType` is a wider union again, so — exactly as for the trigger types added in 11.0.0 — any consumer signature that narrows it back to a smaller set no longer accepts a value typed `OrderType`. Such signatures must widen to `OrderType` or narrow explicitly at the call site. - A strategy placement expands one request into an execution schedule rather than a single resting order, so `OrderResult.orderId` carries a _handle_ — a venue TWAP id, or a client-generated group/session id — rather than an exchange order id. Its documentation says so; the individual exchange ids are in `childOrderIds`. diff --git a/packages/perps-controller/src/PerpsController.ts b/packages/perps-controller/src/PerpsController.ts index aa3492a67b4..2728d0ecf66 100644 --- a/packages/perps-controller/src/PerpsController.ts +++ b/packages/perps-controller/src/PerpsController.ts @@ -24,6 +24,7 @@ import { PerpsMeasurementName } from './constants/performanceMetrics.js'; import type { SortOptionId, ProLayoutPreferences, + ProLayoutPreferencesPatch, PerpsMode, } from './constants/perpsConfig.js'; import { @@ -35,6 +36,7 @@ import { MAX_SLIPPAGE_BOUNDS, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES, + mergeProLayoutPreferences, } from './constants/perpsConfig.js'; import type { PerpsControllerMethodActions } from './PerpsController-method-action-types.js'; import { PERPS_ERROR_CODES } from './perpsErrorCodes.js'; @@ -249,8 +251,15 @@ export { PerpsMode, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES, + mergeProLayoutPreferences, +} from './constants/perpsConfig.js'; +export type { + ProLayoutPreferences, + ProLayoutPreferencesPatch, + ProPositionsSideFilter, + ProPositionsSortConfig, + ProPositionsSortField, } from './constants/perpsConfig.js'; -export type { ProLayoutPreferences } from './constants/perpsConfig.js'; /** * State shape for PerpsController @@ -5141,26 +5150,33 @@ export class PerpsController extends BaseController< getProLayoutPreferences(): ProLayoutPreferences { // Merge over defaults so callers always receive a fully-populated object, // even if the persisted state predates one of the fields. - return { - ...DEFAULT_PRO_LAYOUT_PREFERENCES, - ...this.state.proLayoutPreferences, - }; + return mergeProLayoutPreferences(this.state.proLayoutPreferences); } /** * Update the user's pro-mode layout preferences. * * Patch-style setter: only the provided fields are updated, the rest are - * preserved. This keeps the signature stable as new layout fields are added. + * preserved. Nested `positionsSortConfig` is deep-merged so callers can + * patch a single sort field without clobbering direction (or vice versa). + * This keeps the signature stable as new layout fields are added. * * @param patch - Partial set of pro-mode layout preferences to update. */ - setProLayoutPreferences(patch: Partial): void { + setProLayoutPreferences(patch: ProLayoutPreferencesPatch): void { this.update((state) => { - state.proLayoutPreferences = { + state.proLayoutPreferences = mergeProLayoutPreferences({ ...state.proLayoutPreferences, ...patch, - }; + ...(patch.positionsSortConfig + ? { + positionsSortConfig: { + ...state.proLayoutPreferences.positionsSortConfig, + ...patch.positionsSortConfig, + }, + } + : {}), + }); }); } diff --git a/packages/perps-controller/src/constants/perpsConfig.ts b/packages/perps-controller/src/constants/perpsConfig.ts index 1b2f713139a..1b1513c9134 100644 --- a/packages/perps-controller/src/constants/perpsConfig.ts +++ b/packages/perps-controller/src/constants/perpsConfig.ts @@ -486,19 +486,45 @@ export enum PerpsMode { Pro = 'pro', } +/** + * Side filter for the Pro Positions/Orders panel (long/short/all). + * + * Shared across markets via `proLayoutPreferences.positionsSideFilter`. + */ +export type ProPositionsSideFilter = 'all' | 'long' | 'short'; + +/** + * Sort fields available on the Pro Positions list. + */ +export type ProPositionsSortField = + | 'positionValue' + | 'unrealizedPnl' + | 'fundingRate'; + +/** + * Sort configuration for the Pro Positions list. + */ +export type ProPositionsSortConfig = { + field: ProPositionsSortField; + direction: 'asc' | 'desc'; +}; + /** * Pro-mode layout preferences (network-independent). * - * Flat object that persists across markets (unlike the per-market - * `tradeConfigurations`). `chartExpanded` and the `*Position` fields are - * reserved for future container-position UI and are kept here now so no - * state-shape migration is needed when that UI ships. + * Persists across markets (unlike the per-market `tradeConfigurations`). + * `chartExpanded` and the `*Position` fields are reserved for future + * container-position UI. `positionsSideFilter` / `positionsSortConfig` back + * the Positions/Orders panel sort and side filter so they survive market + * navigation and app restarts. */ export type ProLayoutPreferences = { orderBookExpanded: boolean; chartExpanded: boolean; orderBookPosition: 'left' | 'right'; orderFormPosition: 'left' | 'right'; + positionsSideFilter: ProPositionsSideFilter; + positionsSortConfig: ProPositionsSortConfig; }; /** @@ -513,8 +539,45 @@ export const DEFAULT_PRO_LAYOUT_PREFERENCES: ProLayoutPreferences = { chartExpanded: false, orderBookPosition: 'left', orderFormPosition: 'right', + positionsSideFilter: 'all', + positionsSortConfig: { + field: 'positionValue', + direction: 'desc', + }, }; +/** + * Patch shape for `setProLayoutPreferences`. + * + * Top-level fields are optional; nested `positionsSortConfig` may also be + * partially specified so a caller can update only `field` or only `direction`. + */ +export type ProLayoutPreferencesPatch = Partial< + Omit +> & { + positionsSortConfig?: Partial; +}; + +/** + * Merge a partial/persisted pro-layout preference blob over defaults. + * + * Nested `positionsSortConfig` is deep-merged so a persisted object that + * predates one of its fields still yields a fully-populated config. + * + * @param prefs - Partial preferences from persisted state or a setter patch. + * @returns A fully-populated `ProLayoutPreferences` object. + */ +export const mergeProLayoutPreferences = ( + prefs?: ProLayoutPreferencesPatch | null, +): ProLayoutPreferences => ({ + ...DEFAULT_PRO_LAYOUT_PREFERENCES, + ...prefs, + positionsSortConfig: { + ...DEFAULT_PRO_LAYOUT_PREFERENCES.positionsSortConfig, + ...prefs?.positionsSortConfig, + }, +}); + /** * Default Perps interface mode. */ diff --git a/packages/perps-controller/src/index.ts b/packages/perps-controller/src/index.ts index 4e1b7bf80e3..9d5f0c34df5 100644 --- a/packages/perps-controller/src/index.ts +++ b/packages/perps-controller/src/index.ts @@ -35,6 +35,7 @@ export { PerpsMode, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES, + mergeProLayoutPreferences, } from './PerpsController.js'; export type { PerpsControllerState, @@ -44,6 +45,10 @@ export type { PerpsControllerActions, PerpsControllerEvents, ProLayoutPreferences, + ProLayoutPreferencesPatch, + ProPositionsSideFilter, + ProPositionsSortConfig, + ProPositionsSortField, } from './PerpsController.js'; export type { PerpsControllerCalculateFeesAction, diff --git a/packages/perps-controller/src/selectors.ts b/packages/perps-controller/src/selectors.ts index b289e223f96..341d79ed03e 100644 --- a/packages/perps-controller/src/selectors.ts +++ b/packages/perps-controller/src/selectors.ts @@ -4,8 +4,8 @@ import { MARKET_SORTING_CONFIG, PERPS_CONSTANTS, SortOptionId, - DEFAULT_PRO_LAYOUT_PREFERENCES, DEFAULT_PERPS_MODE, + mergeProLayoutPreferences, } from './constants/perpsConfig.js'; import type { PerpsMode, @@ -249,10 +249,8 @@ export const selectMarketFilterPreferences = ( */ export const selectProLayoutPreferences = ( state: PerpsControllerState, -): ProLayoutPreferences => ({ - ...DEFAULT_PRO_LAYOUT_PREFERENCES, - ...state?.proLayoutPreferences, -}); +): ProLayoutPreferences => + mergeProLayoutPreferences(state?.proLayoutPreferences); /** * Select the current Perps interface mode (lite/pro). diff --git a/packages/perps-controller/tests/src/PerpsController.configuration.test.ts b/packages/perps-controller/tests/src/PerpsController.configuration.test.ts index ecdfd09ad7b..5e6d2986f08 100644 --- a/packages/perps-controller/tests/src/PerpsController.configuration.test.ts +++ b/packages/perps-controller/tests/src/PerpsController.configuration.test.ts @@ -694,12 +694,17 @@ describe('PerpsController', () => { }); describe('pro layout preferences', () => { - it('defaults to collapsed order book, collapsed chart, and reserved positions', () => { + it('defaults to collapsed order book, collapsed chart, reserved positions, and positions sort/filter defaults', () => { expect(controller.getProLayoutPreferences()).toEqual({ orderBookExpanded: false, chartExpanded: false, orderBookPosition: 'left', orderFormPosition: 'right', + positionsSideFilter: 'all', + positionsSortConfig: { + field: 'positionValue', + direction: 'desc', + }, }); }); @@ -711,6 +716,11 @@ describe('PerpsController', () => { chartExpanded: false, orderBookPosition: 'left', orderFormPosition: 'right', + positionsSideFilter: 'all', + positionsSortConfig: { + field: 'positionValue', + direction: 'desc', + }, }); }); @@ -718,12 +728,35 @@ describe('PerpsController', () => { controller.setProLayoutPreferences({ orderBookExpanded: true }); controller.setProLayoutPreferences({ orderBookPosition: 'right' }); controller.setProLayoutPreferences({ orderFormPosition: 'left' }); + controller.setProLayoutPreferences({ positionsSideFilter: 'long' }); + controller.setProLayoutPreferences({ + positionsSortConfig: { field: 'unrealizedPnl', direction: 'asc' }, + }); expect(controller.getProLayoutPreferences()).toEqual({ orderBookExpanded: true, chartExpanded: false, orderBookPosition: 'right', orderFormPosition: 'left', + positionsSideFilter: 'long', + positionsSortConfig: { + field: 'unrealizedPnl', + direction: 'asc', + }, + }); + }); + + it('deep-merges positionsSortConfig so a partial sort patch preserves the other field', () => { + controller.setProLayoutPreferences({ + positionsSortConfig: { field: 'fundingRate', direction: 'asc' }, + }); + controller.setProLayoutPreferences({ + positionsSortConfig: { field: 'unrealizedPnl' }, + }); + + expect(controller.getProLayoutPreferences().positionsSortConfig).toEqual({ + field: 'unrealizedPnl', + direction: 'asc', }); }); @@ -746,6 +779,38 @@ describe('PerpsController', () => { chartExpanded: false, orderBookPosition: 'left', orderFormPosition: 'right', + positionsSideFilter: 'all', + positionsSortConfig: { + field: 'positionValue', + direction: 'desc', + }, + }); + }); + + it('fills in nested positionsSortConfig defaults when only field is persisted', () => { + controller.testUpdate((state) => { + state.proLayoutPreferences = { + orderBookExpanded: false, + chartExpanded: false, + orderBookPosition: 'left', + orderFormPosition: 'right', + positionsSideFilter: 'short', + positionsSortConfig: { + field: 'fundingRate', + }, + } as PerpsControllerState['proLayoutPreferences']; + }); + + expect(controller.getProLayoutPreferences()).toEqual({ + orderBookExpanded: false, + chartExpanded: false, + orderBookPosition: 'left', + orderFormPosition: 'right', + positionsSideFilter: 'short', + positionsSortConfig: { + field: 'fundingRate', + direction: 'desc', + }, }); }); }); diff --git a/packages/perps-controller/tests/src/selectors.test.ts b/packages/perps-controller/tests/src/selectors.test.ts index 220fede9c5b..918f117938c 100644 --- a/packages/perps-controller/tests/src/selectors.test.ts +++ b/packages/perps-controller/tests/src/selectors.test.ts @@ -636,6 +636,11 @@ describe('PerpsController selectors', () => { chartExpanded: false, orderBookPosition: 'left', orderFormPosition: 'right', + positionsSideFilter: 'all', + positionsSortConfig: { + field: 'positionValue', + direction: 'desc', + }, }; it('returns the pro-mode layout preferences', () => { @@ -644,6 +649,11 @@ describe('PerpsController selectors', () => { chartExpanded: true, orderBookPosition: 'right' as const, orderFormPosition: 'left' as const, + positionsSideFilter: 'long' as const, + positionsSortConfig: { + field: 'unrealizedPnl' as const, + direction: 'asc' as const, + }, }; const state = { proLayoutPreferences, @@ -665,6 +675,24 @@ describe('PerpsController selectors', () => { }); }); + it('deep-merges nested positionsSortConfig defaults', () => { + const state = { + proLayoutPreferences: { + positionsSideFilter: 'short', + positionsSortConfig: { field: 'fundingRate' }, + }, + } as unknown as PerpsControllerState; + + expect(selectProLayoutPreferences(state)).toStrictEqual({ + ...defaults, + positionsSideFilter: 'short', + positionsSortConfig: { + field: 'fundingRate', + direction: 'desc', + }, + }); + }); + it('returns defaults when the state slice is missing', () => { const state = {} as unknown as PerpsControllerState; From 5a2195afd2518a5f61d0b538fd536f48205c8ec1 Mon Sep 17 00:00:00 2001 From: Michal Szorad Date: Wed, 12 Aug 2026 13:31:01 +0200 Subject: [PATCH 2/3] fix(perps): link changelog to PR and regenerate messenger action types Satisfy CI changelog link requirements and keep PerpsController method action docs in sync after the setProLayoutPreferences signature/docs change. --- packages/perps-controller/CHANGELOG.md | 3 ++- .../src/PerpsController-method-action-types.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index a9e986ee7b9..62ebb19e28d 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `positionsSideFilter` and `positionsSortConfig` to `ProLayoutPreferences` (defaults `'all'` and `{ field: 'positionValue', direction: 'desc' }`) so Pro Positions/Orders panel sort and side-filter preferences persist across markets and app restarts via the existing `getProLayoutPreferences()` / `setProLayoutPreferences(patch)` API; export `ProPositionsSideFilter`, `ProPositionsSortField`, `ProPositionsSortConfig`, `ProLayoutPreferencesPatch`, and `mergeProLayoutPreferences` (deep-merges nested `positionsSortConfig` over defaults for predated persisted state) +- **BREAKING:** Add `positionsSideFilter` and `positionsSortConfig` to `ProLayoutPreferences` (defaults `'all'` and `{ field: 'positionValue', direction: 'desc' }`) so Pro Positions/Orders panel sort and side-filter preferences persist across markets and app restarts via the existing `getProLayoutPreferences()` / `setProLayoutPreferences(patch)` API; export `ProPositionsSideFilter`, `ProPositionsSortField`, `ProPositionsSortConfig`, `ProLayoutPreferencesPatch`, and `mergeProLayoutPreferences` (deep-merges nested `positionsSortConfig` over defaults for predated persisted state) ([#9838](https://github.com/MetaMask/core/pull/9838)) + - Consumers that construct a full `ProLayoutPreferences` object (instead of using `DEFAULT_PRO_LAYOUT_PREFERENCES`, the getter, or the patch setter) must include the new fields. Persisted state that predates them remains valid at runtime because the getter/selector merge over defaults. - **BREAKING:** Add strategy placement order types to `OrderType`: `twap`, `scale`, and `chase`, placeable through `placeOrder` alongside the existing `market`, `limit`, and trigger types ([#9832](https://github.com/MetaMask/core/pull/9832)) - `OrderType` is a wider union again, so — exactly as for the trigger types added in 11.0.0 — any consumer signature that narrows it back to a smaller set no longer accepts a value typed `OrderType`. Such signatures must widen to `OrderType` or narrow explicitly at the call site. - A strategy placement expands one request into an execution schedule rather than a single resting order, so `OrderResult.orderId` carries a _handle_ — a venue TWAP id, or a client-generated group/session id — rather than an exchange order id. Its documentation says so; the individual exchange ids are in `childOrderIds`. diff --git a/packages/perps-controller/src/PerpsController-method-action-types.ts b/packages/perps-controller/src/PerpsController-method-action-types.ts index 2c005917654..44fcc18c414 100644 --- a/packages/perps-controller/src/PerpsController-method-action-types.ts +++ b/packages/perps-controller/src/PerpsController-method-action-types.ts @@ -975,7 +975,9 @@ export type PerpsControllerGetProLayoutPreferencesAction = { * Update the user's pro-mode layout preferences. * * Patch-style setter: only the provided fields are updated, the rest are - * preserved. This keeps the signature stable as new layout fields are added. + * preserved. Nested `positionsSortConfig` is deep-merged so callers can + * patch a single sort field without clobbering direction (or vice versa). + * This keeps the signature stable as new layout fields are added. * * @param patch - Partial set of pro-mode layout preferences to update. */ From 6ec9a8f45d5ae897ad7c569071d9a262cb48ee76 Mon Sep 17 00:00:00 2001 From: Michal Szorad Date: Wed, 12 Aug 2026 15:31:02 +0200 Subject: [PATCH 3/3] refactor(perps): flatten positions sort prefs on proLayoutPreferences Store positionsSortField and positionsSortDirection as top-level scalars like marketFilterPreferences, dropping the nested sort config and merge helper. --- packages/perps-controller/CHANGELOG.md | 2 +- .../PerpsController-method-action-types.ts | 4 +- .../perps-controller/src/PerpsController.ts | 29 +++----- .../src/constants/perpsConfig.ts | 59 ++++----------- packages/perps-controller/src/index.ts | 4 +- packages/perps-controller/src/selectors.ts | 8 ++- .../src/PerpsController.configuration.test.ts | 72 ++++++------------- .../tests/src/selectors.test.ts | 30 ++------ 8 files changed, 55 insertions(+), 153 deletions(-) diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index 62ebb19e28d..2e7dbf0a5a3 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **BREAKING:** Add `positionsSideFilter` and `positionsSortConfig` to `ProLayoutPreferences` (defaults `'all'` and `{ field: 'positionValue', direction: 'desc' }`) so Pro Positions/Orders panel sort and side-filter preferences persist across markets and app restarts via the existing `getProLayoutPreferences()` / `setProLayoutPreferences(patch)` API; export `ProPositionsSideFilter`, `ProPositionsSortField`, `ProPositionsSortConfig`, `ProLayoutPreferencesPatch`, and `mergeProLayoutPreferences` (deep-merges nested `positionsSortConfig` over defaults for predated persisted state) ([#9838](https://github.com/MetaMask/core/pull/9838)) +- **BREAKING:** Add `positionsSideFilter`, `positionsSortField`, and `positionsSortDirection` to the flat `ProLayoutPreferences` object (defaults `'all'`, `'positionValue'`, `'desc'`) so Pro Positions/Orders panel sort and side-filter preferences persist across markets and app restarts via the existing `getProLayoutPreferences()` / `setProLayoutPreferences(patch)` API; export `ProPositionsSideFilter`, `ProPositionsSortField`, and `ProPositionsSortDirection` ([#9838](https://github.com/MetaMask/core/pull/9838)) - Consumers that construct a full `ProLayoutPreferences` object (instead of using `DEFAULT_PRO_LAYOUT_PREFERENCES`, the getter, or the patch setter) must include the new fields. Persisted state that predates them remains valid at runtime because the getter/selector merge over defaults. - **BREAKING:** Add strategy placement order types to `OrderType`: `twap`, `scale`, and `chase`, placeable through `placeOrder` alongside the existing `market`, `limit`, and trigger types ([#9832](https://github.com/MetaMask/core/pull/9832)) - `OrderType` is a wider union again, so — exactly as for the trigger types added in 11.0.0 — any consumer signature that narrows it back to a smaller set no longer accepts a value typed `OrderType`. Such signatures must widen to `OrderType` or narrow explicitly at the call site. diff --git a/packages/perps-controller/src/PerpsController-method-action-types.ts b/packages/perps-controller/src/PerpsController-method-action-types.ts index 44fcc18c414..2c005917654 100644 --- a/packages/perps-controller/src/PerpsController-method-action-types.ts +++ b/packages/perps-controller/src/PerpsController-method-action-types.ts @@ -975,9 +975,7 @@ export type PerpsControllerGetProLayoutPreferencesAction = { * Update the user's pro-mode layout preferences. * * Patch-style setter: only the provided fields are updated, the rest are - * preserved. Nested `positionsSortConfig` is deep-merged so callers can - * patch a single sort field without clobbering direction (or vice versa). - * This keeps the signature stable as new layout fields are added. + * preserved. This keeps the signature stable as new layout fields are added. * * @param patch - Partial set of pro-mode layout preferences to update. */ diff --git a/packages/perps-controller/src/PerpsController.ts b/packages/perps-controller/src/PerpsController.ts index 2728d0ecf66..243d6d921e6 100644 --- a/packages/perps-controller/src/PerpsController.ts +++ b/packages/perps-controller/src/PerpsController.ts @@ -24,7 +24,6 @@ import { PerpsMeasurementName } from './constants/performanceMetrics.js'; import type { SortOptionId, ProLayoutPreferences, - ProLayoutPreferencesPatch, PerpsMode, } from './constants/perpsConfig.js'; import { @@ -36,7 +35,6 @@ import { MAX_SLIPPAGE_BOUNDS, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES, - mergeProLayoutPreferences, } from './constants/perpsConfig.js'; import type { PerpsControllerMethodActions } from './PerpsController-method-action-types.js'; import { PERPS_ERROR_CODES } from './perpsErrorCodes.js'; @@ -251,13 +249,11 @@ export { PerpsMode, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES, - mergeProLayoutPreferences, } from './constants/perpsConfig.js'; export type { ProLayoutPreferences, - ProLayoutPreferencesPatch, ProPositionsSideFilter, - ProPositionsSortConfig, + ProPositionsSortDirection, ProPositionsSortField, } from './constants/perpsConfig.js'; @@ -5150,33 +5146,26 @@ export class PerpsController extends BaseController< getProLayoutPreferences(): ProLayoutPreferences { // Merge over defaults so callers always receive a fully-populated object, // even if the persisted state predates one of the fields. - return mergeProLayoutPreferences(this.state.proLayoutPreferences); + return { + ...DEFAULT_PRO_LAYOUT_PREFERENCES, + ...this.state.proLayoutPreferences, + }; } /** * Update the user's pro-mode layout preferences. * * Patch-style setter: only the provided fields are updated, the rest are - * preserved. Nested `positionsSortConfig` is deep-merged so callers can - * patch a single sort field without clobbering direction (or vice versa). - * This keeps the signature stable as new layout fields are added. + * preserved. This keeps the signature stable as new layout fields are added. * * @param patch - Partial set of pro-mode layout preferences to update. */ - setProLayoutPreferences(patch: ProLayoutPreferencesPatch): void { + setProLayoutPreferences(patch: Partial): void { this.update((state) => { - state.proLayoutPreferences = mergeProLayoutPreferences({ + state.proLayoutPreferences = { ...state.proLayoutPreferences, ...patch, - ...(patch.positionsSortConfig - ? { - positionsSortConfig: { - ...state.proLayoutPreferences.positionsSortConfig, - ...patch.positionsSortConfig, - }, - } - : {}), - }); + }; }); } diff --git a/packages/perps-controller/src/constants/perpsConfig.ts b/packages/perps-controller/src/constants/perpsConfig.ts index 1b1513c9134..8ea127d834e 100644 --- a/packages/perps-controller/src/constants/perpsConfig.ts +++ b/packages/perps-controller/src/constants/perpsConfig.ts @@ -502,21 +502,19 @@ export type ProPositionsSortField = | 'fundingRate'; /** - * Sort configuration for the Pro Positions list. + * Sort direction for the Pro Positions list. */ -export type ProPositionsSortConfig = { - field: ProPositionsSortField; - direction: 'asc' | 'desc'; -}; +export type ProPositionsSortDirection = 'asc' | 'desc'; /** * Pro-mode layout preferences (network-independent). * - * Persists across markets (unlike the per-market `tradeConfigurations`). - * `chartExpanded` and the `*Position` fields are reserved for future - * container-position UI. `positionsSideFilter` / `positionsSortConfig` back - * the Positions/Orders panel sort and side filter so they survive market - * navigation and app restarts. + * Flat object that persists across markets (unlike the per-market + * `tradeConfigurations`). `chartExpanded` and the `*Position` fields are + * reserved for future container-position UI. `positionsSideFilter` / + * `positionsSortField` / `positionsSortDirection` back the Positions/Orders + * panel sort and side filter so they survive market navigation and app + * restarts. */ export type ProLayoutPreferences = { orderBookExpanded: boolean; @@ -524,7 +522,8 @@ export type ProLayoutPreferences = { orderBookPosition: 'left' | 'right'; orderFormPosition: 'left' | 'right'; positionsSideFilter: ProPositionsSideFilter; - positionsSortConfig: ProPositionsSortConfig; + positionsSortField: ProPositionsSortField; + positionsSortDirection: ProPositionsSortDirection; }; /** @@ -540,44 +539,10 @@ export const DEFAULT_PRO_LAYOUT_PREFERENCES: ProLayoutPreferences = { orderBookPosition: 'left', orderFormPosition: 'right', positionsSideFilter: 'all', - positionsSortConfig: { - field: 'positionValue', - direction: 'desc', - }, -}; - -/** - * Patch shape for `setProLayoutPreferences`. - * - * Top-level fields are optional; nested `positionsSortConfig` may also be - * partially specified so a caller can update only `field` or only `direction`. - */ -export type ProLayoutPreferencesPatch = Partial< - Omit -> & { - positionsSortConfig?: Partial; + positionsSortField: 'positionValue', + positionsSortDirection: 'desc', }; -/** - * Merge a partial/persisted pro-layout preference blob over defaults. - * - * Nested `positionsSortConfig` is deep-merged so a persisted object that - * predates one of its fields still yields a fully-populated config. - * - * @param prefs - Partial preferences from persisted state or a setter patch. - * @returns A fully-populated `ProLayoutPreferences` object. - */ -export const mergeProLayoutPreferences = ( - prefs?: ProLayoutPreferencesPatch | null, -): ProLayoutPreferences => ({ - ...DEFAULT_PRO_LAYOUT_PREFERENCES, - ...prefs, - positionsSortConfig: { - ...DEFAULT_PRO_LAYOUT_PREFERENCES.positionsSortConfig, - ...prefs?.positionsSortConfig, - }, -}); - /** * Default Perps interface mode. */ diff --git a/packages/perps-controller/src/index.ts b/packages/perps-controller/src/index.ts index 9d5f0c34df5..d19405776ea 100644 --- a/packages/perps-controller/src/index.ts +++ b/packages/perps-controller/src/index.ts @@ -35,7 +35,6 @@ export { PerpsMode, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES, - mergeProLayoutPreferences, } from './PerpsController.js'; export type { PerpsControllerState, @@ -45,9 +44,8 @@ export type { PerpsControllerActions, PerpsControllerEvents, ProLayoutPreferences, - ProLayoutPreferencesPatch, ProPositionsSideFilter, - ProPositionsSortConfig, + ProPositionsSortDirection, ProPositionsSortField, } from './PerpsController.js'; export type { diff --git a/packages/perps-controller/src/selectors.ts b/packages/perps-controller/src/selectors.ts index 341d79ed03e..b289e223f96 100644 --- a/packages/perps-controller/src/selectors.ts +++ b/packages/perps-controller/src/selectors.ts @@ -4,8 +4,8 @@ import { MARKET_SORTING_CONFIG, PERPS_CONSTANTS, SortOptionId, + DEFAULT_PRO_LAYOUT_PREFERENCES, DEFAULT_PERPS_MODE, - mergeProLayoutPreferences, } from './constants/perpsConfig.js'; import type { PerpsMode, @@ -249,8 +249,10 @@ export const selectMarketFilterPreferences = ( */ export const selectProLayoutPreferences = ( state: PerpsControllerState, -): ProLayoutPreferences => - mergeProLayoutPreferences(state?.proLayoutPreferences); +): ProLayoutPreferences => ({ + ...DEFAULT_PRO_LAYOUT_PREFERENCES, + ...state?.proLayoutPreferences, +}); /** * Select the current Perps interface mode (lite/pro). diff --git a/packages/perps-controller/tests/src/PerpsController.configuration.test.ts b/packages/perps-controller/tests/src/PerpsController.configuration.test.ts index 5e6d2986f08..d5de543eb8b 100644 --- a/packages/perps-controller/tests/src/PerpsController.configuration.test.ts +++ b/packages/perps-controller/tests/src/PerpsController.configuration.test.ts @@ -701,10 +701,8 @@ describe('PerpsController', () => { orderBookPosition: 'left', orderFormPosition: 'right', positionsSideFilter: 'all', - positionsSortConfig: { - field: 'positionValue', - direction: 'desc', - }, + positionsSortField: 'positionValue', + positionsSortDirection: 'desc', }); }); @@ -717,10 +715,8 @@ describe('PerpsController', () => { orderBookPosition: 'left', orderFormPosition: 'right', positionsSideFilter: 'all', - positionsSortConfig: { - field: 'positionValue', - direction: 'desc', - }, + positionsSortField: 'positionValue', + positionsSortDirection: 'desc', }); }); @@ -730,7 +726,8 @@ describe('PerpsController', () => { controller.setProLayoutPreferences({ orderFormPosition: 'left' }); controller.setProLayoutPreferences({ positionsSideFilter: 'long' }); controller.setProLayoutPreferences({ - positionsSortConfig: { field: 'unrealizedPnl', direction: 'asc' }, + positionsSortField: 'unrealizedPnl', + positionsSortDirection: 'asc', }); expect(controller.getProLayoutPreferences()).toEqual({ @@ -739,24 +736,28 @@ describe('PerpsController', () => { orderBookPosition: 'right', orderFormPosition: 'left', positionsSideFilter: 'long', - positionsSortConfig: { - field: 'unrealizedPnl', - direction: 'asc', - }, + positionsSortField: 'unrealizedPnl', + positionsSortDirection: 'asc', }); }); - it('deep-merges positionsSortConfig so a partial sort patch preserves the other field', () => { + it('updates sort field without clobbering sort direction', () => { controller.setProLayoutPreferences({ - positionsSortConfig: { field: 'fundingRate', direction: 'asc' }, + positionsSortField: 'fundingRate', + positionsSortDirection: 'asc', }); controller.setProLayoutPreferences({ - positionsSortConfig: { field: 'unrealizedPnl' }, + positionsSortField: 'unrealizedPnl', }); - expect(controller.getProLayoutPreferences().positionsSortConfig).toEqual({ - field: 'unrealizedPnl', - direction: 'asc', + expect(controller.getProLayoutPreferences()).toEqual({ + orderBookExpanded: false, + chartExpanded: false, + orderBookPosition: 'left', + orderFormPosition: 'right', + positionsSideFilter: 'all', + positionsSortField: 'unrealizedPnl', + positionsSortDirection: 'asc', }); }); @@ -780,37 +781,8 @@ describe('PerpsController', () => { orderBookPosition: 'left', orderFormPosition: 'right', positionsSideFilter: 'all', - positionsSortConfig: { - field: 'positionValue', - direction: 'desc', - }, - }); - }); - - it('fills in nested positionsSortConfig defaults when only field is persisted', () => { - controller.testUpdate((state) => { - state.proLayoutPreferences = { - orderBookExpanded: false, - chartExpanded: false, - orderBookPosition: 'left', - orderFormPosition: 'right', - positionsSideFilter: 'short', - positionsSortConfig: { - field: 'fundingRate', - }, - } as PerpsControllerState['proLayoutPreferences']; - }); - - expect(controller.getProLayoutPreferences()).toEqual({ - orderBookExpanded: false, - chartExpanded: false, - orderBookPosition: 'left', - orderFormPosition: 'right', - positionsSideFilter: 'short', - positionsSortConfig: { - field: 'fundingRate', - direction: 'desc', - }, + positionsSortField: 'positionValue', + positionsSortDirection: 'desc', }); }); }); diff --git a/packages/perps-controller/tests/src/selectors.test.ts b/packages/perps-controller/tests/src/selectors.test.ts index 918f117938c..01983cd6555 100644 --- a/packages/perps-controller/tests/src/selectors.test.ts +++ b/packages/perps-controller/tests/src/selectors.test.ts @@ -637,10 +637,8 @@ describe('PerpsController selectors', () => { orderBookPosition: 'left', orderFormPosition: 'right', positionsSideFilter: 'all', - positionsSortConfig: { - field: 'positionValue', - direction: 'desc', - }, + positionsSortField: 'positionValue', + positionsSortDirection: 'desc', }; it('returns the pro-mode layout preferences', () => { @@ -650,10 +648,8 @@ describe('PerpsController selectors', () => { orderBookPosition: 'right' as const, orderFormPosition: 'left' as const, positionsSideFilter: 'long' as const, - positionsSortConfig: { - field: 'unrealizedPnl' as const, - direction: 'asc' as const, - }, + positionsSortField: 'unrealizedPnl' as const, + positionsSortDirection: 'asc' as const, }; const state = { proLayoutPreferences, @@ -675,24 +671,6 @@ describe('PerpsController selectors', () => { }); }); - it('deep-merges nested positionsSortConfig defaults', () => { - const state = { - proLayoutPreferences: { - positionsSideFilter: 'short', - positionsSortConfig: { field: 'fundingRate' }, - }, - } as unknown as PerpsControllerState; - - expect(selectProLayoutPreferences(state)).toStrictEqual({ - ...defaults, - positionsSideFilter: 'short', - positionsSortConfig: { - field: 'fundingRate', - direction: 'desc', - }, - }); - }); - it('returns defaults when the state slice is missing', () => { const state = {} as unknown as PerpsControllerState;