Skip to content

Fixes after QA review on feat/PRO-3638/sell-feature-pulse#395

Merged
RanaBug merged 1 commit intostagingfrom
feat/PRO-3638/sell-feature-pulse
Sep 11, 2025
Merged

Fixes after QA review on feat/PRO-3638/sell-feature-pulse#395
RanaBug merged 1 commit intostagingfrom
feat/PRO-3638/sell-feature-pulse

Conversation

@RanaBug
Copy link
Collaborator

@RanaBug RanaBug commented Sep 11, 2025

Description

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Summary by CodeRabbit

  • New Features
    • Search modal now closes with Esc and by clicking outside.
    • Long token symbols are truncated with a hover tooltip for full view.
  • UI Improvements
    • Search header adapts by mode: chain selector on Buy, quick-close on Sell.
    • Sell button shows a detailed action summary (“Sell X TOKEN for Y USDC”).
    • Replaced USD badges with a USDC logo in Sell preview.
    • Clearer loading/disabled states during refreshes.
    • Updated copy to simplify fee wording.
  • Refactor
    • Streamlined refresh/loading logic for smoother, more reliable sell offer updates.

@RanaBug RanaBug requested a review from IAmKio September 11, 2025 10:33
@RanaBug RanaBug self-assigned this Sep 11, 2025
@cloudflare-workers-and-pages
Copy link

Deploying x with  Cloudflare Pages  Cloudflare Pages

Latest commit: 24f93a6
Status: ✅  Deploy successful!
Preview URL: https://156998f2.x-e62.pages.dev
Branch Preview URL: https://feat-pro-3638-sell-feature-p.x-e62.pages.dev

View logs

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Walkthrough

This PR removes global Loading/Refresh contexts, refactors sell-flow components to manage refresh locally via useRelaySell, simplifies AppWrapper routing, enhances Search with ESC/click-outside close, standardizes Esc’s onClose prop, updates SellButton text rendering, and adjusts tests to new providers and APIs.

Changes

Cohort / File(s) Summary
Context removal
src/apps/pulse/contexts/LoadingContext.tsx, src/apps/pulse/contexts/RefreshContext.tsx
Deleted loading/refresh contexts, providers, and hooks. Consumers updated to stop importing/using them.
App flow simplification
src/apps/pulse/components/App/AppWrapper.tsx
Removed Loading/Refresh providers; simplified render to conditional Search vs HomeScreen.
Home screen refresh rework
src/apps/pulse/components/App/HomeScreen.tsx
Replaced context-driven refresh with local isRefreshingHome; uses useRelaySell’s isInitialized/getBestSellOffer; wires onSellOfferUpdate to child.
Sell flow refactor
src/apps/pulse/components/Sell/PreviewSell.tsx, src/apps/pulse/components/Sell/Sell.tsx, src/apps/pulse/components/Sell/SellButton.tsx, src/apps/pulse/hooks/useRelaySell.ts
PreviewSell: replaces onRefresh with onSellOfferUpdate; internal preview refresh with getBestSellOffer and isRefreshingPreview; updates Esc usage and USDC visuals. Sell: adds isRefreshing prop; removes RefreshContext usage; token symbol tooltip/truncation. SellButton: richer inline CTA showing amounts; updated getButtonText signature. useRelaySell: removed LoadingContext; minor copy changes.
Esc/Search/Buy/Status updates
src/apps/pulse/components/Misc/Esc.tsx, src/apps/pulse/components/Search/Search.tsx, src/apps/pulse/components/Buy/PreviewBuy.tsx, src/apps/pulse/components/Status/IntentTracker.tsx
Esc: prop renamed to onClose; adds Escape key handler and test id. Search: ESC and click-outside to close via handleClose; conditional chain button vs Esc; uses limitDigitsNumber. Buy/PreviewBuy and Status/IntentTracker: updated to Esc onClose.
App and Sell tests
src/apps/pulse/components/App/tests/AppWrapper.test.tsx, src/apps/pulse/components/App/tests/HomeScreen.test.tsx, src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx, src/apps/pulse/components/Sell/tests/Sell.test.tsx, src/apps/pulse/components/Sell/tests/SellButton.test.tsx
Removed Loading/Refresh providers/mocks; introduced wrappers with GlobalTransactionsBatchProvider and BottomMenuModalProvider where needed; updated assertions (e.g., SellButton text fragments; token symbol visibility).
Buy and hooks tests
src/apps/pulse/components/Buy/tests/Buy.test.tsx, src/apps/pulse/hooks/tests/useRelaySell.test.tsx
Dropped Loading/Refresh providers and useLoading mocks; simplified render helpers and snapshots.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant HomeScreen
  participant useRelaySell
  participant PreviewSell
  participant Sell

  User->>HomeScreen: Pull to refresh / refresh action
  HomeScreen->>HomeScreen: set isRefreshingHome = true
  HomeScreen->>useRelaySell: walletPortfolio.refetch()
  alt sellToken && tokenAmount && isInitialized
    HomeScreen->>useRelaySell: getBestSellOffer(sellToken, amount)
    useRelaySell-->>HomeScreen: offer | error
    HomeScreen->>PreviewSell: onSellOfferUpdate(offer|null)
  end
  HomeScreen->>Sell: isRefreshing = isRefreshingHome
  HomeScreen->>HomeScreen: set isRefreshingHome = false
Loading
sequenceDiagram
  autonumber
  actor User
  participant Search
  participant Esc

  User->>Search: Open modal
  Search->>Search: attach click-outside + ESC listeners
  alt Click outside modal
    User->>Search: click document
    Search->>Search: handleClose() reset state + close
  else Press ESC
    User->>Esc: keydown Escape
    Esc-->>Search: onClose()
    Search->>Search: handleClose() reset state + close
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • IAmKio
  • vignesha22

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is the repository template filled only with placeholders ("-") and lacks a substantive summary, testing details, screenshots, or an explicit statement of impact, so reviewers cannot assess scope or verify changes from the PR body. The raw changeset contains many functional and API-level edits (context removals, hook API extensions, prop renames, UI updates) that warrant clear documentation and testing notes, and potentially include breaking changes that must be called out. Because required sections are unfilled and testing information is missing, the description does not satisfy the template requirements. Please replace the placeholders with a concise summary of the primary changes and rationale (mention removal of Loading/Refresh contexts, useRelaySell additions like getBestSellOffer/isInitialized, PreviewSell.onSellOfferUpdate, Sell.isRefreshing prop, Esc.onClose rename, etc.), list key affected files or public API changes, provide detailed "How Has This Been Tested?" steps and environment (unit tests run, manual test steps, commands), attach screenshots for UI changes, and update the "Types of changes" checkboxes to correctly indicate bug/feature/breaking changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Fixes after QA review on feat/PRO-3638/sell-feature-pulse" correctly signals that these are QA fixes for the sell feature, but it is generic and includes the source branch name rather than summarizing the primary technical change or its impact, making it harder to scan in history. It does not call out the main edits (for example: removal of Loading/Refresh contexts, migration of refresh logic into useRelaySell, PreviewSell/Sell API changes, and Esc prop rename). Because it is related to the changeset but not descriptive, it meets the minimum relation criterion but should be made clearer.

Poem

hop-hop, I tidy the wires and threads,
contexts retire, new hooks lift their heads.
Esc taps twice—the modal takes flight,
Sell sings numbers, crisp and bright.
I thump with joy: cleaner flow, less fuss—
carrot-approve this merge for us! 🥕✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/PRO-3638/sell-feature-pulse

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Collaborator

@IAmKio IAmKio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@RanaBug RanaBug merged commit faa8dd2 into staging Sep 11, 2025
2 of 3 checks passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/apps/pulse/components/Sell/Sell.tsx (1)

92-106: Fix balance lookup: symbol-first search can miss the correct contract

If multiple assets share the same symbol, finding by symbol first can return the wrong asset and hide the actual balance. Search contracts across all assets by address+chain instead.

Apply:

-  // Find the asset in the portfolio
-  const assetData = walletPortfolioData.result.data.assets.find(
-    (asset) => asset.asset.symbol === token.symbol
-  );
-
-  if (!assetData) return 0;
-
-  // Find the contract balance for the specific token address and chain
-  const contractBalance = assetData.contracts_balances.find(
-    (contract) =>
-      contract.address.toLowerCase() === token.address.toLowerCase() &&
-      contract.chainId === `evm:${token.chainId}`
-  );
-  return contractBalance?.balance || 0;
+  // Find the contract balance for the specific token address and chain across all assets
+  const assets = walletPortfolioData.result.data.assets || [];
+  for (const asset of assets) {
+    const contractBalance = asset.contracts_balances?.find(
+      (contract) =>
+        contract.address?.toLowerCase() === token.address.toLowerCase() &&
+        contract.chainId === `evm:${token.chainId}`
+    );
+    if (contractBalance) return contractBalance.balance || 0;
+  }
+  return 0;
🧹 Nitpick comments (23)
src/apps/pulse/components/Buy/tests/Buy.test.tsx (1)

