Skip to content

Conversation

@fairlighteth
Copy link
Contributor

@fairlighteth fairlighteth commented Nov 17, 2025

Summary

This PR splits the selector into a proper controller/view structure without changing the UI:

  • Extracts all widget state management into dedicated controller*.ts modules (view props builder, modal props memo, state helpers, etc.).
  • Leaves SelectTokenWidget/index.tsx as a thin shell that just renders the modal + chain panel based on controller output.
  • Moves the widget styling (Wrapper/InnerWrapper/ModalContainer) into styled.ts, keeping layout constants in one place.
  • Adds the inline comment documenting the recent‑token storage schema.

No visual changes yet—this is purely to unblock the subsequent UX PR.

Testing

  • Manual sanity: open/close selector in swap/limit/TWAP to confirm behavior matches feat/token-selector-8

Summary by CodeRabbit

  • New Features

    • Improved token selection widget architecture with better state management and component organization.
  • Bug Fixes

    • Token widget now renders consistently within the trade interface.
  • Style

    • Added responsive styling for token widget layout with improved layout flexibility.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
cowfi Ready Ready Preview Dec 15, 2025 8:57am
explorer-dev Ready Ready Preview Dec 15, 2025 8:57am
swap-dev Ready Ready Preview Dec 15, 2025 8:57am
widget-configurator Ready Ready Preview Dec 15, 2025 8:57am
2 Skipped Deployments
Project Deployment Review Updated (UTC)
cosmos Ignored Ignored Dec 15, 2025 8:57am
sdk-tools Ignored Ignored Preview Dec 15, 2025 8:57am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The SelectTokenWidget component is refactored from a monolithic implementation into a controller-driven architecture. Six new controller modules introduce hooks for managing widget state, dependencies, modal props, and view state. The SelectTokenWidgetContent component is removed and its conditional rendering logic is distributed across these controllers. The main component is simplified to delegate to the controller and view components.

Changes

Cohort / File(s) Summary
New Controller Modules
controller.ts, controllerState.ts, controllerDependencies.ts, controllerViewState.ts, controllerModalProps.ts, controllerProps.ts
Introduces six new React hook modules that orchestrate widget state management, token data sources, widget dependencies, modal props, and view state computation. Collectively wire token selection flows, network switching, import handlers, recent tokens, and UI metadata into cohesive controller hooks.
Main Component & Styling
index.tsx, styled.ts
Refactors SelectTokenWidget to use controller-driven rendering with a thin wrapper delegating to controller and view components. Adds styled components (Wrapper, InnerWrapper, ModalContainer) for layout management with responsive chain panel support.
Type & Helper Updates
types.ts, helpers.tsx
Removes SelectTokenWidgetContentProps interface and all associated imports; eliminates SelectTokenWidgetContent component that previously handled conditional modal rendering.
Documentation & Trade Widget
recentTokensStorage.ts, ../../trade/containers/TradeWidget/index.tsx
Adds storage schema documentation comments. Changes token widget rendering to always resolve to the widget component regardless of open state.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–75 minutes

  • controllerState.ts: High logic density with multiple hooks managing visibility, token admin actions, data sources, widget metadata, import flow orchestration, network switching, and recent tokens; requires careful review of handler composition and hook dependencies.
  • controllerViewState.ts: Orchestrates multiple dependencies and features (bridge flag, chain panel logic, modal props building); integration with external hooks needs validation.
  • controller.ts & controllerDependencies.ts & controllerModalProps.ts: Verify proper memoization, dependency arrays, and handler signatures across the controller composition layers.
  • Removal of SelectTokenWidgetContent: Ensure all conditional rendering logic (ImportTokenModal, ImportListModal, ManageListsAndTokens, LpTokenPage, SelectTokenModal) is correctly replicated in the new architecture.
  • Integration points: Validate that all existing state, handlers, and UI flows remain functional after component restructuring.

Possibly related PRs

Suggested reviewers

  • limitofzero
  • elena-zh
  • shoom3301

Poem

