feat(perps): implement Pro mode market detail header - #33899
Conversation
Replace the placeholder Pro market header scaffold with the full Figma design: back button, tappable asset identity (token logo, name, leverage tag, market-list caret, and ticker-USD perp subtitle), plus wallet, watchlist favorite, and the read-only Lite/Pro mode pill. Wire the header handlers in PerpsProMarketView (back, market list, watchlist toggle, wallet navigation, and mode switch) and add the corresponding header test IDs and coverage.
|
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. |
PR template — items to address before "Ready for review"Warnings — informational, address before merging:
See docs/readme/ready-for-review.md for the full Definition of Ready for Review. |
Replace the collapsing nested flex layout in the identity column with a content-hugging layout and an explicit max-width title cap (matching Figma), so the asset name, leverage tag, and ticker subtitle render reliably.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 615f90d. Configure here.
Narrow the market prop to Partial with a required symbol so lint:tsc accepts route params after the symbol guard, and add dedicated header unit tests plus view handler coverage for the new Pro header actions.
🧪 Flaky unit test detection✅ All previously detected unit test flakiness issues in this PR have been fixed. This check is informational only and does not block merging. |
Extract header back/market-list/wallet/watchlist/mode handlers into usePerpsProMarketHeaderActions with full unit coverage so new-code coverage clears Sonar's 80% gate (View handler bodies were not credited in the merged CI lcov).
⚡ Performance Test Results
✅ All tests passed · 2 tests · 1 device 📱 Devices tested (1)Android: Google Pixel 8 Pro (v14.0) ✅ Passed Tests (2)
Branch: |
geositta
left a comment
There was a problem hiding this comment.
Looks good and the header actions are behind a hook - that extraction is the right call and I would like to see the Lite view adopt it too.
Two things I would ideally like to fix in this PR: the duplicated identity block (comment 1) and the missing accessibility labels (comment 2). Both are small and both get harder to retrofit once other suites and screens key off this markup.
The rest are refinements you can fold in here or track for the next Pro ticket; none of them should hold up UAT.
| collateral: PERPS_COLLATERAL_SYMBOL, | ||
| }); | ||
|
|
||
| const renderIdentity = (pressed: boolean) => ( |
There was a problem hiding this comment.
This identity block is a near-verbatim copy of PerpsMarketInlineHeader.tsx:97 Same structure, same perps.market_details.perp_pair subtitle, same ArrowRight caret, same rounded-lg p-1 ${pressed ? 'bg-pressed' : ''}, same Pressable + accessibilityLabel={strings('perps.market_details.market_list')} wrapper - only the token size (32 vs 40), gap (2 vs 3), and testID constants differ.
Two copies of the same Figma spec will drift the first time design tweaks the identity row. Would prefer we extract a PerpsMarketIdentity component taking { symbol, name, maxLeverage, size, onPress, testIDs } and have both headers render it. This is the one change I would most like to see land with this PR.
| gap={2} | ||
| > | ||
| {onBackPress ? ( | ||
| <ButtonIcon |
There was a problem hiding this comment.
ButtonIcon sets accessible but derives no label from iconName, so back,
wallet, and favorite each announce as an unlabeled button to VoiceOver/TalkBack. ButtonIconProps extends PressableProps, so accessibilityLabel is typed and passes through to the underlying Pressable — PerpsChartFullscreenModal.tsx:255 already does this.
Add a strings()-sourced label to each, and give the star a state-aware one so the toggle is discoverable without seeing the fill:
<ButtonIcon
iconName={isFavorite ? IconName.StarFilled : IconName.Star}
size={ButtonIconSize.Md}
onPress={onFavoritePress}
accessibilityLabel={strings(
isFavorite
? 'perps.market_details.remove_from_watchlist'
: 'perps.market_details.add_to_watchlist',
)}
testID={PerpsProMarketViewSelectorsIDs.HEADER_FAVORITE_BUTTON}
/>
Those two locale keys need adding - locales/languages/en.json under perps.market_details (which already holds perp_pair and market_list).
| */ | ||
| market: Partial<PerpsMarketData> & { symbol: string }; | ||
| /** Active Perps mode — drives the read-only mode pill on the right. */ | ||
| mode: PerpsMode; |
There was a problem hiding this comment.
PerpsProMarketHeader.tsx:38 and :191
mode is required, but only rendered when onModeChange is supplied
Callers that omit onModeChange must still pass a mode that is never displayed. Either make mode optional alongside the handler, or accept the mode pill as a modeToggle?: ReactNode slot the way PerpsMarketInlineHeader takes endAccessory.
| }, [symbol, isWatchlist, addToWatchlist, removeFromWatchlist]); | ||
|
|
||
| const handlePerpsModeChange = useCallback( | ||
| (nextMode: PerpsMode) => { |
There was a problem hiding this comment.
the hook duplicates logic already in the Lite view handlePerpsModeChange matches PerpsMarketDetailsView.tsx:346-352 line for line, handleMarketListPress matches :875-891, and handleBackPress matches :827-833.
Pulling these into a hook is the right instinct, prefer we finish the job by having PerpsMarketDetailsView consume the same hook, which deletes ~50 lines there and guarantees the two screens track the same analytics
payloads.
| } | ||
|
|
||
| if (isWatchlist) { | ||
| removeFromWatchlist(symbol); |
There was a problem hiding this comment.
unawaited promises from the watchlist actions addToWatchlist /removeFromWatchlist are async.
They swallow their own errors today, so nothing breaks, but the call site relies on that undocumented guarantee. Mark it deliberate (void addToWatchlist(symbol); with a one-line comment on the fire-and-forget/optimistic-update contract) so a future change to those functions surfaces here.
Extract PerpsMarketIdentity for Lite/Pro headers, add VoiceOver labels, make mode optional, and mark watchlist updates as fire-and-forget.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |
|
abretonc7s
left a comment
There was a problem hiding this comment.
Approved with comments
| /> | ||
| </Box> | ||
|
|
||
| <Box flexDirection={BoxFlexDirection.Row} alignItems={BoxAlignItems.Center}> |
There was a problem hiding this comment.
[suggestion] The trailing action row (wallet, favorite, mode pill) has no horizontal gap. Lite’s Pro-enabled endAccessory and design-system HeaderSubpage multi-icon layout both use gap={2} / gap-2. With ButtonIconSize.Md at h-8 w-8, the controls sit flush and will look denser than the Lite header and Figma spacing.
Suggestion: Add gap={2} (or equivalent twClassName="gap-2") on the end-actions Box, matching PerpsMarketDetailsView’s end accessory and HeaderSubpage.
| > | ||
| <PerpsProMarketHeader symbol={symbol} /> | ||
| <PerpsProMarketHeader | ||
| market={{ ...market, symbol: market.symbol }} |
There was a problem hiding this comment.
[suggestion] The header is fed only route.params.market. Lite market details explicitly enrich partial route markets (e.g. Recent Activity / deep links missing formatted maxLeverage) via usePerpsMarkets before rendering identity. On Pro, those entries will omit the leverage tag and fall back to the ticker for the title, so the new header can look incomplete on common entry paths even though the screen still loads.
Suggestion: Mirror Lite’s enrichment (needsEnrichment + markets lookup, or shared helper) before passing market into PerpsProMarketHeader, so name/leverage stay populated when route params are minimal.
| ).toHaveTextContent('BTC-USD perp'); | ||
| }); | ||
|
|
||
| it('wires header actions from usePerpsProMarketHeaderActions', () => { |
There was a problem hiding this comment.
[suggestion] The integration test “wires header actions from usePerpsProMarketHeaderActions” presses back / market list / wallet / favorite, but never the mode pill, even though mockHandlePerpsModeChange is already mocked. Mode switch is a primary header action (and the path that remounts Lite via PerpsMarketDetailsRouter).
Suggestion: Press PerpsModeToggleSelectorsIDs.PRO_SEGMENT (or the rendered active segment) and assert mockHandlePerpsModeChange was called once (with PerpsMode.Lite if the real toggle is used, or just called if the toggle is stubbed).
| * actions remain placeholders until their owning capability is implemented. | ||
| * Left: back button. Center: tappable asset identity (token icon, name, | ||
| * leverage pill, market-list caret, and `[Ticker]-[collateral] perp` | ||
| * subtitle). Right: wallet, favorite (watchlist star), and the read-only |
There was a problem hiding this comment.
[nit] Comments/JSDoc still describe the mode control as a “read-only” Lite/Pro pill. PerpsModeToggle variant="active" is interactive and flips mode (same as Lite market details).
Suggestion: Drop “read-only” and describe it as the active-mode pill that switches Lite/Pro.




Description
Implements the Perps Pro-mode market detail header from Figma, replacing the previous scaffold (symbol + placeholder box).
Motivation: Pro market details already routes to
PerpsProMarketView, but the fixed header was a placeholder. This brings it in line with the design and with the Lite-mode market-detail patterns (back, identity → market list, watchlist, mode pill).Solution:
PerpsProMarketHeaderinto the full layout: back button; tappable asset identity (token logo, name, leverage tag, market-list caret, and[Ticker]-USD perpsubtitle); wallet, favorite star, and read-only Lite/Pro mode pill (PerpsModeTogglevariant="active").PerpsProMarketView(back with home fallback, market list + analytics, watchlist toggle with limit guard, wallet → Perps home, mode switch + flash).Scope note: live price (below the header in
PerpsProMarketSummary) and PnL (inPerpsProPositionsPanel) are unchanged — the Figma header node does not include them.Changelog
CHANGELOG entry: Added the Pro mode market detail header with asset identity, watchlist, and Lite/Pro mode controls
Related issues
Fixes: N/A
Manual testing steps
Screenshots/Recordings
N/A — UI change implemented from Figma; screenshots to be attached after device/simulator verification.
Before
Scaffold header (symbol + muted placeholder box).
After
Figma Pro market header: back · token · name / leverage / caret · subtitle · wallet · star · Pro pill.
Pre-merge author checklist
Performance checks (if applicable)
trace()for usage andaddTokenfor an exampleFor performance guidelines and tooling, see the Performance Guide.
Pre-merge reviewer checklist
Note
Low Risk
UI and navigation/watchlist wiring in Perps with existing patterns; no auth or payment changes. Broad test coverage limits regression risk.
Overview
Replaces the Pro market detail scaffold header (symbol text + placeholder) with the full Figma layout: back, tappable asset identity (logo, name, leverage, market-list caret,
Ticker-USD perpsubtitle), wallet, watchlist star, and Lite/Pro mode pill.Introduces shared
PerpsMarketIdentity(also wired into LitePerpsMarketInlineHeaderdetail layout) andusePerpsProMarketHeaderActionsto centralize navigation, watchlist toggle, analytics on market-list tap, mode switch + flash, and back-with-home-fallback. Adds header test IDs, i18n strings for a11y labels, and unit tests for the header, identity component, hook, and screen integration.Reviewed by Cursor Bugbot for commit 73b0bad. Bugbot is set up for automated code reviews on this repo. Configure here.