144-144: Snapshot without wrappers is fine; consider focusing on behavior over structure.

To reduce snapshot churn from minor markup changes, prefer targeted assertions for key UI states.

src/apps/pulse/components/Misc/Esc.tsx (2)

10-21: ESC key handler looks solid; minor a11y/compat tweak.

Consider accepting legacy "Esc" and declaring a keyboard shortcut for assistive tech.

Apply:

-    const handleKeyDown = (event: KeyboardEvent) => {
-      if (event.key === 'Escape') {
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (event.key === 'Escape' || event.key === 'Esc') {
         onClose();
       }
     };

23-30: Nice: clear, accessible button API.

aria-label and data-testid are in place; onClick is correctly wired to onClose.

You can also hint the shortcut to AT:

     <button
       className="flex items-center justify-center w-[36px] h-[34px] bg-[#1E1D24] rounded-[8px]"
       onClick={onClose}
       type="button"
       aria-label="Close"
+      aria-keyshortcuts="Escape"
       data-testid="esc-button"
src/apps/pulse/hooks/useRelaySell.ts (2)

724-731: Clarify step description for fee transfer.

Slightly clearer wording improves activity logs without changing behavior.

-          stepDescription: 'USDC Fee Transfer',
+          stepDescription: 'Transfer USDC fee (1%)',

858-864: Tighten transaction description copy.

Current phrasing is a bit long and mixes step detail inline. Consider a concise, consistent template.

-        const description = `Selling ${amount} ${token.symbol} for ${sellOffer.tokenAmountToReceive.toFixed(2)} USDC on ${stepDescription}`;
+        const description = `Sell ${amount} ${token.symbol} → ~${sellOffer.tokenAmountToReceive.toFixed(2)} USDC (${stepDescription})`;
src/apps/pulse/components/App/tests/HomeScreen.test.tsx (3)

74-79: Avoid duplicating wrapper setup in snapshot.

Reuse renderWithProviders or create a helper for renderer.create to keep tests DRY.


120-125: Simplify rerender: wrapper is preserved.

When using renderWithProviders, you can rerender with just the element.

-    rerender(
-      <GlobalTransactionsBatchProvider>
-        <BottomMenuModalProvider>
-          <HomeScreen {...mockProps} isBuy={false} />
-        </BottomMenuModalProvider>
-      </GlobalTransactionsBatchProvider>
-    );
+    rerender(<HomeScreen {...mockProps} isBuy={false} />);

236-241: Same here: use preserved wrapper on rerender.

-    rerender(
-      <GlobalTransactionsBatchProvider>
-        <BottomMenuModalProvider>
-          <HomeScreen {...mockProps} isBuy={false} />
-        </BottomMenuModalProvider>
-      </GlobalTransactionsBatchProvider>
-    );
+    rerender(<HomeScreen {...mockProps} isBuy={false} />);
src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (1)

113-113: Snapshot aligns with new rendering approach.

Looks good as a baseline; keep behavioral tests as primary signal.

src/apps/pulse/components/Sell/tests/Sell.test.tsx (1)

89-90: Expose mocks for assertions

Returning mockUseRelaySell is fine. Consider also returning its fns (e.g., getBestSellOffer) so individual tests can assert call args directly if needed.

src/apps/pulse/components/App/AppWrapper.tsx (1)

48-69: Minor: avoid extra effect re-runs from URLSearchParams

Unrelated to this hunk but nearby: useQuery() creates a fresh URLSearchParams each render; the effect at Line 40 will re-run more than needed. Consider deriving tokenAddress via useLocation().search + useMemo/useSearchParams to stabilize deps.

src/apps/pulse/components/App/tests/AppWrapper.test.tsx (3)

40-48: Centralized render helper is good; consider MemoryRouter in the wrapper

renderWithProviders is solid. To reduce repetition, you could accept initialEntries and include MemoryRouter inside the wrapper.


90-98: Use the same wrapper in the snapshot test

Snapshot currently reimplements the provider tree inline. Prefer renderWithProviders(<MemoryRouter>...) for consistency.


258-262: Simplify rerender path

You can call the returned rerender with the new element only; re-wrapping with providers isn’t necessary since the wrapper is already fixed by renderWithProviders.

Also applies to: 273-281

src/apps/pulse/components/Sell/SellButton.tsx (1)

100-110: Compute disabled once to avoid repeated work

Cache isDisabled() to avoid triple eval and pass it through to getButtonText.

Apply:

   const isDisabled = () => {
     return (
       isLoadingOffer ||
       !token ||
       !(parseFloat(tokenAmount) > 0) ||
       !sellOffer ||
       sellOffer.tokenAmountToReceive === 0 ||
       notEnoughLiquidity ||
       !isInitialized
     );
   };

   return (
     <button
       className="flex-1 items-center justify-center"
       onClick={handleSellClick}
-      disabled={isDisabled()}
+      disabled={disabled}
       style={{
-        backgroundColor: isDisabled() ? '#29292F' : '#8A77FF',
-        color: isDisabled() ? 'grey' : '#FFFFFF',
+        backgroundColor: disabled ? '#29292F' : '#8A77FF',
+        color: disabled ? 'grey' : '#FFFFFF',
         borderRadius: 8,
       }}
       type="button"
     >
       {getButtonText(
         isLoadingOffer,
         isInitialized,
         token,
         tokenAmount,
-        sellOffer,
-        isDisabled()
+        sellOffer,
+        disabled
       )}
     </button>
   );

Add above the return:

+  const disabled = isDisabled();

Also applies to: 112-131

src/apps/pulse/components/Search/Search.tsx (3)

127-137: Stabilize handleClose to avoid stale closure issues

Wrap handleClose in useCallback so click-outside/ESC listeners always use the latest isBuy and routing state.

Apply:

-  const handleClose = () => {
+  const handleClose = useCallback(() => {
     setSearchText('');
     // It resets search type to MyHoldings if on sell screen
     if (!isBuy) {
       setSearchType(SearchType.MyHoldings);
     } else {
       setSearchType(undefined);
     }
     setSearching(false);
     removeQueryParams();
-  };
+  }, [isBuy, setSearchText, setSearching, removeQueryParams]);

Add import (outside this hunk):

import React, { Dispatch, SetStateAction, useEffect, useRef, useState, useCallback } from 'react';

139-156: Click-outside effect should depend on handleClose

This removes the ESLint disable and ensures correct behavior if props change.

Apply:

-  useEffect(() => {
+  useEffect(() => {
     const handleClickOutside = (event: MouseEvent) => {
       if (
         searchModalRef.current &&
         !searchModalRef.current.contains(event.target as Node)
       ) {
         handleClose();
       }
     };

     document.addEventListener('mousedown', handleClickOutside);
     return () => {
       document.removeEventListener('mousedown', handleClickOutside);
     };
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [handleClose]);

157-170: ESC key effect should depend on handleClose

Same rationale as above; remove the disable and add the dependency.

Apply:

-  useEffect(() => {
+  useEffect(() => {
     const handleKeyDown = (event: KeyboardEvent) => {
       if (event.key === 'Escape') {
         handleClose();
       }
     };

     document.addEventListener('keydown', handleKeyDown);
     return () => {
       document.removeEventListener('keydown', handleKeyDown);
     };
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [handleClose]);
src/apps/pulse/components/Sell/Sell.tsx (4)

229-242: Improve number input UX and validation

Use inputMode/pattern to surface numeric keyboards and prevent invalid strings (e.g., “1..2”) that later crash parseUnits downstream.

 <input
   className="w-full no-spinner text-right bg-transparent outline-none pr-0"
   style={{
     fontSize: '36px',
     lineHeight: '1.2',
     fontWeight: '500',
   }}
   placeholder={inputPlaceholder}
   onChange={handleTokenAmountChange}
   value={tokenAmount}
-  type="text"
+  type="text"
+  inputMode="decimal"
+  pattern="^\d*\.?\d*$"
+  autoComplete="off"
   onFocus={() => setInputPlaceholder('')}
   data-testid="pulse-sell-amount-input"
 />

244-263: Tooltip: add a11y + mobile support

Mouse-only tooltip isn’t available on touch and lacks semantics. Add title/aria attributes and role=tooltip. Consider showing full symbol on focus.

-  <p
-    className="text-grey ml-0 flex-shrink-0 opacity-50 cursor-help text-4xl font-medium"
-    data-testid="pulse-sell-token-symbol"
-    onMouseEnter={() => setShowTooltip(true)}
-    onMouseLeave={() => setShowTooltip(false)}
-  >
+  <p
+    className="text-grey ml-0 flex-shrink-0 opacity-50 cursor-help text-4xl font-medium"
+    data-testid="pulse-sell-token-symbol"
+    onMouseEnter={() => setShowTooltip(true)}
+    onMouseLeave={() => setShowTooltip(false)}
+    onFocus={() => setShowTooltip(true)}
+    onBlur={() => setShowTooltip(false)}
+    title={token.symbol}
+    aria-describedby="sell-token-symbol-tooltip"
+    tabIndex={0}
+  >
     {token.symbol.length > 4
       ? `${token.symbol.slice(0, 3)}...`
       : token.symbol}
   </p>
-  {showTooltip && token.symbol.length > 4 && (
-    <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-black text-white text-xs rounded shadow-lg z-10 whitespace-nowrap">
+  {showTooltip && token.symbol.length > 4 && (
+    <div
+      id="sell-token-symbol-tooltip"
+      role="tooltip"
+      className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-black text-white text-xs rounded shadow-lg z-10 whitespace-nowrap"
+    >
       {token.symbol}
     </div>
   )}

307-313: Drop lodash.parseInt import; use Number.parseInt with radix

Avoid an extra dependency for this simple case and be explicit with radix.

-        {['10%', '25%', '50%', '75%', 'MAX'].map((item) => {
+        {['10%', '25%', '50%', '75%', 'MAX'].map((item) => {
           const isMax = item === 'MAX';
-          const percentage = isMax ? 100 : parseInt(item);
+          const percentage = isMax ? 100 : Number.parseInt(item, 10);

And at file top:

-import { parseInt } from 'lodash';

141-154: Confirm: should amount reset on token change (per prior behavior)?

Previous behavior reset the input when switching tokens to avoid stale liquidity states. Current code recalculates liquidity but keeps the amount. Confirm intended UX or re-introduce a reset on token change.

Possible re-introduction:

+  useEffect(() => {
+    setTokenAmount('');
+    setParentTokenAmount('');
+    setLocalSellOffer(null);
+    setNotEnoughLiquidity(false);
+  }, [token]);
src/apps/pulse/components/Sell/PreviewSell.tsx (1)

108-137: Preview refresh flow is sound; minor guard improvement

Flow and guards are good. Tiny nit: early-return when prerequisites fail avoids setting/clearing the spinner unnecessarily.

-  const refreshPreviewSellData = useCallback(async () => {
-    setIsRefreshingPreview(true);
-    try {
-      // Only fetch new sell offer - wallet portfolio is already fresh from HomeScreen
-      if (sellToken && tokenAmount && isInitialized && onSellOfferUpdate) {
+  const refreshPreviewSellData = useCallback(async () => {
+    if (!sellToken || !tokenAmount || !isInitialized || !onSellOfferUpdate) return;
+    setIsRefreshingPreview(true);
+    try {
       const newOffer = await getBestSellOffer({
         fromAmount: tokenAmount,
         fromTokenAddress: sellToken.address,
         fromChainId: sellToken.chainId,
         fromTokenDecimals: sellToken.decimals,
       });
       onSellOfferUpdate(newOffer);
     } catch (e) {
       console.error('Failed to refresh sell offer:', e);
-      if (onSellOfferUpdate) {
-        onSellOfferUpdate(null);
-      }
+      onSellOfferUpdate(null);
     } finally {
       setIsRefreshingPreview(false);
     }
   }, [
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ddf921 and 24f93a6.

⛔ Files ignored due to path filters (5)
  • src/apps/pulse/assets/usd-coin-usdc-logo.png is excluded by !**/*.png
  • src/apps/pulse/components/Buy/tests/__snapshots__/PreviewBuy.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pulse/components/Sell/tests/__snapshots__/PreviewSell.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pulse/components/Sell/tests/__snapshots__/Sell.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pulse/components/Sell/tests/__snapshots__/SellButton.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (19)
  • src/apps/pulse/components/App/AppWrapper.tsx (1 hunks)
  • src/apps/pulse/components/App/HomeScreen.tsx (5 hunks)
  • src/apps/pulse/components/App/tests/AppWrapper.test.tsx (12 hunks)
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx (4 hunks)
  • src/apps/pulse/components/Buy/PreviewBuy.tsx (1 hunks)
  • src/apps/pulse/components/Buy/tests/Buy.test.tsx (2 hunks)
  • src/apps/pulse/components/Misc/Esc.tsx (1 hunks)
  • src/apps/pulse/components/Search/Search.tsx (9 hunks)
  • src/apps/pulse/components/Sell/PreviewSell.tsx (8 hunks)
  • src/apps/pulse/components/Sell/Sell.tsx (4 hunks)
  • src/apps/pulse/components/Sell/SellButton.tsx (3 hunks)
  • src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (2 hunks)
  • src/apps/pulse/components/Sell/tests/Sell.test.tsx (2 hunks)
  • src/apps/pulse/components/Sell/tests/SellButton.test.tsx (1 hunks)
  • src/apps/pulse/components/Status/IntentTracker.tsx (2 hunks)
  • src/apps/pulse/contexts/LoadingContext.tsx (0 hunks)
  • src/apps/pulse/contexts/RefreshContext.tsx (0 hunks)
  • src/apps/pulse/hooks/tests/useRelaySell.test.tsx (0 hunks)
  • src/apps/pulse/hooks/useRelaySell.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • src/apps/pulse/hooks/tests/useRelaySell.test.tsx
  • src/apps/pulse/contexts/LoadingContext.tsx
  • src/apps/pulse/contexts/RefreshContext.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
PR: pillarwallet/x#374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx
  • src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx
  • src/apps/pulse/components/App/AppWrapper.tsx
  • src/apps/pulse/components/App/tests/AppWrapper.test.tsx
  • src/apps/pulse/hooks/useRelaySell.ts
📚 Learning: 2025-09-09T12:40:15.611Z
Learnt from: RanaBug
PR: pillarwallet/x#391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.611Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.

Applied to files:

  • src/apps/pulse/components/Sell/Sell.tsx
  • src/apps/pulse/components/Sell/SellButton.tsx
  • src/apps/pulse/components/Sell/PreviewSell.tsx
🧬 Code graph analysis (11)
src/apps/pulse/components/App/tests/HomeScreen.test.tsx (1)
src/apps/pulse/components/App/HomeScreen.tsx (1)
  • HomeScreen (32-297)
src/apps/pulse/components/Buy/tests/Buy.test.tsx (1)
src/apps/pulse/components/Buy/Buy.tsx (1)
  • Buy (35-442)
src/apps/pulse/components/App/AppWrapper.tsx (2)
src/apps/pulse/components/Search/Search.tsx (1)
  • Search (59-425)
src/apps/pulse/components/App/HomeScreen.tsx (1)
  • HomeScreen (32-297)
src/apps/pulse/components/Sell/Sell.tsx (1)
src/apps/pulse/hooks/useRelaySell.ts (1)
  • SellOffer (38-41)
src/apps/pulse/components/Status/IntentTracker.tsx (1)
src/apps/pulse/components/Misc/Esc.tsx (1)
  • Esc (7-36)
src/apps/pulse/components/App/tests/AppWrapper.test.tsx (1)
src/apps/pulse/components/App/AppWrapper.tsx (1)
  • AppWrapper (11-70)
src/apps/pulse/components/Buy/PreviewBuy.tsx (1)
src/apps/pulse/components/Misc/Esc.tsx (1)
  • Esc (7-36)
src/apps/pulse/components/Search/Search.tsx (3)
src/utils/number.tsx (1)
  • limitDigitsNumber (42-69)
src/apps/pulse/components/Search/ChainSelect.tsx (1)
  • ChainSelectButton (3-13)
src/apps/pulse/components/Misc/Esc.tsx (1)
  • Esc (7-36)
src/apps/pulse/components/Sell/SellButton.tsx (3)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/hooks/useRelaySell.ts (1)
  • SellOffer (38-41)
src/utils/number.tsx (1)
  • limitDigitsNumber (42-69)
src/apps/pulse/components/App/HomeScreen.tsx (1)
src/apps/pulse/hooks/useRelaySell.ts (2)
  • useRelaySell (51-918)
  • SellOffer (38-41)
src/apps/pulse/components/Sell/PreviewSell.tsx (2)
src/apps/pulse/hooks/useRelaySell.ts (2)
  • SellOffer (38-41)
  • useRelaySell (51-918)
src/apps/pulse/components/Misc/Esc.tsx (1)
  • Esc (7-36)
🔇 Additional comments (27)
src/apps/pulse/components/Buy/tests/Buy.test.tsx (1)

134-134: Good move: remove obsolete providers.

Rendering Buy directly matches the removal of Loading/Refresh contexts. Tests stay focused on component behavior.

src/apps/pulse/components/Status/IntentTracker.tsx (2)

5-9: Imports aligned with Esc API change.

Swapping to the new Esc and keeping TransactionStatus import intact makes sense.


138-138: Prop rename applied correctly.

matches the new EscProps.

src/apps/pulse/components/Buy/PreviewBuy.tsx (1)

121-121: Prop rename applied correctly.

is consistent with the updated Esc API.

src/apps/pulse/components/App/tests/HomeScreen.test.tsx (1)

40-45: Providers updated appropriately.

Using GlobalTransactionsBatchProvider and BottomMenuModalProvider reflects the new app wiring.

src/apps/pulse/components/Sell/tests/SellButton.test.tsx (1)

79-85: Good: assert fragments, not a monolithic string.

This reduces brittleness from formatting changes.

src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (1)

93-93: Direct render is appropriate.

Removing obsolete providers keeps the test lean.

src/apps/pulse/components/Sell/tests/Sell.test.tsx (2)

92-94: Provider-less render is appropriate

Rendering <Sell /> without legacy Loading/Refresh providers matches the refactor and keeps tests lean.


112-114: Updated assertion is correct

Checking that pulse-sell-token-symbol is absent when no token is selected aligns with the new UI.

src/apps/pulse/components/App/AppWrapper.tsx (1)

48-69: Simplified conditional render: good cleanup

Switching to a single ternary for Search vs HomeScreen removes unnecessary wrappers and matches the provider-light approach.

src/apps/pulse/components/App/tests/AppWrapper.test.tsx (3)

16-19: Providers imported at the right level

Using GlobalTransactionsBatchProvider and BottomMenuModalProvider here mirrors app runtime and fixes missing context issues in tests.


106-110: Broad replacement with renderWithProviders looks good

All these cases correctly switch to the shared helper, reducing boilerplate and drift across tests.

Also applies to: 125-130, 140-144, 153-157, 169-177, 197-205, 224-228, 243-247


36-38: Empty Redux store is fine; watch for future selector usage

Since reducer is {}, ensure components under test don’t rely on selectors. If they start doing so, switch to a minimal slice or a mock store with preloadedState.

Would you like a tiny reducer scaffold added for tests to prevent accidental regressions?

src/apps/pulse/components/Sell/SellButton.tsx (3)

10-15: Imports for number formatting and HighDecimalsFormatted are appropriate

These keep rendering logic focused and consistent with app-wide formatting.


21-23: Helper signature extension is fine

Adding sellOffer and isDisabled keeps the button text logic encapsulated.


37-65: Clear, richer CTA text when actionable

The inline UI (“Sell X TOKEN for Y USDC”) with digit limiting is a UX win and avoids string parsing in tests.

src/apps/pulse/components/Search/Search.tsx (2)

18-18: Modal close UX improvements look good

  • Using limitDigitsNumber for price-derived values avoids noisy decimals.
  • searchModalRef + click-outside and the Esc button integrate well with the new close behavior.

Also applies to: 29-30, 87-87, 263-263, 298-319


206-211: Price formatting change is appropriate

Switching to limitDigitsNumber(...).toString() for usdValue yields consistent, readable numbers across buy/sell selection.

Also applies to: 215-223, 226-234, 236-246

src/apps/pulse/components/Sell/Sell.tsx (1)

34-36: Prop plumbing for refresh looks good

Passing external refresh via isRefreshing and folding it into isLoadingOffer is correct and keeps the button state consistent during global refresh.

Also applies to: 45-46, 362-364

src/apps/pulse/components/App/HomeScreen.tsx (3)

42-43: Good: consume useRelaySell directly for refresh logic

Importing getBestSellOffer and isInitialized here simplifies the refresh flow and removes context coupling.


239-245: Refresh button state wiring looks consistent

Using isRefreshingHome for isLoading/disabled is coherent and prevents concurrent refresh.


273-281: Propagating isRefreshing to Sell is correct

Keeps offer-loading UX consistent between manual quote fetch and global refresh.

src/apps/pulse/components/Sell/PreviewSell.tsx (5)

34-45: API swap to onSellOfferUpdate is clear

The new prop better communicates intent; wiring from HomeScreen via setSellOffer looks correct.


49-57: Localize refresh state; remove global contexts

Introducing isRefreshingPreview and using useRelaySell directly is the right decoupling.


231-234: Refresh button UX is correct

Spinner and disabled states align with isRefreshingPreview.


311-317: USDC logo addition is fine

Static logo usage with chain badge looks correct.


351-365: Receiving amount loader toggle is correct

Clean conditional UI; no further action.

Comment on lines 52 to 92
const handleRefresh = useCallback(async () => {
await Promise.all([refetchWalletPortfolio(), refreshSell()]);
}, [refetchWalletPortfolio, refreshSell]);
setIsRefreshingHome(true);

try {
const refreshPromises: Promise<void>[] = [
Promise.resolve(refetchWalletPortfolio()),
];

// If we have the required data, refresh the sell offer (regardless of whether one exists)
if (sellToken && tokenAmount && isInitialized) {
const sellOfferPromise = (async () => {
try {
const newOffer = await getBestSellOffer({
fromAmount: tokenAmount,
fromTokenAddress: sellToken.address,
fromChainId: sellToken.chainId,
fromTokenDecimals: sellToken.decimals,
});
setSellOffer(newOffer);
} catch (error) {
console.error('Failed to refresh sell offer:', error);
setSellOffer(null);
}
})();
refreshPromises.push(sellOfferPromise);
}

await Promise.all(refreshPromises);
} catch (error) {
console.error('Refresh failed:', error);
} finally {
setIsRefreshingHome(false);
}
}, [
refetchWalletPortfolio,
sellToken,
tokenAmount,
isInitialized,
getBestSellOffer,
]);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make wallet refetch awaitable; current Promise.resolve may not wait for network

If refetchWalletPortfolio returns void, Promise.resolve won’t track the underlying refetch; isRefreshingHome may flip false too early. Prefer a Promise-returning refetch and await it.

-interface HomeScreenProps {
+interface HomeScreenProps {
   ...
-  refetchWalletPortfolio: () => void;
+  refetchWalletPortfolio: () => Promise<void>;
 }
 
 const handleRefresh = useCallback(async () => {
   setIsRefreshingHome(true);
   try {
-    const refreshPromises: Promise<void>[] = [
-      Promise.resolve(refetchWalletPortfolio()),
-    ];
+    const refreshPromises: Promise<void>[] = [refetchWalletPortfolio()];

If changing the prop type isn’t feasible, consider using the local query’s refetch from useGetWalletPortfolioQuery instead.

🤖 Prompt for AI Agents
In src/apps/pulse/components/App/HomeScreen.tsx around lines 52 to 92, the
refresh uses Promise.resolve(refetchWalletPortfolio()) which won’t await an
underlying asynchronous refetch if refetchWalletPortfolio returns void; change
the code so the wallet refetch is a real Promise: either 1) update the
refetchWalletPortfolio prop to return a Promise<void> and call/await it directly
when building refreshPromises, or 2) if you cannot change the prop signature,
call the local query hook’s refetch (useGetWalletPortfolioQuery().refetch) which
returns a Promise and push/await that Promise in refreshPromises; ensure the
Promise you add represents the actual network operation so
setIsRefreshingHome(false) only runs after the real refetch completes.

💡 Verification agent

🧩 Analysis chain

Verify removal of legacy Loading/Refresh contexts across repo

Ensure no stale imports/usages remain.


🏁 Script executed:

#!/bin/bash
# Find any leftover context usages or old onRefresh props.
rg -nP -C2 '(LoadingContext|RefreshContext|useLoading|useRefresh|LoadingProvider|RefreshProvider|\bonRefresh\b)'

Length of output: 401


Update remaining test using onRefresh

Search found only one leftover: src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx — onRefresh: vi.fn() (≈line 89). Remove or update this test to the new refresh API; no other LoadingContext/RefreshContext/useLoading/useRefresh/LoadingProvider/RefreshProvider occurrences found.

🤖 Prompt for AI Agents
In src/apps/pulse/components/App/HomeScreen.tsx lines 52–92 we switched to a
centralized refresh API (handleRefresh using refetchWalletPortfolio and
getBestSellOffer) so update the remaining test at
src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (around line ~89) that
still passes onRefresh: vi.fn(); remove that obsolete onRefresh prop and instead
mock or spy the new refresh mechanism used by the component (e.g., the refresh
hook or RefreshContext/provider your app now uses), trigger the refresh via that
API in the test, and update assertions to expect the new refresh calls/behavior
rather than the old onRefresh mock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants