feat(discover-v2): add featured perps markets carousel#7418
feat(discover-v2): add featured perps markets carousel#7418DanielSinclair wants to merge 4 commits intodaniel/discover-v2-market-carouselsfrom
Conversation
91c9a66 to
b5f5ba8
Compare
c39c9bd to
6befca4
Compare
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 637a37a1cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const showPredictionsPlacement = availability.predictions && isLoading; | ||
| const showPlacements = showPerpsPlacement || showPredictionsPlacement; | ||
| const perpsPlacement = placements.find(placement => placement.id === PLACEMENT_IDS.PERPS); | ||
| const showPerpsPlacement = availability.perps && (isLoading || Boolean(perpsPlacement?.items.length)); |
There was a problem hiding this comment.
Keep perps carousel mounted until market hydration completes
showPerpsPlacement only checks the placements store loading flag, but useDiscoverPlacements filters out all items until useHyperliquidMarketsStore has loaded matching markets. In the common case where placements finish first, isLoading becomes false while perpsPlacement.items is still empty, so the carousel disappears entirely instead of staying in a loading state. This causes visible flicker/blanking on slower market fetches and can make the featured perps section appear missing until a later re-render.
Useful? React with 👍 / 👎.
|
🧪 Flashlight Performance Report (AWS Device Farm) 🔀 Commit: bda05d4
|
Derives filtered Perps placement items from the raw placements store, excluding hyperliquid markets not yet loaded.
Perps market card with token icon, leverage badge, symbol, and percent-change arrow. Sized without a sparkline slot — sparklines land in the sparklines branch.
Deep-link from Discover Perps card to market detail (with explain sheet gate), and See All to search screen without going through account home. PerpsNavigator reads initialPerpsPage from route params; PerpsSearchScreen skips delayed mount when markets are already cached.
Connects PerpMarketCard as the renderItem for the Perps placement carousel, using the composition store for data and per-item width sizing.
b5f5ba8 to
6bbfc95
Compare
637a37a to
7e10dd7
Compare
ibrahimtaveras00
left a comment
There was a problem hiding this comment.
Looks good on both OS's ✅
| const renderPerpCard = (item: PlacementItem, { trackPress }: { trackPress: PerpMarketCardProps['onPressTracked'] }) => ( | ||
| <PerpMarketCard item={item} onPressTracked={trackPress} /> | ||
| ); | ||
|
|
||
| const getPerpCardWidth = (item: PlacementItem) => { | ||
| const symbol = useHyperliquidMarketsStore.getState().getMarket(item.ref.id)?.baseSymbol ?? item.ref.id; | ||
| return computePerpCardWidth({ symbol }); | ||
| }; | ||
|
|
There was a problem hiding this comment.
Mentioned in the previous PR in the stack moving the specific carousels to their own components, and this logic being needed I think is another good reason for that.
There was a problem hiding this comment.
Could we just extend formatPriceChange in src/features/perps/utils.ts to work for both of these cases? Is the isFinite check needed?
| market, | ||
| }); | ||
|
|
||
| const hasSeenExplainSheet = device.get(['hasSeenPerpsExplainSheet']); |
There was a problem hiding this comment.
nit: since this is used in multiple files now, should go in a constant like HAS_SEEN_PERPS_EXPLAIN_KEY
Could also be cleaner to add a helper maybeNavigateToPerpsExplainSheet(navigationAction: () => void) so that this pattern is centralized instead of duplicated here and in navigateToPerps
Also I know this was already here, but probably makes more sense for all the perps nav utils to be in the navigateToPerps file.
| .map(id => state.getPlacement(id)) | ||
| .filter((placement): placement is Placement => placement !== undefined) | ||
| ); | ||
| const isLoading = $(usePlacementsStore, state => state.getStatus('isInitialLoad')) as boolean; |
| export function computePerpCardWidth({ symbol }: { symbol?: string }): number { | ||
| const estimatedTextWidth = symbol | ||
| ? symbol.length * ESTIMATED_SYMBOL_CHARACTER_WIDTH + SYMBOL_TEXT_BUFFER | ||
| : PERP_MARKET_NO_CHART_TEXT_MIN_WIDTH; | ||
| const textWidth = Math.max(PERP_MARKET_NO_CHART_TEXT_MIN_WIDTH, estimatedTextWidth); | ||
|
|
||
| return Math.min(PERP_MARKET_CARD_MAX_WIDTH, Math.ceil(PERP_MARKET_NO_CHART_FIXED_WIDTH + textWidth)); | ||
| } |
There was a problem hiding this comment.
This was only added a couple days ago, but we do now have a measureTextSync that can get you the exact text width given some style. Using that here would make this cleaner.
| </View> | ||
|
|
||
| <View style={styles.badgePositioner}> | ||
| <View |
There was a problem hiding this comment.
Same thing here with the borders
|
|
||
| const accentColor = market.metadata?.colors?.color || market.metadata?.colors?.fallbackColor || '#3ECFAD'; | ||
| const percentChange = | ||
| candlestickPercentChange ?? convertStoredPerpPriceChangeToPercent(market.priceChange['1h'] || market.priceChange['24h']); |
There was a problem hiding this comment.
0 is valid here I assume, should be ??
|
|
||
| if (!market) return null; | ||
|
|
||
| const accentColor = market.metadata?.colors?.color || market.metadata?.colors?.fallbackColor || '#3ECFAD'; |
There was a problem hiding this comment.
use HYPERLIQUID_COLORS.green as the fallback here
| ); | ||
|
|
||
| const getPerpCardWidth = (item: PlacementItem) => { | ||
| const symbol = useHyperliquidMarketsStore.getState().getMarket(item.ref.id)?.baseSymbol ?? item.ref.id; |
There was a problem hiding this comment.
should use hyperliquidMarketsActions.getMarket(). A couple other places where you should use hyperliquidMarketsActions over useHyperliquidMarketsStore.getState().action()
| flexDirection: 'row', | ||
| gap: 2, | ||
| minHeight: 20, | ||
| minWidth: 0, |
There was a problem hiding this comment.
you do not need any of the minWidth: 0 in this file. I assume this is from css conventions, but in react native it's not required.

APP-3530, APP-3670
What changed
useDiscoverPlacements— composition store subscribing tousePlacementsStoreanduseHyperliquidMarketsStore; produces a filteredPlacementItem[]where only items with a loadedHyperliquidMarketare included, preventing broken cards before market data arrivesPerpMarketCard— displaysHyperliquidMarket.baseSymbol, leverage badge fromHyperliquidMarket.maxLeverage, and H1percentChange(number) fromuseCandlestickStore; card width is computed dynamically from symbol character length viacomputePerpCardWidthPerpsNavigatornow readsinitialPerpsPagefrom route params viauseStableValueto land directly on any page;navigateToPerpsSearchcallsPerpsNavigation.navigatefirst (handles the already-open case) then triggers the app-level navigate;SheetFooterremoves anInteractionManagerdelay that was blocking immediate Open Position responseScreen recordings / screenshots
What to test