From d80df3241de625d8e71996698934f540421baf0f Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Mon, 24 Nov 2025 14:26:35 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`feature?=
=?UTF-8?q?/pulse-search-ui-mobile-refinements`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Docstrings generation was requested by @vignesha22.
* https://github.com/pillarwallet/x/pull/461#issuecomment-3562808673
The following files were modified:
* `src/apps/pulse/components/Buy/Buy.tsx`
* `src/apps/pulse/components/Buy/BuyButton.tsx`
* `src/apps/pulse/components/Price/TokenPrice.tsx`
* `src/apps/pulse/components/Search/MarketList.tsx`
* `src/apps/pulse/components/Search/Search.tsx`
* `src/apps/pulse/components/Search/SearchSkeleton.tsx`
* `src/apps/pulse/components/Search/TokenList.tsx`
* `src/apps/pulse/utils/parseSearchData.ts`
---
src/apps/pulse/components/Buy/Buy.tsx | 10 ++-
src/apps/pulse/components/Buy/BuyButton.tsx | 17 +++-
.../pulse/components/Price/TokenPrice.tsx | 9 +-
.../pulse/components/Search/MarketList.tsx | 15 +++-
src/apps/pulse/components/Search/Search.tsx | 20 ++++-
.../components/Search/SearchSkeleton.tsx | 11 ++-
.../pulse/components/Search/TokenList.tsx | 15 +++-
src/apps/pulse/utils/parseSearchData.ts | 85 +++++++++++++++++--
8 files changed, 167 insertions(+), 15 deletions(-)
diff --git a/src/apps/pulse/components/Buy/Buy.tsx b/src/apps/pulse/components/Buy/Buy.tsx
index a642ba32..3875329f 100644
--- a/src/apps/pulse/components/Buy/Buy.tsx
+++ b/src/apps/pulse/components/Buy/Buy.tsx
@@ -84,6 +84,14 @@ interface BuyProps {
usdcPrice?: number; // For Relay Buy: USDC price from portfolio (passed from HomeScreen)
}
+/**
+ * Render the Buy UI and manage the token purchase flow using either Relay Buy or the Intent SDK.
+ *
+ * This component handles user input for USD amounts, debounces and validates the amount, computes dispensable assets and permitted chains, fetches Relay buy offers or generates Express Intent responses, and surfaces preview and error states. It also integrates token selection (including token-atlas search), manages module installation for the Intent SDK, and exposes refresh and preview callbacks to parent components.
+ *
+ * @param props - Component props that provide token and portfolio data, balance information, callbacks for preview/selection/refresh, and configuration such as custom amounts and optional USDC price.
+ * @returns A React element rendering the buy interface and its associated controls and status.
+ */
export default function Buy(props: BuyProps) {
const {
setExpressIntentResponse: setExInResp,
@@ -842,4 +850,4 @@ export default function Buy(props: BuyProps) {
);
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/components/Buy/BuyButton.tsx b/src/apps/pulse/components/Buy/BuyButton.tsx
index 0fa49020..eb8e6def 100644
--- a/src/apps/pulse/components/Buy/BuyButton.tsx
+++ b/src/apps/pulse/components/Buy/BuyButton.tsx
@@ -125,6 +125,21 @@ export interface BuyButtonProps {
useRelayBuy: boolean;
}
+/**
+ * Render the Buy button with state-aware label, styling, and enable/disable logic.
+ *
+ * The button text adapts to loading/installing/fetching states, missing modules (prompts to enable trading),
+ * selected token and USD amount (shows estimated token and USD values), and express intent/offer responses.
+ *
+ * @param props - Component props that control rendering and behavior. Key behaviors:
+ * - isDisabled when installing, fetching, notEnoughLiquidity, loading, no token selected, USD amount ≤ 0,
+ * or when the express intent/offer response is missing, contains an `error`, or contains an empty `bids` array.
+ * - Exception: when not using Relay Buy and modules are not installed but a paying token exists, the button
+ * is enabled to allow enabling trading.
+ * - onClick is forwarded to `handleBuySubmit`.
+ *
+ * @returns The rendered button element for initiating a buy or enabling trading.
+ */
export default function BuyButton(props: BuyButtonProps) {
const {
areModulesInstalled,
@@ -190,4 +205,4 @@ export default function BuyButton(props: BuyButtonProps) {
)}
);
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/components/Price/TokenPrice.tsx b/src/apps/pulse/components/Price/TokenPrice.tsx
index d62b7c5f..b72081a4 100644
--- a/src/apps/pulse/components/Price/TokenPrice.tsx
+++ b/src/apps/pulse/components/Price/TokenPrice.tsx
@@ -2,6 +2,13 @@ export interface TokenPriceProps {
value: number;
}
+/**
+ * Renders a token price in USD using different formats depending on its magnitude and where the first non-zero decimal appears.
+ *
+ * @param props - Component props.
+ * @param props.value - The token price in USD.
+ * @returns A JSX element displaying the formatted price: "$0.00" when all decimals are zero, "$X.XX" for values >= 0.01 or when the first non-zero decimal is within the first two places, and a compact "$0.0Ndddd" form for very small values where `N` is the number of leading zeros before the first significant digits and `dddd` are up to four significant decimal digits.
+ */
export default function TokenPrice(props: TokenPriceProps): JSX.Element {
const { value } = props;
const fixed = value.toFixed(10);
@@ -47,4 +54,4 @@ export default function TokenPrice(props: TokenPriceProps): JSX.Element {
{significantDigits}
);
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/components/Search/MarketList.tsx b/src/apps/pulse/components/Search/MarketList.tsx
index 133733cb..4534a0f3 100644
--- a/src/apps/pulse/components/Search/MarketList.tsx
+++ b/src/apps/pulse/components/Search/MarketList.tsx
@@ -14,6 +14,19 @@ export interface MarketListProps {
minLiquidity?: number;
}
+/**
+ * Render a vertical list of market rows as interactive buttons.
+ *
+ * Each row shows token0 artwork (or initials fallback), an overlaid chain logo,
+ * pair name, exchange, liquidity and 24h volume, USD liquidity on the right,
+ * and an optional 24h price change badge. Clicking a row invokes the provided
+ * selection handler with the corresponding market.
+ *
+ * @param props - Component props containing markets and selection handler.
+ * - `markets`: array of market objects to render; when missing or empty nothing is rendered.
+ * - `handleMarketSelect`: callback invoked with the selected `Market` when a row is clicked.
+ * @returns The rendered list of market rows, or `null` when `markets` is falsy or empty.
+ */
export default function MarketList(props: MarketListProps) {
const { markets, handleMarketSelect } = props;
@@ -157,4 +170,4 @@ export default function MarketList(props: MarketListProps) {
})}
>
);
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/components/Search/Search.tsx b/src/apps/pulse/components/Search/Search.tsx
index 7a6b82c9..7f166331 100644
--- a/src/apps/pulse/components/Search/Search.tsx
+++ b/src/apps/pulse/components/Search/Search.tsx
@@ -67,6 +67,24 @@ const overlayStyling = {
position: 'fixed' as const,
};
+/**
+ * Render a modal search interface for discovering tokens, markets, and wallet holdings with support for buy/sell modes, chain selection, sorting, and refresh.
+ *
+ * The component manages search state, fetches trending/fresh/top-gainers data when applicable, renders parsed token and market lists, applies liquidity filtering for markets, and handles token/market selection (including multi-chain token resolution using wallet USDC balances). It also supports keyboard and outside-click dismissal and preserves a relayBuy query parameter when closing.
+ *
+ * @param setSearching - Callback to toggle the parent search open/closed state
+ * @param isBuy - If true, the component operates in buy mode (shows Trending/Fresh/Top Gainers filters and chain selector); otherwise it operates in sell/holdings mode
+ * @param setBuyToken - Setter invoked with the selected buy token payload
+ * @param setSellToken - Setter invoked with the selected sell token payload
+ * @param chains - Array of selected chain identifiers used to scope search requests
+ * @param setChains - Setter to update the selected chains
+ * @param walletPortfolioData - Optional wallet portfolio data used for "My Holdings" and multi-chain heuristics
+ * @param walletPortfolioLoading - Boolean indicating initial wallet portfolio loading state
+ * @param walletPortfolioFetching - Boolean indicating ongoing wallet portfolio refetching
+ * @param walletPortfolioError - Optional error object for wallet portfolio fetch failures
+ * @param refetchWalletPortfolio - Optional function to trigger a wallet portfolio refetch
+ * @returns The rendered Search component JSX element
+ */
export default function Search({
setSearching,
isBuy,
@@ -795,4 +813,4 @@ export default function Search({
}
);
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/components/Search/SearchSkeleton.tsx b/src/apps/pulse/components/Search/SearchSkeleton.tsx
index 1fc9b253..ee42fb9c 100644
--- a/src/apps/pulse/components/Search/SearchSkeleton.tsx
+++ b/src/apps/pulse/components/Search/SearchSkeleton.tsx
@@ -4,6 +4,15 @@ interface SearchSkeletonProps {
showSections?: boolean;
}
+/**
+ * Renders a pulsing loading skeleton for a search/results list.
+ *
+ * Shows optional section header placeholders and a fixed number of skeleton rows
+ * consisting of an avatar, two text lines, and two right-aligned value bars.
+ *
+ * @param showSections - When `true`, render the two header placeholder lines; when `false`, omit them.
+ * @returns A React element containing the loading skeleton UI.
+ */
export default function SearchSkeleton({ showSections = true }: SearchSkeletonProps) {
return (
@@ -42,4 +51,4 @@ export default function SearchSkeleton({ showSections = true }: SearchSkeletonPr
))}
);
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/components/Search/TokenList.tsx b/src/apps/pulse/components/Search/TokenList.tsx
index 0c6675cc..4a95b046 100644
--- a/src/apps/pulse/components/Search/TokenList.tsx
+++ b/src/apps/pulse/components/Search/TokenList.tsx
@@ -17,6 +17,19 @@ export interface TokenListProps {
hideHeaders?: boolean;
}
+/**
+ * Render a sortable, clickable list of tokens with visuals, metrics, and price information.
+ *
+ * Renders optional sort headers (when `hideHeaders` is false and `searchType` is Trending or Fresh),
+ * a list of token rows that call `handleTokenSelect` when clicked, and an empty fragment when `assets` is falsy.
+ *
+ * @param props - Component props
+ * @param props.assets - Array of token assets to display; if falsy, the component renders nothing
+ * @param props.handleTokenSelect - Callback invoked with the selected asset when a row is clicked
+ * @param props.searchType - Controls header visibility and whether fresh timestamps are shown
+ * @param props.hideHeaders - When true, suppresses the sort header row even for Trending/Fresh search types
+ * @returns A JSX element containing the token list or an empty fragment when no assets are provided
+ */
export default function TokenList(props: TokenListProps) {
const { assets, handleTokenSelect, searchType, hideHeaders } = props;
@@ -225,4 +238,4 @@ export default function TokenList(props: TokenListProps) {
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>>;
-}
+}
\ No newline at end of file
diff --git a/src/apps/pulse/utils/parseSearchData.ts b/src/apps/pulse/utils/parseSearchData.ts
index dbbcfdee..a2b173ba 100644
--- a/src/apps/pulse/utils/parseSearchData.ts
+++ b/src/apps/pulse/utils/parseSearchData.ts
@@ -52,6 +52,15 @@ export type Market = {
price: number | null;
};
+/**
+ * Build a single Asset entry for a token using its first supported chain as the primary chain and attach multi-chain metadata.
+ *
+ * Filters out unsupported chains and wrapped native token deployments; when at least one valid chain remains, returns an array containing one Asset populated from the primary chain and including `allChains`, `allContracts`, and `allDecimals` for every valid chain. If no valid chains remain, returns an empty array.
+ *
+ * @param asset - The token response object to convert into an Asset
+ * @param chains - Which chain(s) to consider (specific chain name or `MobulaChainNames.All`)
+ * @returns An array containing one Asset with primary-chain fields and multi-chain metadata, or an empty array if no valid chains are found
+ */
export function parseAssetData(
asset: TokenAssetResponse,
chains: MobulaChainNames
@@ -107,6 +116,12 @@ export function parseAssetData(
return result;
}
+/**
+ * Convert a TokenAssetResponse into a single Asset entry that consolidates multi-chain information when supported.
+ *
+ * @param asset - TokenAssetResponse from the API representing a token across chains; wrapped native-token contract entries are ignored.
+ * @returns An array containing one Asset built from the first valid chain as the primary chain and populated `allChains`, `allContracts`, and `allDecimals`; returns an empty array if no valid chains are available.
+ */
export function parseTokenData(asset: TokenAssetResponse): Asset[] {
const result: Asset[] = [];
const { blockchains, decimals, contracts } = asset;
@@ -156,7 +171,12 @@ export function parseTokenData(asset: TokenAssetResponse): Asset[] {
}
/**
- * Parse market pairs from TokenAssetResponse, ensuring the searched token appears first
+ * Extracts market pairs from a token response where either token matches the search term, ordering each pair so the matched token is first.
+ *
+ * @param asset - Token asset response containing pair data
+ * @param searchTerm - Term used to match token symbol or name (case-insensitive)
+ * @param chains - Chain filter; use MobulaChainNames.All to include all chains
+ * @returns An array of Market entries matching the search term with the matched token positioned as `token0`; empty if none
*/
export function parseMarketPairs(
asset: TokenAssetResponse,
@@ -225,7 +245,16 @@ export function parseMarketPairs(
}
/**
- * Parse market pairs from PairResponse
+ * Construct the pair representation including tokens, liquidity, volume, and pricing information.
+ *
+ * @returns An object representing the market pair with these fields:
+ * - `pairName`: string in the form `"token0.symbol/token1.symbol"`
+ * - `token0`, `token1`: token objects from the pair
+ * - `liquidity`: pair liquidity
+ * - `volume24h`: 24-hour volume (prefers `pair.volume_24h` if present)
+ * - `blockchain`, `address`, `exchange`: source identifiers
+ * - `priceChange24h`: 24-hour price change if provided, `null` otherwise
+ * - `price`: current pair price
*/
export function parsePairResponse(pair: PairResponse): Market {
return {
@@ -243,7 +272,13 @@ export function parsePairResponse(pair: PairResponse): Market {
}
/**
- * Sort assets by relevance to search term, then by market cap
+ * Order assets by relevance to the provided search term, then by market capitalization.
+ *
+ * The search term is compared to asset symbols case-insensitively after trimming; assets whose symbol exactly matches the search term are placed before others. Assets with equal relevance are ordered by `mCap` descending (treating missing `mCap` as zero).
+ *
+ * @param assets - Array of assets to sort
+ * @param searchTerm - Term used to prioritize exact symbol matches
+ * @returns The same assets array sorted with exact symbol matches first, then by market cap (highest first)
*/
export function sortAssets(assets: Asset[], searchTerm: string): Asset[] {
const normalizedSearchTerm = searchTerm.toLowerCase().trim();
@@ -264,7 +299,11 @@ export function sortAssets(assets: Asset[], searchTerm: string): Asset[] {
}
/**
- * Sort markets by relevance to search term, then by liquidity
+ * Order markets by relevance to the search term, placing markets whose token0 symbol exactly matches the term first, then by descending liquidity.
+ *
+ * @param markets - Array of market entries to sort
+ * @param searchTerm - Search string used to determine relevance (matched against token0 symbol, case-insensitive)
+ * @returns Markets sorted so exact `token0` symbol matches to `searchTerm` come first, ties broken by higher `liquidity`
*/
export function sortMarkets(markets: Market[], searchTerm: string): Market[] {
const normalizedSearchTerm = searchTerm.toLowerCase().trim();
@@ -286,7 +325,11 @@ export function sortMarkets(markets: Market[], searchTerm: string): Market[] {
}
/**
- * Filter markets by minimum liquidity threshold
+ * Selects markets with liquidity greater than or equal to a minimum threshold.
+ *
+ * @param markets - Array of market entries to filter
+ * @param minLiquidity - Minimum liquidity threshold; markets with liquidity >= `minLiquidity` are kept
+ * @returns The filtered array of markets whose `liquidity` is greater than or equal to `minLiquidity`
*/
export function filterMarketsByLiquidity(
markets: Market[],
@@ -295,6 +338,13 @@ export function filterMarketsByLiquidity(
return markets.filter((market) => (market.liquidity || 0) >= minLiquidity);
}
+/**
+ * Parse mixed API search results into deduplicated, filtered, and optionally sorted asset and market lists.
+ *
+ * @param searchData - Array of API responses which may be token/asset entries or pair records.
+ * @param chains - Chain filter controlling which chains to consider (e.g., all chains or a specific Mobula chain).
+ * @param searchTerm - Optional search term used to prioritize and sort results; also enables debug logging for certain terms.
+ * @returns An object containing `assets` (deduplicated and filtered Asset[] with merged multi-chain metadata) and `markets` (deduplicated Market[]), optionally sorted by relevance to `searchTerm`.
export function parseSearchData(
searchData: TokenAssetResponse[] | PairResponse[],
chains: MobulaChainNames,
@@ -389,8 +439,12 @@ export function parseSearchData(
}
/**
- * Deduplicate assets by Mobula ID and symbol
- * This handles cases where the API returns both 'asset' and 'token' types for the same asset
+ * Produce a deduplicated list of assets, preferring entries that include a Mobula `id`.
+ *
+ * Removes duplicate token entries by symbol when an asset with an `id` exists, and merges multi-chain metadata for entries that represent the same asset across chains.
+ *
+ * @param assets - Array of Asset entries (may include both asset-type entries with `id` and token-type entries without `id`)
+ * @returns An array of unique Asset objects where duplicates are removed and multi-chain fields (`allChains`, `allContracts`, `allDecimals`) are merged into the retained entry
*/
function deduplicateAssetsBySymbol(assets: Asset[]): Asset[] {
const assetMap = new Map();
@@ -438,6 +492,13 @@ function deduplicateAssetsBySymbol(assets: Asset[]): Asset[] {
return Array.from(assetMap.values());
}
+/**
+ * Builds Asset entries from projection data representing fresh and trending tokens across chains.
+ *
+ * Parses each projection's market rows to produce assets keyed by symbol (or name if symbol is empty), merging entries that appear on multiple chains into a single Asset with aggregated `volume` and `mCap`, and populated `allChains`, `allContracts`, and `allDecimals`.
+ *
+ * @param projections - Array of projection objects containing tokens market data across chains.
+ * @returns An array of Assets with non-zero volume and market cap, where multi-chain occurrences are merged and per-asset fields (price, priceChange24h, liquidity, timestamp, decimals, logo) are populated.
export function parseFreshAndTrendingTokens(
projections: Projection[]
): Asset[] {
@@ -529,6 +590,14 @@ export function parseFreshAndTrendingTokens(
return filteredAssets;
}
+/**
+ * Merge multi-chain arrays (`allChains`, `allContracts`, `allDecimals`) from `source` into `target`, mutating `target`.
+ *
+ * If `source` lacks the multi-chain arrays the function does nothing. If `target` lacks those arrays they are copied from `source`. Otherwise, each chain present in `source` that is not already in `target.allChains` is appended along with its corresponding contract and decimals.
+ *
+ * @param target - Asset to receive merged multi-chain data (mutated).
+ * @param source - Asset providing multi-chain data to merge.
+ */
function mergeMultiChainData(target: Asset, source: Asset) {
if (!source.allChains || !source.allContracts || !source.allDecimals) return;
@@ -551,4 +620,4 @@ function mergeMultiChainData(target: Asset, source: Asset) {
target.allDecimals.push(source.allDecimals[i]);
}
}
-}
+}
\ No newline at end of file