🐰 A widget once tangled, now controllers unwind,
Hooks orchestrate tokens with logic refined,
From monolith chaos to architecture so clear,
Network switches smoothly—no bridge-crossing fear!
The recent tokens remember, imports flow sweet,
This refactor makes SelectToken complete! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
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.
Title check ❓ Inconclusive The title is incomplete/truncated ('feat(tokenselector): 9 - implement SelectTokenWidget with enhanced chain …') and vague due to the ellipsis, failing to clearly convey the core refactoring objective of restructuring into controller/view architecture. Complete the title to clearly summarize the main change, e.g., 'refactor: split SelectTokenWidget into controller/view structure' or 'feat(tokenselector): restructure SelectTokenWidget with controller-driven architecture'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description covers summary, technical details, and testing checklist, but lacks proper issue reference, visual examples/screenshots, and background context despite the template requiring them.

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.

@fairlighteth
Copy link
Contributor Author

@coderabbitai review

@alfetopito
Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 12, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Collaborator

@alfetopito alfetopito left a comment

Choose a reason for hiding this comment

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

Now that you mentioned LIMIT/TWAP, I forgot to check it in earlier PRs.
In this one, the selector is broken.
The page height gets very long, but no tokens can be seen at all
image

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: 3

🧹 Nitpick comments (6)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (2)

148-150: useState called inline within useWidgetMetadata may cause issues.

The useState call at line 148-150 is invoked directly within useWidgetMetadata. If the parameters (field, oppositeToken, lpTokensWithBalancesCount) change between renders, the initial value passed to useState will be ignored after the first render, potentially causing stale category state.

Consider lifting this state to the consuming component or using useEffect to reset the state when dependencies change.


302-304: Consider making the utility more explicit.

hasAvailableChains returns Boolean(chainsToSelect) which checks for truthiness. If ChainsToSelectState can be an empty object or have empty arrays, this might not accurately reflect "available chains."

-export function hasAvailableChains(chainsToSelect: ChainsToSelectState | undefined): boolean {
-  return Boolean(chainsToSelect)
+export function hasAvailableChains(chainsToSelect: ChainsToSelectState | undefined): boolean {
+  return Boolean(chainsToSelect && (chainsToSelect.isLoading || (chainsToSelect.chains?.length ?? 0) > 0))
 }
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts (1)

146-214: Consider extracting the inline type annotation.

The createSelectTokenModalProps function has a large inline type annotation (lines 168-190). Consider extracting this to a named interface for reusability and readability.

interface CreateSelectTokenModalPropsArgs {
  account: string | undefined
  chainsPanelTitle: string
  // ... rest of properties
}

function createSelectTokenModalProps(args: CreateSelectTokenModalPropsArgs): SelectTokenModalProps {
  // ...
}
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts (2)

17-40: Avoid exporting ReturnType<typeof useX>-shaped API types (brittle surface).

Since WidgetViewDependenciesResult is exported, tying its field types to hook ReturnTypes will force downstream churn on any refactor of those hooks. Prefer exporting explicit named function types (or the shared interfaces like RecentTokenSection, PoolPageHandlers, etc.) instead.


31-40: Prefer activeChainId?: number over activeChainId: number | undefined for args.

This reads more idiomatically for optional inputs and reduces call-site noise.

apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts (1)

126-183: Consider making hasChainPanel: true imply onSelectChain is required (type-level).

Right now onSelectChain is optional and silently no-ops; a discriminated union on hasChainPanel would prevent “panel shown but selection doesn’t work” states.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71e5b70 and a316ba2.

📒 Files selected for processing (12)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controller.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerViewState.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/helpers.tsx (0 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/styled.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/types.ts (0 hunks)
  • apps/cowswap-frontend/src/modules/tokensList/hooks/recentTokensStorage.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/helpers.tsx
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/types.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-10-10T20:28:16.565Z
Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 6347
File: apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx:49-49
Timestamp: 2025-10-10T20:28:16.565Z
Learning: In apps/cowswap-frontend/src/modules/trade, TradeConfirmation follows a two-layer architecture: TradeConfirmationView (pure/stateless) in pure/TradeConfirmation/index.tsx renders the UI, while TradeConfirmation (container) in containers/TradeConfirmation/index.tsx wraps it to freeze props during pending trades (via useStableTradeConfirmationProps), wire in signing state (useSigningStep), and inject trade confirmation state (useTradeConfirmState). Consuming modules should import the container TradeConfirmation from 'modules/trade' to preserve this stateful behavior.
<!-- [add_learning]
When reviewing component refactoring in apps/cowswap-frontend/src/modules/trade, recognize the pattern where a pure view component (e.g., TradeConfirmationView) is separated from a stateful container (e.g., TradeConfirmation) that wraps it. The container adds runtime state management (prop stabilization, signing state, etc.) while the view remains testable and composable. Do not flag usages that import th...

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/styled.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts
  • apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerViewState.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controller.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-09-19T11:38:59.206Z
Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 6232
File: apps/cowswap-frontend/src/modules/tokensList/pure/ChainsSelector/index.tsx:199-200
Timestamp: 2025-09-19T11:38:59.206Z
Learning: The makeBuildClickEvent function in apps/cowswap-frontend/src/modules/tokensList/pure/ChainsSelector/index.tsx takes five parameters: defaultChainId, contextLabel, mode, isSwapMode, and chainsCount. The chainsCount parameter is used to determine the CrossChain flag in analytics events.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts
  • apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerViewState.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controller.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-08-12T05:57:08.021Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6138
File: libs/hook-dapp-lib/src/hookDappsRegistry.ts:1-1
Timestamp: 2025-08-12T05:57:08.021Z
Learning: The matchHooksToDapps function in libs/hook-dapp-lib/src/utils.ts provides backward compatibility for permit hooks through function selector detection (EIP_2612_PERMIT_SELECTOR and DAI_PERMIT_SELECTOR) rather than dappId matching, making it robust against dappId changes.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts
📚 Learning: 2025-02-20T15:59:33.749Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 5443
File: apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx:71-71
Timestamp: 2025-02-20T15:59:33.749Z
Learning: The swap module in apps/cowswap-frontend/src/modules/swap/ is marked for deletion in PR #5444 as part of the swap widget unification effort.

Applied to files:

  • apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-07-24T16:42:53.154Z
Learnt from: cowdan
Repo: cowprotocol/cowswap PR: 6009
File: apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/HighFeeWarningTooltipContent.tsx:23-33
Timestamp: 2025-07-24T16:42:53.154Z
Learning: In apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/HighFeeWarningTooltipContent.tsx, the use of toFixed(2) for percentage formatting in tooltip content is intentional and differs from the banner message formatting that uses toSignificant(2, undefined, Rounding.ROUND_DOWN). This formatting difference serves different UX purposes and should not be flagged as inconsistent.

Applied to files:

  • apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx
📚 Learning: 2025-07-18T08:07:55.497Z
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/tokens/src/const/tokensList.json:135-167
Timestamp: 2025-07-18T08:07:55.497Z
Learning: Token lists for CoW Swap are maintained in a separate repository at https://github.com/cowprotocol/token-lists, not in the main cowswap repository. Issues related to missing token lists should be tracked in the token-lists repository.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-05-26T12:39:29.009Z
Learnt from: cowdan
Repo: cowprotocol/cowswap PR: 5715
File: libs/common-const/src/tokens.ts:539-555
Timestamp: 2025-05-26T12:39:29.009Z
Learning: The team accepts using NATIVE_CURRENCY_ADDRESS as a temporary placeholder for COW token contract addresses on new networks (Polygon, Avalanche) until actual COW contracts are deployed.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-08-05T14:27:05.023Z
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerViewState.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-08-08T13:55:17.528Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/state/tokens/allTokensAtom.ts:78-78
Timestamp: 2025-08-08T13:55:17.528Z
Learning: In libs/tokens/src/state/tokens/allTokensAtom.ts (TypeScript/Jotai), the team prefers to wait for token lists to initialize (listsStatesListAtom non-empty) before returning tokens. No fallback to favorites/user-added/native tokens should be used when listsStatesList is empty.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerViewState.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts
📚 Learning: 2025-08-08T13:56:18.009Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/updaters/TokensListsUpdater/index.tsx:29-31
Timestamp: 2025-08-08T13:56:18.009Z
Learning: In libs/tokens/src/updaters/TokensListsUpdater/index.tsx, the project’s current Jotai version requires using `unstable_getOnInit` (not `getOnInit`) in atomWithStorage options; keep `{ unstable_getOnInit: true }` until Jotai is upgraded.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/hooks/recentTokensStorage.ts
📚 Learning: 2025-08-12T06:33:19.348Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6137
File: libs/tokens/src/state/tokens/allTokensAtom.ts:34-65
Timestamp: 2025-08-12T06:33:19.348Z
Learning: In libs/tokens/src/utils/parseTokenInfo.ts, the parseTokenInfo() function returns a new instance of TokenInfo using object spread syntax ({ ...token, ... }), making it safe to mutate properties like lpTokenProvider on the returned object without side effects.

Applied to files:

  • apps/cowswap-frontend/src/modules/tokensList/hooks/recentTokensStorage.ts
🧬 Code graph analysis (6)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/styled.ts (1)
libs/common-hooks/src/useTheme.ts (1)
  • styled (26-26)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts (1)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (8)
  • useManageWidgetVisibility (92-99)
  • usePoolPageHandlers (182-195)
  • useRecentTokenSection (241-260)
  • useTokenSelectionHandler (262-300)
  • useImportFlowCallbacks (197-239)
  • useTokenDataSources (116-138)
  • useTokenAdminActions (101-114)
  • useDismissHandler (172-180)
apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx (1)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx (1)
  • SelectTokenWidget (17-31)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts (5)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controller.ts (1)
  • SelectTokenWidgetViewProps (85-85)
libs/common-const/src/types.ts (1)
  • TokenWithLogo (6-36)
libs/tokens/src/types.ts (1)
  • ListState (39-42)
apps/cowswap-frontend/src/modules/tokensList/types.ts (2)
  • ChainsToSelectState (26-30)
  • TokenSelectionHandler (11-11)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (1)
  • TokenDataSources (54-66)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts (4)
libs/common-const/src/types.ts (1)
  • TokenWithLogo (6-36)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerDependencies.ts (1)
  • WidgetViewDependenciesResult (17-29)
apps/cowswap-frontend/src/modules/tokensList/hooks/useOnSelectChain.ts (1)
  • useOnSelectChain (14-32)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (6)
  • useRecentTokenSection (241-260)
  • useTokenDataSources (116-138)
  • useWidgetMetadata (140-156)
  • useManageWidgetVisibility (92-99)
  • usePoolPageHandlers (182-195)
  • useTokenSelectionHandler (262-300)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controller.ts (5)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts (1)
  • SelectTokenWidgetViewProps (14-34)
apps/cowswap-frontend/src/modules/tokensList/hooks/useOnSelectChain.ts (1)
  • useOnSelectChain (14-32)
libs/common-hooks/src/useIsBridgingEnabled.ts (1)
  • useIsBridgingEnabled (5-7)
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (4)
  • useManageWidgetVisibility (92-99)
  • useTokenDataSources (116-138)
  • useTokenAdminActions (101-114)
  • useWidgetMetadata (140-156)
libs/wallet/src/api/hooks.ts (1)
  • useWalletInfo (24-26)
🔇 Additional comments (20)
apps/cowswap-frontend/src/modules/tokensList/hooks/recentTokensStorage.ts (1)

6-7: Storage schema/migration comment matches implementation; keep it in sync with versioning.

This accurately reflects readStoredTokens() handling of legacy array payloads vs map payloads and the StoredRecentTokensByChain type.

apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerState.ts (7)

1-36: Well-organized imports and type definitions.

The module organization is clean, with clear separation between external dependencies, internal modules, and local imports. The type definitions (interfaces) provide good documentation of the expected shapes.


92-114: LGTM - Clean hook implementations.

useManageWidgetVisibility and useTokenAdminActions are well-structured with proper useCallback memoization and clear return types.


116-138: LGTM - Token data aggregation hook.

useTokenDataSources cleanly aggregates multiple data sources into a single return object with clear typing.


172-195: LGTM - Handler hooks are well-memoized.

Both useDismissHandler and usePoolPageHandlers correctly use useCallback with appropriate dependency arrays.


197-239: LGTM - Import flow callbacks with proper error handling.

The useImportFlowCallbacks hook correctly handles the import flow with try/catch for error cases and proper cleanup via updateSelectTokenWidget.


241-260: LGTM - Recent token section wrapper.

Clean wrapper around useRecentTokens with proper memoization of the click handler.


280-286: The code is correct. The in operator works reliably with TypeScript numeric enums because the compiled output includes reverse mappings where numeric values become string keys. This pattern is used throughout the codebase and is the established convention here.

apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/index.tsx (1)

26-26: LGTM - Clean slot-based integration.

The change correctly delegates rendering control to SelectTokenWidget itself (via shouldRender in the controller), while maintaining slot override capability for custom implementations.

apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/styled.ts (1)

1-22: LGTM - Well-structured styled components.

The layout components are clean and follow styled-components conventions. The transient prop $hasSidebar is correctly prefixed to avoid DOM attribute forwarding, and the responsive flex-direction logic is appropriate.

apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerModalProps.ts (3)

20-20: Good practice - stable empty array reference.

Using a module-level constant EMPTY_FAV_TOKENS prevents unnecessary re-renders when standalone is true, as the array reference remains stable across renders.


37-78: LGTM - Well-structured modal props hook.

The useWidgetModalProps hook correctly assembles all necessary props and delegates memoization to useSelectTokenModalPropsMemo. The conditional favoriteTokens handling for standalone mode is appropriate.


100-144: LGTM - Clean props transformation.

getSelectTokenWidgetViewPropsArgs is a pure function that maps internal hook results to the view props interface. The property renaming (e.g., importFlows.resetTokenImportonBackFromImport) improves API clarity.

apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controller.ts (5)

24-33: LGTM - Clear interface definitions.

The SelectTokenWidgetProps and SelectTokenWidgetController interfaces provide clear contracts for the component's input and output, supporting the controller/view architecture pattern.


35-58: LGTM - Well-organized hook initialization.

The controller correctly aggregates all required state and data sources. The resolvedField fallback to Field.INPUT provides a safe default.


60-76: LGTM - Clean delegation to view state hook.

The useSelectTokenWidgetViewState call centralizes view state computation with all necessary dependencies passed through.


85-85: LGTM - Type re-export for public API.

Re-exporting SelectTokenWidgetViewProps allows consumers to type their props when implementing custom views.


78-83: Verify shouldRender condition matches previous behavior.

The condition widgetState.onSelectToken && (widgetState.open || widgetState.forceOpen) determines when the widget renders. Ensure this matches the previous implementation - particularly that onSelectToken being falsy should prevent rendering.

#!/bin/bash
# Search for previous shouldRender or open/forceOpen logic patterns
rg -n "shouldRender|forceOpen|\.open" --type ts apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/ -C 3
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerViewState.ts (1)

122-136: resolveActiveChainId() ordering looks sensible; helper is appropriately defensive.

apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx (1)

67-125: Blocking view selection is clean and readable; ordering matches expected precedence.

Comment on lines +185 to +249
export function useSelectTokenModalPropsMemo(props: SelectTokenModalProps): SelectTokenModalProps {
return useMemo(
() => ({
standalone: props.standalone,
displayLpTokenLists: props.displayLpTokenLists,
unsupportedTokens: props.unsupportedTokens,
selectedToken: props.selectedToken,
allTokens: props.allTokens,
favoriteTokens: props.favoriteTokens,
recentTokens: props.recentTokens,
balancesState: props.balancesState,
permitCompatibleTokens: props.permitCompatibleTokens,
onSelectToken: props.onSelectToken,
onTokenListItemClick: props.onTokenListItemClick,
onInputPressEnter: props.onInputPressEnter,
onDismiss: props.onDismiss,
onOpenManageWidget: props.onOpenManageWidget,
openPoolPage: props.openPoolPage,
tokenListCategoryState: props.tokenListCategoryState,
disableErc20: props.disableErc20,
account: props.account,
areTokensLoading: props.areTokensLoading,
tokenListTags: props.tokenListTags,
areTokensFromBridge: props.areTokensFromBridge,
isRouteAvailable: props.isRouteAvailable,
modalTitle: props.modalTitle,
hasChainPanel: props.hasChainPanel,
hideFavoriteTokensTooltip: props.hideFavoriteTokensTooltip,
chainsPanelTitle: props.chainsPanelTitle,
selectedTargetChainId: props.selectedTargetChainId,
onSelectChain: props.onSelectChain,
onClearRecentTokens: props.onClearRecentTokens,
}),
[
props.standalone,
props.displayLpTokenLists,
props.unsupportedTokens,
props.selectedToken,
props.allTokens,
props.favoriteTokens,
props.recentTokens,
props.balancesState,
props.permitCompatibleTokens,
props.onSelectToken,
props.onTokenListItemClick,
props.onInputPressEnter,
props.onDismiss,
props.onOpenManageWidget,
props.openPoolPage,
props.tokenListCategoryState,
props.disableErc20,
props.account,
props.areTokensLoading,
props.tokenListTags,
props.areTokensFromBridge,
props.isRouteAvailable,
props.modalTitle,
props.hasChainPanel,
props.chainsPanelTitle,
props.hideFavoriteTokensTooltip,
props.selectedTargetChainId,
props.onSelectChain,
props.onClearRecentTokens,
],
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

BUG: useSelectTokenModalPropsMemo() drops chainsToSelect (and doesn’t depend on it).

buildSelectTokenModalPropsInput() provides chainsToSelect, but the memo hook doesn’t include it in the returned object or deps, so the modal can lose chain state (or become stale).

 export function useSelectTokenModalPropsMemo(props: SelectTokenModalProps): SelectTokenModalProps {
   return useMemo(
     () => ({
@@
       modalTitle: props.modalTitle,
       hasChainPanel: props.hasChainPanel,
+      chainsToSelect: props.chainsToSelect,
       hideFavoriteTokensTooltip: props.hideFavoriteTokensTooltip,
       chainsPanelTitle: props.chainsPanelTitle,
       selectedTargetChainId: props.selectedTargetChainId,
       onSelectChain: props.onSelectChain,
       onClearRecentTokens: props.onClearRecentTokens,
     }),
     [
@@
       props.modalTitle,
       props.hasChainPanel,
+      props.chainsToSelect,
       props.chainsPanelTitle,
       props.hideFavoriteTokensTooltip,
       props.selectedTargetChainId,
       props.onSelectChain,
       props.onClearRecentTokens,
     ],
   )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useSelectTokenModalPropsMemo(props: SelectTokenModalProps): SelectTokenModalProps {
return useMemo(
() => ({
standalone: props.standalone,
displayLpTokenLists: props.displayLpTokenLists,
unsupportedTokens: props.unsupportedTokens,
selectedToken: props.selectedToken,
allTokens: props.allTokens,
favoriteTokens: props.favoriteTokens,
recentTokens: props.recentTokens,
balancesState: props.balancesState,
permitCompatibleTokens: props.permitCompatibleTokens,
onSelectToken: props.onSelectToken,
onTokenListItemClick: props.onTokenListItemClick,
onInputPressEnter: props.onInputPressEnter,
onDismiss: props.onDismiss,
onOpenManageWidget: props.onOpenManageWidget,
openPoolPage: props.openPoolPage,
tokenListCategoryState: props.tokenListCategoryState,
disableErc20: props.disableErc20,
account: props.account,
areTokensLoading: props.areTokensLoading,
tokenListTags: props.tokenListTags,
areTokensFromBridge: props.areTokensFromBridge,
isRouteAvailable: props.isRouteAvailable,
modalTitle: props.modalTitle,
hasChainPanel: props.hasChainPanel,
hideFavoriteTokensTooltip: props.hideFavoriteTokensTooltip,
chainsPanelTitle: props.chainsPanelTitle,
selectedTargetChainId: props.selectedTargetChainId,
onSelectChain: props.onSelectChain,
onClearRecentTokens: props.onClearRecentTokens,
}),
[
props.standalone,
props.displayLpTokenLists,
props.unsupportedTokens,
props.selectedToken,
props.allTokens,
props.favoriteTokens,
props.recentTokens,
props.balancesState,
props.permitCompatibleTokens,
props.onSelectToken,
props.onTokenListItemClick,
props.onInputPressEnter,
props.onDismiss,
props.onOpenManageWidget,
props.openPoolPage,
props.tokenListCategoryState,
props.disableErc20,
props.account,
props.areTokensLoading,
props.tokenListTags,
props.areTokensFromBridge,
props.isRouteAvailable,
props.modalTitle,
props.hasChainPanel,
props.chainsPanelTitle,
props.hideFavoriteTokensTooltip,
props.selectedTargetChainId,
props.onSelectChain,
props.onClearRecentTokens,
],
)
export function useSelectTokenModalPropsMemo(props: SelectTokenModalProps): SelectTokenModalProps {
return useMemo(
() => ({
standalone: props.standalone,
displayLpTokenLists: props.displayLpTokenLists,
unsupportedTokens: props.unsupportedTokens,
selectedToken: props.selectedToken,
allTokens: props.allTokens,
favoriteTokens: props.favoriteTokens,
recentTokens: props.recentTokens,
balancesState: props.balancesState,
permitCompatibleTokens: props.permitCompatibleTokens,
onSelectToken: props.onSelectToken,
onTokenListItemClick: props.onTokenListItemClick,
onInputPressEnter: props.onInputPressEnter,
onDismiss: props.onDismiss,
onOpenManageWidget: props.onOpenManageWidget,
openPoolPage: props.openPoolPage,
tokenListCategoryState: props.tokenListCategoryState,
disableErc20: props.disableErc20,
account: props.account,
areTokensLoading: props.areTokensLoading,
tokenListTags: props.tokenListTags,
areTokensFromBridge: props.areTokensFromBridge,
isRouteAvailable: props.isRouteAvailable,
modalTitle: props.modalTitle,
hasChainPanel: props.hasChainPanel,
chainsToSelect: props.chainsToSelect,
hideFavoriteTokensTooltip: props.hideFavoriteTokensTooltip,
chainsPanelTitle: props.chainsPanelTitle,
selectedTargetChainId: props.selectedTargetChainId,
onSelectChain: props.onSelectChain,
onClearRecentTokens: props.onClearRecentTokens,
}),
[
props.standalone,
props.displayLpTokenLists,
props.unsupportedTokens,
props.selectedToken,
props.allTokens,
props.favoriteTokens,
props.recentTokens,
props.balancesState,
props.permitCompatibleTokens,
props.onSelectToken,
props.onTokenListItemClick,
props.onInputPressEnter,
props.onDismiss,
props.onOpenManageWidget,
props.openPoolPage,
props.tokenListCategoryState,
props.disableErc20,
props.account,
props.areTokensLoading,
props.tokenListTags,
props.areTokensFromBridge,
props.isRouteAvailable,
props.modalTitle,
props.hasChainPanel,
props.chainsToSelect,
props.chainsPanelTitle,
props.hideFavoriteTokensTooltip,
props.selectedTargetChainId,
props.onSelectChain,
props.onClearRecentTokens,
],
)
}
🤖 Prompt for AI Agents
In
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/controllerProps.ts
around lines 185 to 249, the memoized props object omits chainsToSelect and the
useMemo dependency array does not include it, causing chain selection state to
be lost or stale; update the returned object to include chainsToSelect:
props.chainsToSelect and add props.chainsToSelect to the dependency array so the
memo updates when chainsToSelect changes.

Comment on lines +46 to +95
// TODO: Re-enable once Yield should support cross-network selection in the modal.
const ENABLE_YIELD_CHAIN_PANEL = false

export function useSelectTokenWidgetViewState(args: SelectTokenWidgetViewStateArgs): ViewStateResult {
const {
displayLpTokenLists,
standalone,
widgetState,
chainsToSelect,
onSelectChain,
manageWidget,
updateSelectTokenWidget,
account,
closeTokenSelectWidget,
tokenData,
onTokenListAddingError,
tokenAdminActions,
widgetMetadata,
walletChainId,
isBridgeFeatureEnabled,
} = args

const activeChainId = resolveActiveChainId(widgetState, walletChainId)
const widgetDeps = useWidgetViewDependencies({
manageWidget,
closeTokenSelectWidget,
updateSelectTokenWidget,
tokenData,
tokenAdminActions,
onTokenListAddingError,
widgetState,
activeChainId,
})
const shouldDisableChainPanelForYield = widgetState.tradeType === TradeType.YIELD && !ENABLE_YIELD_CHAIN_PANEL
const isChainPanelEnabled =
isBridgeFeatureEnabled && hasAvailableChains(chainsToSelect) && !shouldDisableChainPanelForYield
const selectTokenModalProps = useWidgetModalProps({
account,
chainsToSelect,
displayLpTokenLists,
widgetDeps,
hasChainPanel: isChainPanelEnabled,
onSelectChain,
recentTokens: widgetDeps.recentTokens,
standalone,
tokenData,
widgetMetadata,
widgetState,
isInjectedWidgetMode: isInjectedWidget(),
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep a single source of truth for “chain panel enabled” vs “visible”.

This hook clearly defines isChainPanelEnabled and passes it into modal-props construction. Ensure the view layer does not later override hasChainPanel based on layout/visibility (see index.tsx), otherwise Yield/feature-flag gating can be bypassed or chain selection can disappear on mobile.

Comment on lines +53 to +63
const showDesktopChainPanel = isChainPanelVisible && isChainPanelEnabled && chainsToSelect

const {
tokens: allTokens,
isLoading: areTokensLoading,
favoriteTokens,
areTokensFromBridge,
isRouteAvailable,
} = useTokensToSelect()
const { recentTokens, addRecentToken, clearRecentTokens } = useRecentTokens({
allTokens,
favoriteTokens,
activeChainId: selectedTargetChainId ?? walletChainId,
})
const handleTokenListItemClick = useCallback(
(token: TokenWithLogo) => {
addRecentToken(token)
},
[addRecentToken],
return (
<>
<ModalContainer>
<SelectTokenModal {...selectTokenModalProps} hasChainPanel={isChainPanelVisible} />
</ModalContainer>
{showDesktopChainPanel && (
<ChainPanel title={chainsPanelTitle} chainsState={chainsToSelect} onSelectChain={onSelectChain} />
)}
</>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

BUG: SelectTokenModal overrides hasChainPanel after spread (breaks enablement gating / mobile access).

selectTokenModalProps already contains the correct hasChainPanel derived from controller state; overriding it with isChainPanelVisible conflates “layout sidebar visible” with “feature enabled”.

-        <SelectTokenModal {...selectTokenModalProps} hasChainPanel={isChainPanelVisible} />
+        <SelectTokenModal {...selectTokenModalProps} />

If the modal truly needs a layout signal, pass a separate prop (or rename at the pure component level) rather than reusing hasChainPanel.

🤖 Prompt for AI Agents
In
apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx
around lines 53 to 63, the component spread `selectTokenModalProps` already
contains the correct hasChainPanel from controller state but you are overriding
it with `isChainPanelVisible` which conflates layout visibility with feature
enablement; fix by removing the override so the modal receives the hasChainPanel
from selectTokenModalProps, or if you need to convey layout-only visibility pass
a separately named prop (e.g., layoutHasChainPanel or isSidebarVisible) instead
of reusing hasChainPanel.

Copy link
Collaborator

@alfetopito alfetopito left a comment

Choose a reason for hiding this comment

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

Approving to get this moving forward, however:

  1. There's the issue I pointed out in the previous entry
  2. The controller pattern is only used on https://github.com/cowprotocol/cowswap/blob/develop/apps/cowswap-frontend/src/modules/application/utils/faviconAnimation/controller.ts, and at least from how it's implemented here doesn't look great.
    There seems to be a lot of repetition on the types and props, and the mapping between them.
    I like how it makes the fns smaller though.
    We can discuss this in a more in depth architectural review.
    Anyway, maybe those concerns have been addressed in a future PR as it's mentioned in a reply to Denis.

…/token-selector-9

# Conflicts:
#	apps/cowswap-frontend/src/modules/tokensList/hooks/recentTokensStorage.ts
@fairlighteth
Copy link
Contributor Author

@alfetopito thanks for reviewing:

  1. This has been fixed from the next PR forwards
  2. This has been addressed in PR 14 (after getting similar comments from @limitofzero )

the repetition is gone in feat/token-selector-14: the SelectTokenWidget controllers were flattened—no duplicate view-prop/build types, no 1:1 prop mappers, and no manual “list every prop twice” memo. Token data/context are
hydrated into tokenListViewAtom, and SelectTokenModal now has a slim prop surface. This addresses the prior type/prop mapping noise without losing the smaller functions.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants