Skip to content

Conversation

@gomesalexandre
Copy link
Contributor

@gomesalexandre gomesalexandre commented Jan 16, 2026

Description

A collection of UX and layout improvements for the yield/staking pages.

Layout & Display

  • Add two-column desktop layout for yield detail page (info left, actions right)
  • Create YieldInfoCard component for strategy info display
  • Create YieldAvailableToDeposit component showing wallet balance and potential earnings
  • Swap fiat/crypto display order on yield detail page
  • Display strategy names instead of asset symbols in yield cards
  • Create getYieldDisplayName utility for clean yield names (curator name for Morpho vaults, symbol for simple yields)
  • Add clean icon + label display for Asset, Chain, and Protocol (desktop and mobile)
  • Remove useless secondary row on mobile showing ugly metadata name

UX Improvements

  • Add "Available to Earn" tab for yields page navigation
  • Add yield explainers to YieldForm with educational tooltips
  • Add documentation link to yield detail page
  • Create YieldExplainers component for consistent staking info

Bug Fixes

  • Fix filter handling and code cleanup on yield page
  • Fix validator mismatch bug on yield detail page (Cosmos/Solana now properly use default validators)
  • Add maintenance and deprecated warnings for yield opportunities
  • Fix "Visit Website" alignment in YieldProviderInfo
  • Fix Lending badge from UPPERCASE to Capitalize

Issue (if applicable)

closes #

Risk

Low-Medium risk. UI/UX changes only, no changes to transaction logic or state management. Changes are isolated to yield pages.

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

None - these are purely display/UI changes.

Testing

Engineering

  1. Navigate to /yields and verify the list displays correctly
  2. Click on a Morpho vault (e.g., Steakhouse High Yield) and verify:
    • Card name matches detail page name
    • Asset, Chain, Protocol display with icons
    • Two-column layout on desktop, stacked on mobile
  3. Navigate to Cosmos native staking and verify ShapeShift DAO is always the default validator
  4. Verify "Available to Deposit" card shows correct wallet balance
  5. Test on mobile viewport to verify layout

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Manual testing steps:

  • Visit yield list page, verify cards show clean names (e.g., "Steakhouse High Yield" not "Steakhouse High Yield USDC Morpho Vault")
  • Click into a yield detail page, verify Asset/Chain/Protocol icons display correctly
  • Test on mobile device or viewport, verify layout is clean and readable
  • Verify Cosmos staking defaults to ShapeShift DAO validator

Screenshots (if applicable)


🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced yields feature with detailed yield information pages, provider descriptions, and website links.
    • Added available deposit amounts with potential yearly earnings calculations.
    • Implemented yield explainers showing mechanics (liquid staking, vaults, lending, etc.).
    • Added related yields recommendations.
    • Introduced tab navigation for viewing all yields, available opportunities, and positions.
    • Added "New" badge indicator for yields feature in navigation.
  • Documentation

    • Added comprehensive React and Next.js performance optimization guide with 40+ best practices.

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

gomesalexandre and others added 14 commits January 16, 2026 12:41
Remove titleOverride props so single yields show their metadata.name
(e.g., "Aave v3 USDC Lending") instead of just the asset symbol.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Display warning badges on yield cards and alerts on detail pages when
yields are marked as underMaintenance or deprecated in the API.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…badges

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create reusable YieldExplainers component that shows reward schedule,
unbonding periods, and other yield-specific info based on mechanics type.
Add it to YieldEnterModal and EarnConfirm for consistent user education
across all enter flows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show a "Learn more" link with external icon when a yield has
metadata.documentation available. Appears below the description
text on the yield detail page.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add YieldExplainers component to YieldForm.tsx (yield page enter modal)
- Update ProviderDto to include website and references fields
- Use provider.references[0] for documentation link (protocol website)
- Fix docs link styling: inline icon next to description
- Prefer provider documentation over yield metadata documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 3-tab structure: All | Available to Earn | My Positions
- Auto-navigate to "Available to Earn" tab when wallet connects
- Each tab has explicit URL param for proper navigation
- Keep recommended strip visible in all tabs
- Add YieldTab enum for type safety
- Rename Earn button action to navigate to Available tab

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix filters not working on My Positions tab (use unfiltered data)
- Fix filters affecting Recommended For You (should be independent)
- Fix "Show all" button navigating to All tab when on Available tab
- Show full metadata.name on yield detail page below provider pill
- Hide Withdraw button when user has no balance (instead of disabled)
- Add YieldProviderInfo component with provider descriptions
- Add YieldRelatedMarkets component showing other yields for same token
- Make Yields first tab in Earn menu when feature flag enabled
- Code simplification: remove unnecessary useMemo wrappers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show fiat value as primary (large text) and crypto as secondary.
This matches user expectations where fiat is the more relevant metric.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a professional two-column layout for desktop screens with info on the
left and actions on the right. Create YieldInfoCard and YieldAvailableToDeposit
components. Mobile layout remains unchanged.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix validator mismatch on yield detail page by passing selectedValidatorAddress
  from YieldDetail to YieldPositionCard (ensures Cosmos uses ShapeShift DAO)
- Add "New" badge support to NavigationDropdown component
- Mark Yields menu item with New badge
- Apply react-best-practices cleanup to yield components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create getYieldDisplayName that returns curator name for Morpho/Yearn
  vaults (e.g., "Steakhouse High Yield") and symbol for simple yields
- Integrate into YieldItem, YieldDetail, YieldInfoCard, YieldHero
- Remove useless secondary row on mobile showing ugly metadata name
- Fix Visit Website alignment in YieldProviderInfo
- Fix Lending badge from UPPERCASE to Capitalize
- Simplify components by removing unnecessary useMemo calls

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace cramped pill with clean icon + label pairs
- Show Asset (icon + symbol), Chain (icon + name), Protocol (icon + name)
- Apply same pattern to both desktop (YieldInfoCard) and mobile (YieldHero)
- Remove redundant subtitle section
- Clean up unused code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gomesalexandre gomesalexandre requested a review from a team as a code owner January 16, 2026 15:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

Warning

Rate limit exceeded

@gomesalexandre has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b24cbd9 and e872c09.

📒 Files selected for processing (10)
  • .claude/skills/react-best-practices/SKILL.md
  • src/pages/Yields/YieldDetail.tsx
  • src/pages/Yields/components/YieldAvailableToDeposit.tsx
  • src/pages/Yields/components/YieldEnterModal.tsx
  • src/pages/Yields/components/YieldExplainers.tsx
  • src/pages/Yields/components/YieldHero.tsx
  • src/pages/Yields/components/YieldItem.tsx
  • src/pages/Yields/components/YieldPositionCard.tsx
  • src/pages/Yields/components/YieldProviderInfo.tsx
  • src/pages/Yields/components/YieldsList.tsx
📝 Walkthrough

Walkthrough

This PR introduces comprehensive React best practices documentation (40+ performance optimization rules) and significantly expands the Yields feature with new UI components, utilities, translations, and navigation enhancements including provider information, explainers, and related market discovery.

Changes

Cohort / File(s) Summary
React Best Practices Documentation
.claude/skills/react-best-practices/*
Adds comprehensive performance optimization guide with SKILL.md overview and detailed reference documentation covering 8 categories: eliminating waterfalls, bundle size, server/client-side patterns, re-render optimization, rendering performance, JavaScript micro-optimizations, and advanced patterns. Includes 40+ rules with code examples, configuration guidance, and structured metadata.
Yield Feature Components
src/pages/Yields/components/{YieldAvailableToDeposit,YieldExplainers,YieldInfoCard,YieldProviderInfo,YieldRelatedMarkets}.tsx
Adds 5 new memoized components for yield detail presentation: displays available balance to deposit, contextual explainers by yield type, consolidated info card with alerts, provider details with description/links, and related yield recommendations with filtering and sorting.
Yield Page Refactor
src/pages/Yields/{YieldDetail,components/{YieldEnterModal,YieldForm,YieldHero,YieldItem,YieldOpportunityStats,YieldPositionCard,YieldStats,YieldsList}}.tsx
Major restructuring of yield pages: YieldDetail now composes new sub-components with responsive layouts; YieldHero extended with descriptions and documentation links; YieldItem refactored for status badges and display names; YieldsList adds third tab (Available To Earn) with navigation helpers; YieldOpportunityStats updates props for tab navigation; simplified memoization patterns throughout.
Yield Utilities & Types
src/lib/yieldxyz/{getYieldDisplayName.ts,getYieldDisplayName.test.ts,providerDescriptions.ts,types.ts}
Adds display name resolution for yields (with curator mapping), comprehensive provider description map, type extensions for ProviderDto (website, references), and corresponding tests using table-driven approach.
Navigation & Header Updates
src/components/Layout/Header/{Header.tsx,NavBar/NavigationDropdown.tsx}
Adds isNew badge flag to submenu items with conditional rendering in NavigationDropdown; updates earn submenu to conditionally show yields as primary path when YieldXyz feature flag enabled; Header refactored memoization patterns.
Translations
src/assets/translations/en/main.json
Adds 40+ new translation strings for yield features: market states (underMaintenance, deprecated), UI labels, yield types, explainer messages, form fields (stakeAmount, selectYieldOpportunity), and earn confirmations.
Hooks & Selectors
src/react-queries/queries/yieldxyz/useYields.ts
Exposes unfiltered yield data via new unfiltered property on hook return for downstream filtering logic.
Supporting Documentation
NEAR_IMPROVEMENTS.md
Design improvement guide for Yield features covering UI/UX enhancements, data field utilization, mobile considerations, and component mapping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Possibly related PRs

  • feat: revert of the revert of new nav #10490: Modifies Header and NavigationDropdown components—overlaps with this PR's navigation updates adding isNew flag and badge handling to yield submenu items.
  • feat: yield.xyz POC #11578: Implements Yield.xyz feature surface with overlapping yield component codepaths, route structures, and getYieldDisplayName/provider metadata utilities.
  • feat: multi-account yield support #11665: Implements multi-account support for yields with shared modifications to YieldDetail, YieldEnterModal, YieldForm, and yield-related hooks.

Suggested labels

high risk, yields, documentation, ui/ux


Suggested reviewers

  • premiumjibles
  • NeOMakinG

Poem

🐰 Hops through yields with docs galore,
Forty rules to optimize and soar,
Components stack with provider insight,
Explainers glow, earning feels right!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: yield improvements p2' is vague and does not clearly convey the specific changes made; it uses a generic placeholder ('p2') rather than describing the main feature or improvement. Consider a more descriptive title such as 'feat: add yield detail redesign with two-column layout and explainers' or 'feat: add Available to Earn tab and yield improvements' to better communicate the main objectives.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat_yield_improve_more

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.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gomesalexandre gomesalexandre changed the title feat: improve yield page UX with clean names and better asset/chain display feat: yield improvements p2 Jan 16, 2026
"quoteFailedDescription": "Unable to get a quote for this transaction. Please try again."
}
},
"underMaintenance": "Under Maintenance",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

revert ea4338b to test deprecated/under maintenance

<NavigationDropdown
label='defi.earn'
items={earnSubMenuItems}
defaultPath={isYieldXyzEnabled ? '/yields' : '/tcy'}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yield as the default path for Earn tab (assuming yield flag is on)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Untranslated is on purpose for now!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note, diverged a bit from codebase bits - added 2d8bb8d on top of it, see this guy which reverts the spewy function/IIFE bits from diff cccc386

… skill

- Prefer arrow functions over function keyword in memo components
- Prefer useMemo over IIFE in TSX
- Prefer implicit returns in useMemo/useCallback
- Document avoidance of nested ternaries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 16

🤖 Fix all issues with AI agents
In @.claude/skills/react-best-practices/references/rules/bundle-conditional.md:
- Around line 14-29: The error handler in AnimationPlayer references setEnabled
which doesn't exist; update the component to either manage its own enabled state
or change the error handling: add local state like const [isEnabled,
setIsEnabled] = useState(enabled) and use isEnabled in the effect and
setIsEnabled(false) in the catch, or remove the setEnabled call and handle the
error by logging or setting frames to an empty array (e.g., setFrames([]));
ensure all references in useEffect and the prop usage are updated to use the
chosen local symbol (isEnabled/setIsEnabled) or no longer call setEnabled.

In
@.claude/skills/react-best-practices/references/rules/client-event-listeners.md:
- Around line 57-66: The useSWRSubscription call is missing its closing
parenthesis and therefore the hook invocation isn't terminated; close the call
by adding the missing ")" after the hook's cleanup return so that the function
body for useSWRSubscription (which defines handler and uses keyCallbacks and
window.addEventListener/removeEventListener) is properly closed—ensure the final
lines read as the cleanup return followed by a closing parenthesis and any
necessary semicolon.

In
@.claude/skills/react-best-practices/references/rules/js-cache-property-access.md:
- Around line 12-18: The comment incorrectly states "(3 lookups × N iterations)"
while the loop also reads arr.length each iteration; update the comment near the
example so it either counts arr.length (e.g., "(4 lookups × N iterations)"
counting arr.length + obj.config.settings.value) or rephrase to explicitly list
both costs (e.g., "arr.length + obj.config.settings.value — total 4 property
accesses per iteration"); reference the loop variables (for, arr.length) and the
nested property access (obj.config.settings.value) when making the
clarification.
- Around line 20-28: Update the example comment and/or code to accurately
represent what's being optimized: change the label "(1 lookup total)" to
something like "Correct (cache lookups before the loop)" or similar, and either
remove the cached arr.length example or note that arr.length caching is an
edge-case optimization; specifically reference the nested access
obj.config.settings.value (which is being cached into value) and the arr.length
caching, and instruct to either keep only the nested property caching example or
add a clarifying note that arr.length caching is typically unnecessary in modern
engines except for polymorphic/mutable-array scenarios.

In @.claude/skills/react-best-practices/references/rules/js-cache-storage.md:
- Around line 41-54: The cookie parsing in getCookie (using
document.cookie.split('; ').map(c => c.split('='))) breaks on values with '='
and doesn't decode or validate entries; fix by parsing each cookie string by
finding the first '=' (e.g., indexOf) to split name and value, decode the value
with decodeURIComponent, ignore empty/malformed entries, and keep the
cookieCache logic intact so getCookie(name) returns the properly decoded value
or undefined for missing/invalid cookies.

In
@.claude/skills/react-best-practices/references/rules/js-length-check-first.md:
- Around line 45-49: Clarify which optimization yields each benefit by mapping
the bullet points to the specific code changes: state that the length-check
(comparing array.length) provides the early-return for differing sizes; that
using .toSorted() versus .sort() prevents mutating the original arrays; that
avoiding join() and string comparisons removes extra memory allocation for large
arrays; and that the explicit element-by-element loop (or early-exit
for/for...of) enables returning early when a difference is found; alternatively,
update the title/description to say the example demonstrates multiple best
practices (length check, non-mutating sort, and early-exit comparison) so
readers understand each bullet corresponds to a distinct change (.length,
.toSorted()/ .sort(), join(), loop early-exit).
- Around line 34-35: Replace uses of the ES2023 .toSorted() calls by creating a
shallow copy and calling .sort() so the code is compatible with target browsers:
update the expressions that set currentSorted and originalSorted (currently
using current.toSorted() and original.toSorted()) to use [...current].sort() (or
Array.from(current).sort()) instead, preserving the original sort comparator if
present.

In @.claude/skills/react-best-practices/references/rules/rendering-hoist-jsx.md:
- Around line 14-42: The examples reference an undefined loading variable and
mix two different patterns; update both examples so they compile and clearly
demonstrate the hoisting pattern: change Container to accept a loading prop
(function Container({ loading }: { loading: boolean })) or otherwise define
loading as state, replace the incorrect example's usage to either call the
reusable component (<LoadingSkeleton />) or use the hoisted element
(loadingSkeleton) consistently, ensure the hoisted constant loadingSkeleton is
declared outside or inside Container as shown, and add a brief note that
hoisting is only beneficial for truly static JSX that never depends on
props/state and is reused conditionally.

In
@.claude/skills/react-best-practices/references/rules/rerender-defer-reads.md:
- Around line 27-38: Add caveats and a "When to use" section to the ShareButton
example: explain that reading window.location.search inside handleShare is a
read-on-demand pattern that won’t trigger re-renders if the URL changes, is
invalid during SSR/Server Components because window is undefined, and differs
from Next.js’s useSearchParams() which provides reactive updates; state that
this pattern is appropriate only for client components and one-off event reads
(like ShareButton.handleShare) or non-Next.js contexts, and recommend
useSearchParams() for URL-driven reactivity in Next.js App Router scenarios.

In
@.claude/skills/react-best-practices/references/rules/rerender-transitions.md:
- Around line 12-23: Update the wording and example in the ScrollTracker snippet
to clarify that frequent scroll updates are treated as urgent by React (not
literally "blocking" the UI) and show the rendering impact: replace the phrase
"blocks UI on every scroll" with wording about "updates are treated as urgent by
default; use startTransition to make them non‑urgent/interruptible", and
complete the example by returning JSX that uses scrollY (e.g., render
<ExpensiveComponent scrollY={scrollY} />) and include a second snippet
demonstrating how to wrap setScrollY in React.startTransition to mark updates as
non‑urgent; reference the ScrollTracker component, setScrollY, startTransition,
and ExpensiveComponent so readers can find and test the behavior.
- Around line 25-40: Extend the "Correct (non-blocking updates)" ScrollTracker
example to include rendering logic that shows the impact of using
startTransition: after the useEffect block, return a JSX fragment that renders
an ExpensiveComponent receiving scrollY (e.g., <ExpensiveComponent
scrollY={scrollY} />) and a short comment explaining that scroll updates are
non-urgent; keep the same state and handler (scrollY, setScrollY,
startTransition) so readers can see how startTransition prevents scroll updates
from blocking the expensive render.

In @.claude/skills/react-best-practices/references/rules/server-cache-react.md:
- Around line 8-10: Update the guidance under the "Per-Request Deduplication
with React.cache()" section to explicitly state that React.cache() only works in
React Server Components (RSC) and will not run in client-side code or browser
bundles; keep existing availability notes intact. Locate the paragraph
mentioning React.cache() and append or replace with a concise sentence like
"Note: React.cache() is only supported in React Server Components and has no
effect in client-side code." to make the RSC requirement unambiguous while
leaving the rest of the advice unchanged.

In
@.claude/skills/react-best-practices/references/rules/server-parallel-fetching.md:
- Around line 1-79: This reference on Parallel Data Fetching incorrectly targets
RSC/Next.js server-side patterns that don't apply to our client-only
shapeshift/web; update the file by either removing it or adding a prominent
applicability note at the top clarifying it's for RSC/App Router only and not
for shapeshift/web, and mention that constructs referenced (async Server
Components like Page, Header, Sidebar, Layout and server data fetchers like
fetchHeader/fetchSidebarItems or utilities such as React.cache()) are not
supported in this repo so readers should ignore or adapt those examples for
client-side equivalents.

In @.claude/skills/react-best-practices/SKILL.md:
- Around line 59-64: Replace the misleading per-icon import example in SKILL.md
(the line showing import Check from 'lucide-react/dist/esm/icons/check') with
the official recommended named-import pattern: use the named export Check via
import { Check } from 'lucide-react' and update the surrounding comment to state
that modern bundlers tree-shake named imports by default; remove or rephrase the
per-icon path suggestion and any claim that it’s the preferred approach without
extra bundler config.

In `@src/pages/Yields/components/YieldsList.tsx`:
- Around line 497-506: The comparator in filtered.sort uses aBalance and
bBalance which may be undefined due to optional chaining on allBalances; ensure
monotonic numeric results by null-coalescing both results to a zero BigNumber
before calling .minus() and .toNumber() (e.g., use aBalance ?? bnOrZero(0) and
bBalance ?? bnOrZero(0)); update the comparator in the YieldsList component so
allBalances[a.id]?.reduce(...) and allBalances[b.id]?.reduce(...) are wrapped
with ?? bnOrZero(0) before invoking .minus() to prevent runtime errors or NaN.
🧹 Nitpick comments (33)
.claude/skills/react-best-practices/references/rules/rerender-dependencies.md (1)

28-45: Consider adding useMemo to align with team's memoization practices.

The derived state example is technically correct and pedagogically clear. However, the team's documented practice (from learnings and .cursor/rules/react-best-practices.mdc) is to use useMemo for "all derived values and conditional values."

Based on learnings, the aggressive memoization pattern would be:

const isMobile = useMemo(() => width < 768, [width])
useEffect(() => {
  if (isMobile) {
    enableMobileMode()
  }
}, [isMobile])

That said, the current example prioritizes teaching the narrow-dependency concept without the noise of memoization hooks, which may be preferable for documentation clarity.

Based on learnings, the ShapeShift team practices aggressive memoization for all derived values.

.claude/skills/react-best-practices/references/rules/js-hoist-regexp.md (1)

22-35: Clarify the example by removing unused constant or adding escapeRegex definition.

The "correct" example has two clarity issues:

  1. EMAIL_REGEX is defined on line 25 but never used in the Highlighter component. If it's meant to demonstrate module-level hoisting (per line 10's guidance), consider showing it in a separate example or removing it.

  2. escapeRegex(query) on line 29 is called but never defined, which may confuse readers trying to understand the complete pattern.

📚 Suggested improvements for clarity

Option 1: Remove the unused EMAIL_REGEX constant:

-const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
-
 function Highlighter({ text, query }: Props) {
   const regex = useMemo(
-    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
+    () => new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'),
     [query]
   )

Option 2: Use EMAIL_REGEX in a separate example demonstrating module-level hoisting:

+// Module-level hoisting for static patterns
 const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
 
+function EmailValidator({ email }: Props) {
+  const isValid = EMAIL_REGEX.test(email)
+  return <div>{isValid ? '✓' : '✗'}</div>
+}
+
+// useMemo for dynamic patterns
 function Highlighter({ text, query }: Props) {
   const regex = useMemo(
-    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
+    () => new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'),
     [query]
   )
.claude/skills/react-best-practices/references/rules/js-min-max-loop.md (1)

74-82: Consider clarifying the spread operator limitation.

The caveat about spread operators is valid, but could be more specific. The issue isn't just performance—with extremely large arrays (typically 65k-125k+ elements depending on the JS engine), Math.max(...array) can throw a RangeError: Maximum call stack size exceeded because the spread operator passes each element as a separate function argument.

📝 Optional clarification
-This works for small arrays but can be slower for very large arrays due to spread operator limitations. Use the loop approach for reliability.
+This works for small arrays but can fail with a `RangeError: Maximum call stack size exceeded` for extremely large arrays (typically 65k-125k+ elements) because the spread operator passes each element as a separate function argument. Use the loop approach for reliability with arbitrarily large arrays.
.claude/skills/react-best-practices/references/rules/js-combine-iterations.md (1)

8-10: Add guidance on when to apply this optimization.

The rule presents the imperative loop as universally "correct" without discussing trade-offs. For small-to-medium arrays (most use cases), the .filter() approach is more readable and maintainable, and the performance difference is negligible. This optimization should be applied judiciously—primarily for large arrays in hot paths identified through profiling.

Consider adding a note like: "Apply this optimization when profiling shows array iteration is a bottleneck. For most cases, prefer the more readable .filter() approach."

📝 Suggested addition to provide context
 ## Combine Multiple Array Iterations
 
 Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
+
+**When to apply:** Use this optimization for large arrays (1000+ items) in performance-critical code paths. For typical use cases, prefer the more readable `.filter()` approach unless profiling identifies a bottleneck.
 
 **Incorrect (3 iterations):**
.claude/skills/react-best-practices/references/rules/bundle-defer-third-party.md (1)

4-4: Consider more precise terminology for the deferral behavior.

The description "loads after hydration" is slightly imprecise. With { ssr: false }, the component is client-side only and the JavaScript is code-split/loaded asynchronously, but it renders during the client-side render phase (part of hydration) rather than strictly "after" hydration completes.

Consider alternatives like "client-side only", "code-split and loaded asynchronously", or "doesn't block initial bundle" for technical precision.

Also applies to: 10-10

.claude/skills/react-best-practices/references/rules/client-event-listeners.md (2)

38-55: Document callback stability requirement.

Since callback is in the dependency array (line 55), consumers must wrap their callbacks in useCallback to avoid constant re-registration. Without this, the useEffect will run on every render, repeatedly adding/removing the same callback from the Map.

Consider adding a note to the documentation or updating the example to demonstrate this requirement:

📝 Example showing useCallback requirement
function Profile() {
  const handleP = useCallback(() => { 
    console.log('P pressed') 
  }, [])
  
  const handleK = useCallback(() => { 
    console.log('K pressed') 
  }, [])
  
  useKeyboardShortcut('p', handleP)
  useKeyboardShortcut('k', handleK)
}

Or add a documentation note:

**Note:** Callbacks passed to `useKeyboardShortcut` should be wrapped in `useCallback` to prevent unnecessary re-registration.

68-74: Consider showing same-key usage across multiple components to better demonstrate deduplication.

The current example shows different keys ('p' and 'k') sharing the global listener, which is correct. However, showing the same key used across multiple component instances would more clearly demonstrate the core deduplication benefit (N instances registering the same shortcut = 1 event listener + N callbacks in the Map).

💡 Alternative example showing same-key deduplication
function SaveButton() {
  useKeyboardShortcut('s', () => console.log('Save from button'))
  return <button>Save</button>
}

function SaveMenuItem() {
  useKeyboardShortcut('s', () => console.log('Save from menu'))
  return <MenuItem>Save</MenuItem>
}

function Editor() {
  return (
    <>
      <SaveButton />
      <SaveMenuItem />
      {/* Both components register 's', but only ONE global keydown listener exists */}
    </>
  )
}
.claude/skills/react-best-practices/references/rules/server-cache-react.md (1)

14-24: Clarify Server Components context in the example.

The example demonstrates a valid pattern but should explicitly state it's for Server Components. Consider adding a comment or note clarifying where this code should live (e.g., "This function must be defined in a Server Component file or server-side utility module").

📝 Suggested enhancement
 **Usage:**
 
 ```typescript
+// In a Server Component or server-side utility
 import { cache } from 'react'
 
 export const getCurrentUser = cache(async () => {
.claude/skills/react-best-practices/references/rules/server-cache-lru.md (2)

14-33: Improve type safety and code completeness in the example.

The implementation has two issues that reduce its value as a reference:

  1. Line 17: Using any defeats TypeScript's type safety. Consider using a generic type parameter or at least unknown.
  2. Line 26: The db object is undefined. Add an import statement or a comment explaining it's a placeholder.

Based on learnings, gomesalexandre values TypeScript-first approaches and proper type definitions.

📝 Proposed improvements
 **Implementation:**
 
 ```typescript
 import { LRUCache } from 'lru-cache'
+import { db } from './db'  // or: const db = prisma
+
+type User = {
+  id: string
+  name: string
+  // ... other fields
+}
 
-const cache = new LRUCache<string, any>({
+const cache = new LRUCache<string, User>({
   max: 1000,
   ttl: 5 * 60 * 1000  // 5 minutes
 })
 
 export async function getUser(id: string) {
   const cached = cache.get(id)
   if (cached) return cached
 
   const user = await db.user.findUnique({ where: { id } })
   cache.set(id, user)
   return user
 }

Alternatively, if keeping it generic:

-const cache = new LRUCache<string, any>({
+// Replace 'any' with your specific type (e.g., User, Product, etc.)
+const cache = new LRUCache<string, unknown>({
   max: 1000,
   ttl: 5 * 60 * 1000  // 5 minutes
 })

35-35: Consider adding cache invalidation guidance.

The usage guidance is clear, but documentation could benefit from mentioning cache invalidation strategies (e.g., when user data is updated, how to invalidate the cache) and considerations for stale data with the 5-minute TTL.

Example addition:

Use when sequential user actions hit multiple endpoints needing the same data within seconds. In serverless, consider Redis for cross-process caching.

**Important considerations:**
- Implement cache invalidation when data is mutated (e.g., `cache.delete(id)` after user updates)
- Be aware of stale data within the TTL window
- Monitor memory usage in long-running processes
src/components/Layout/Header/Header.tsx (1)

86-95: Hoist static display objects to module scope.

These objects are static; keeping them inside the component recreates them every render. Consider hoisting them alongside the other style constants.

♻️ Suggested refactor
 const searchBoxMaxWSx = { base: 'auto', lg: '400px' }
 const searchBoxMinWSx = { base: 'auto', xl: '300px' }
+const searchBoxDisplay = { base: 'none', '2xl': 'flex', xl: 'none' }
+const iconButtonDisplay = { base: 'flex', '2xl': 'none' }
@@
-  const searchBoxDisplay = {
-    base: 'none',
-    '2xl': 'flex',
-    xl: 'none',
-  }
-
-  const iconButtonDisplay = {
-    base: 'flex',
-    '2xl': 'none',
-  }

As per coding guidelines, prefer hoisting static style objects to avoid per-render allocations.

.claude/skills/react-best-practices/references/rules/js-cache-property-access.md (1)

10-10: Consider clarifying "hot paths" terminology.

The term "hot paths" is developer jargon that may not be immediately clear to all readers. Consider briefly defining it (e.g., "frequently executed code paths") or providing more context.

📝 Suggested clarification
-Cache object property lookups in hot paths.
+Cache object property lookups in hot paths (frequently executed code, such as tight loops).
.claude/skills/react-best-practices/references/rules/async-parallel.md (1)

8-28: Document error handling trade-offs for Promise.all().

The guidance correctly shows parallelization benefits, but omits a critical behavioral difference: with Promise.all(), if any promise rejects, the entire operation fails immediately. Sequential awaits fail at the first error but don't attempt subsequent calls.

Consider adding:

  1. A note about error handling behavior
  2. Recommendation to use Promise.allSettled() when partial results are acceptable
  3. Emphasis that operations must be truly independent (no data dependencies)
📚 Suggested addition
**Error Handling Considerations:**

If any operation can fail independently and you need partial results, use `Promise.allSettled()`:

```typescript
const results = await Promise.allSettled([
  fetchUser(),
  fetchPosts(),
  fetchComments()
])

// Handle each result
results.forEach((result, index) => {
  if (result.status === 'fulfilled') {
    // Use result.value
  } else {
    // Handle result.reason
  }
})

Important: Only use Promise.all() when operations are truly independent with no data dependencies between them.


</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/rendering-svg-precision.md (1)</summary><blockquote>

`8-28`: **Clarify precision trade-offs and provide selection guidance.**

While the document mentions "optimal precision depends on the viewBox size," it doesn't explain how to determine the appropriate precision level. Using `--precision=1` universally may be too aggressive for certain use cases:

- Icons scaled to various sizes may show visual artifacts with precision=1
- Larger viewBox sizes can tolerate lower precision better than smaller ones
- Complex paths benefit from higher precision to maintain curve quality

Consider adding guidance on:
1. How to determine appropriate precision for a given viewBox
2. Visual testing recommendations before/after optimization
3. A more conservative default (e.g., precision=2 or 3) with a note to reduce further if file size is critical



<details>
<summary>📝 Suggested enhancement</summary>

```markdown
**Choosing the Right Precision:**

A good starting point is `precision=2` for most icons. Reduce to `precision=1` only after visual inspection confirms no quality loss. For large or complex SVGs, `precision=3` may be necessary.

Rule of thumb: `precision = Math.ceil(Math.log10(viewBox_size))`

Always visually compare the optimized SVG at various scales before committing.
.claude/skills/react-best-practices/references/rules/js-set-map-lookups.md (1)

8-24: Add context about when this optimization is beneficial.

While the O(1) vs. O(n) complexity is correct, the document doesn't mention when this optimization actually provides measurable benefits. Creating a Set has overhead that may not be worthwhile for small arrays:

  • For arrays with < ~10-20 items, Array.includes() may be faster due to Set creation cost
  • Benefit is most significant when:
    • The array is large (100+ items)
    • Multiple lookups are performed against the same collection
    • Lookups occur in performance-critical paths
  • If converting for a single lookup, the Set creation overhead may negate the benefit

Consider adding a note about when to apply this optimization, or adjust the impact level to reflect that it's only beneficial for specific scenarios.

📝 Suggested addition
**When to Apply:**

Use Set/Map when:
- The collection has 20+ items
- Multiple lookups will be performed
- The lookup is in a hot path (e.g., inside `filter`, `map`, or a loop)

For small collections or one-time lookups, the Set creation overhead may exceed the benefit.
.claude/skills/react-best-practices/references/rules/download_rules.sh (1)

1-63: Consider adding error handling for robustness.

The script will print "Downloaded all rule files successfully!" even if some downloads fail silently due to -s flag. For a developer utility script, this may be acceptable, but consider adding basic error handling.

🔧 Optional: Add fail-fast behavior and download verification
 #!/bin/bash
+set -euo pipefail
+
 BASE_URL="https://raw.githubusercontent.com/vercel-labs/agent-skills/react-best-practices/skills/react-best-practices/references/rules"
 
+download_file() {
+  local file="$1"
+  if ! curl -sf "$BASE_URL/$file" -o "$file"; then
+    echo "Failed to download: $file" >&2
+    return 1
+  fi
+}
+
 # Async rules
-curl -s "$BASE_URL/async-api-routes.md" > async-api-routes.md
+download_file "async-api-routes.md"
 # ... (apply to all files)
.claude/skills/react-best-practices/references/rules/advanced-event-handler-refs.md (1)

1-38: Document useEffectEvent as the preferred modern solution for React 19.2+.

React 19.2 introduced useEffectEvent() specifically to provide stable event handler identity without triggering effect re-subscriptions. The manual ref pattern shown is technically correct but essentially reimplements what useEffectEvent provides natively. Update the document to recommend useEffectEvent as the primary approach while noting the ref pattern for earlier React versions.

📝 Suggested update
 ## Store Event Handlers in Refs
 
 Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
+
+**React 19.2+:** Use `useEffectEvent()` for this pattern—it provides stable event handler identity without the manual ref bookkeeping.

 **Incorrect (re-subscribes on every render):**

Or add a separate section documenting useEffectEvent as the recommended modern approach.

.claude/skills/react-best-practices/references/rules/bundle-barrel-imports.md (2)

40-53: Update Next.js feature status.

The optimizePackageImports feature graduated from experimental status in Next.js 13.5+ and is stable in Next.js 14+. The documentation should reflect the current stable status rather than labeling it as experimental.

📝 Suggested update
-**Alternative (Next.js 13.5+):**
+**Alternative (Next.js 13.5+, stable in 14+):**

 ```js
-// next.config.js - use optimizePackageImports
+// next.config.js
 module.exports = {
-  experimental: {
-    optimizePackageImports: ['lucide-react', '@mui/material']
-  }
+  optimizePackageImports: ['lucide-react', '@mui/material']
 }

</details>

---

`16-38`: **Add version-specific caveat for import paths.**

Direct import paths vary across library versions. For example, `lucide-react` changed its structure in v0.263.0+ (now uses `/icons/` instead of `/dist/esm/icons/`). Consider adding a note advising readers to verify import paths against their installed library version.


<details>
<summary>📝 Suggested addition</summary>

Add after line 38:

```markdown
**Note:** Import paths vary by library version. Always verify the correct path structure for your installed version by checking the library's package.json exports or documentation.
.claude/skills/react-best-practices/references/rules/js-cache-storage.md (2)

21-37: Add deletion helper to maintain cache consistency.

The example provides getLocalStorage and setLocalStorage but omits deleteLocalStorage. Deletion operations will cause cache staleness if the cache isn't invalidated.

➕ Add deletion helper
function deleteLocalStorage(key: string) {
  localStorage.removeItem(key)
  storageCache.delete(key)  // keep cache in sync
}

56-67: Consider adding memory management guidance.

The cache invalidation strategy clears the entire cache on visibility change, which is good. However, the document doesn't mention that caches can grow unbounded in long-lived applications. Consider adding a note about potential memory implications and when to implement size limits (e.g., LRU eviction) for applications with many storage keys.

.claude/skills/react-best-practices/references/rules/async-api-routes.md (1)

38-38: Add context or link for "better-all" reference.

The document references better-all for complex dependency chains but provides no explanation, import path, or link. Readers won't know what this library is or how to use it.

📚 Suggested addition
For operations with more complex dependency chains, use [`better-all`](https://www.npmjs.com/package/better-all) to automatically maximize parallelism (see Dependency-Based Parallelization):

```typescript
import { all } from 'better-all'

const result = await all({
  auth: auth(),
  config: fetchConfig(),
  data: async ({ auth }) => fetchData(auth.user.id)
})

</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/async-suspense-boundaries.md (1)</summary><blockquote>

`8-66`: **LGTM - Excellent Suspense boundary guidance.**

The document provides clear, accurate guidance on strategic Suspense boundaries with well-chosen examples. The "When NOT to use" section is particularly valuable as it helps readers make informed trade-offs between faster initial paint and layout stability.

One optional enhancement: Consider mentioning error boundaries alongside Suspense boundaries, as they often work together (Suspense handles loading states, error boundaries handle error states in streaming scenarios).

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/js-cache-function-results.md (1)</summary><blockquote>

`31-42`: **Consider noting cache eviction for unbounded inputs.**

The module-level cache pattern is correct. For completeness, consider adding a note about cache eviction strategies (e.g., LRU) when the input domain is unbounded, to prevent memory growth in long-running applications. The current example works well for bounded sets like project names.

</blockquote></details>
<details>
<summary>src/pages/Yields/components/YieldOpportunityStats.tsx (1)</summary><blockquote>

`15-15`: **Re-memoize the derived yield aggregates.**  
The active/idle value and APY aggregations now recompute on every render; the reductions are non-trivial and should stay memoized per project performance guidance.

<details>
<summary>♻️ Suggested memoization</summary>

```diff
-import { memo } from 'react'
+import { memo, useMemo } from 'react'
@@
-const activeValueUsd = positions.reduce((acc, position) => {
-  const positionBalances = balances?.[position.id]
-  if (!positionBalances) return acc
-  const activeBalances = positionBalances.filter(b => b.type === 'active' || b.type === 'locked')
-  return activeBalances.reduce((sum, b) => sum.plus(bnOrZero(b.amountUsd)), acc)
-}, bnOrZero(0))
+const activeValueUsd = useMemo(
+  () =>
+    positions.reduce((acc, position) => {
+      const positionBalances = balances?.[position.id]
+      if (!positionBalances) return acc
+      const activeBalances = positionBalances.filter(
+        b => b.type === 'active' || b.type === 'locked',
+      )
+      return activeBalances.reduce((sum, b) => sum.plus(bnOrZero(b.amountUsd)), acc)
+    }, bnOrZero(0)),
+  [positions, balances],
+)
@@
-const idleValueUsd = (() => {
+const idleValueUsd = useMemo(() => {
   if (!isConnected || !allYields) return bnOrZero(0)
@@
-})()
+}, [isConnected, allYields, portfolioBalances])
@@
-const { weightedApy, potentialEarningsValue } = (() => {
+const { weightedApy, potentialEarningsValue } = useMemo(() => {
   if (!isConnected || !yields?.byInputAssetId || !portfolioBalances) {
     return { weightedApy: 0, potentialEarningsValue: bnOrZero(0) }
   }
@@
-})()
+}, [isConnected, yields?.byInputAssetId, portfolioBalances])

As per coding guidelines, ...

Also applies to: 56-105

src/pages/Yields/components/YieldRelatedMarkets.tsx (1)

15-19: Consider adding a displayName for debugging.

The component uses an anonymous function with memo(). While functionally correct, adding a named function or displayName improves React DevTools debugging:

-export const YieldRelatedMarkets = memo(
-  ({ currentYieldId, tokenSymbol }: YieldRelatedMarketsProps) => {
+export const YieldRelatedMarkets = memo(function YieldRelatedMarkets({
+  currentYieldId,
+  tokenSymbol,
+}: YieldRelatedMarketsProps) {
src/pages/Yields/components/YieldInfoCard.tsx (3)

40-48: Consider adding displayName and memoizing computed values.

Per coding guidelines, computed values should use useMemo. The component also uses an anonymous function with memo():

♻️ Suggested improvements
-export const YieldInfoCard = memo(
-  ({ yieldItem, validatorOrProvider, titleOverride }: YieldInfoCardProps) => {
+export const YieldInfoCard = memo(function YieldInfoCard({
+  yieldItem,
+  validatorOrProvider,
+  titleOverride,
+}: YieldInfoCardProps) {
     const translate = useTranslate()

-    const iconSource = resolveYieldInputAssetIcon(yieldItem)
-    const apy = bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2)
-    const yieldTitle = titleOverride ?? getYieldDisplayName(yieldItem)
-    const type = yieldItem.mechanics.type
-    const description = yieldItem.metadata.description
+    const iconSource = useMemo(() => resolveYieldInputAssetIcon(yieldItem), [yieldItem])
+    const apy = useMemo(
+      () => bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2),
+      [yieldItem.rewardRate.total],
+    )
+    const yieldTitle = useMemo(
+      () => titleOverride ?? getYieldDisplayName(yieldItem),
+      [titleOverride, yieldItem],
+    )
+    const type = yieldItem.mechanics.type
+    const description = yieldItem.metadata.description

50-87: Consider memoizing the icon elements.

The assetIcon and stackedIconElement computations include JSX creation and conditional logic. Per coding guidelines, these could benefit from useMemo:

♻️ Suggested improvement
-    const assetIcon = iconSource.assetId ? (
+    const assetIcon = useMemo(() => iconSource.assetId ? (
       <AssetIcon assetId={iconSource.assetId} size='lg' />
     ) : (
       <AssetIcon src={iconSource.src} size='lg' />
-    )
+    ), [iconSource.assetId, iconSource.src])

-    const hasOverlay = validatorOrProvider?.logoURI || yieldItem.chainId
+    const hasOverlay = validatorOrProvider?.logoURI || yieldItem.chainId

-    const stackedIconElement = !hasOverlay ? (
+    const stackedIconElement = useMemo(() => !hasOverlay ? (
       assetIcon
     ) : (
       // ... overlay logic
-    )
+    ), [hasOverlay, assetIcon, validatorOrProvider, yieldItem.chainId])

145-178: Asset icon rendering is duplicated.

Lines 147-151 duplicate the same conditional AssetIcon pattern from lines 50-54. Consider extracting a shared element:

+    const assetIconXs = useMemo(
+      () =>
+        iconSource.assetId ? (
+          <AssetIcon assetId={iconSource.assetId} size='xs' />
+        ) : (
+          <AssetIcon src={iconSource.src} size='xs' />
+        ),
+      [iconSource.assetId, iconSource.src],
+    )

Then use {assetIconXs} in the HStack. This reduces duplication and follows the guideline to memoize JSX elements.

src/pages/Yields/components/YieldProviderInfo.tsx (2)

16-24: Consider using a named function for memo.

Same pattern as other components - adding a named function improves debugging:

-export const YieldProviderInfo = memo(
-  ({ providerId, providerName, providerLogoURI, providerWebsite }: YieldProviderInfoProps) => {
+export const YieldProviderInfo = memo(function YieldProviderInfo({
+  providerId,
+  providerName,
+  providerLogoURI,
+  providerWebsite,
+}: YieldProviderInfoProps) {

The useMemo for description lookup is appropriate.


43-55: Button inside Link creates nested interactive elements.

Wrapping a Button inside a Link creates nested interactive elements which can cause accessibility issues (focusable elements within focusable elements). Consider using Link with button styling or Button with as={Link}:

♻️ Alternative approach
-                {providerWebsite && (
-                  <Link href={providerWebsite} isExternal ml={-2}>
-                    <Button
-                      variant='ghost'
-                      size='sm'
-                      rightIcon={<ExternalLinkIcon />}
-                      color='text.subtle'
-                      _hover={{ color: 'text.base' }}
-                    >
-                      {translate('yieldXYZ.visitWebsite')}
-                    </Button>
-                  </Link>
-                )}
+                {providerWebsite && (
+                  <Button
+                    as={Link}
+                    href={providerWebsite}
+                    isExternal
+                    variant='ghost'
+                    size='sm'
+                    rightIcon={<ExternalLinkIcon />}
+                    color='text.subtle'
+                    _hover={{ color: 'text.base', textDecoration: 'none' }}
+                    ml={-2}
+                  >
+                    {translate('yieldXYZ.visitWebsite')}
+                  </Button>
+                )}

Based on learnings, this can be deferred to a follow-up if preferred.

src/pages/Yields/components/YieldItem.tsx (1)

78-110: Consider memoizing the stats computation for group yields.

The stats IIFE performs array operations (map, reduce, Set) on potentially large yields arrays on every render. While this may be acceptable for small datasets, it could impact performance for groups with many yields.

♻️ Optional: Memoize stats computation
- const stats = (() => {
+ const stats = useMemo(() => {
    if (isSingle) {
      const y = data.yieldItem
      return {
        apy: y.rewardRate.total,
        apyLabel: y.rewardRate.rateType,
        tvlUsd: y.statistics?.tvlUsd ?? '0',
        providers: [{ id: y.providerId, logo: data.providerIcon }],
        chainIds: y.chainId ? [y.chainId] : [],
        count: 1,
        name: y.metadata.name,
        canEnter: y.status.enter,
      }
    }
    const yields = data.yields
    const maxApy = Math.max(0, ...yields.map(y => y.rewardRate.total))
    const totalTvlUsd = yields
      .reduce((acc, y) => acc.plus(bnOrZero(y.statistics?.tvlUsd)), bnOrZero(0))
      .toFixed()
    const providerIds = [...new Set(yields.map(y => y.providerId))]
    const chainIds = [...new Set(yields.map(y => y.chainId).filter(Boolean))] as string[]

    return {
      apy: maxApy,
      apyLabel: 'APY',
      tvlUsd: totalTvlUsd,
      providers: providerIds.map(id => ({ id, logo: yieldProviders?.[id]?.logoURI })),
      chainIds,
      count: yields.length,
      name: data.assetName,
      canEnter: true,
    }
- })()
+ }, [data, isSingle, yieldProviders])
src/pages/Yields/YieldDetail.tsx (1)

157-185: Consider memoizing userBalances computation.

This IIFE performs multiple reduce operations on arrays. While the dependencies are limited, this computation runs on every render. Given that balances, selectedValidatorAddress, and userCurrencyToUsdRate are likely to change together with other state, this may cause unnecessary recalculations.

♻️ Optional: Wrap in useMemo
- const userBalances = (() => {
+ const userBalances = useMemo(() => {
    if (!balances) return { userCurrency: '0', crypto: '0' }
    // ... rest of computation
    return {
      userCurrency: totalUsd.times(userCurrencyToUsdRate).toFixed(),
      crypto: totalCrypto.toFixed(),
    }
- })()
+ }, [balances, selectedValidatorAddress, userCurrencyToUsdRate])

- Fix dangerous fallback to 18 decimals in YieldAvailableToDeposit
- Remove unnecessary Box wrapper in YieldEnterModal
- Simplify nested ternary in YieldDetail using useMemo
- Refactor YieldExplainers with memoized translations
- Convert function keywords to arrow functions in memo components
- Use implicit returns in useMemo/useCallback
- Simplify enum mappings in YieldsList using array pattern
- Remove NEAR_IMPROVEMENTS.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 20

Caution

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

⚠️ Outside diff range comments (1)
src/pages/Yields/YieldDetail.tsx (1)

103-121: Default validator selection skips non-mapped validator yields.

defaultValidator only uses DEFAULT_NATIVE_VALIDATOR_BY_CHAIN_ID. For staking yields that require validator selection but aren’t in that mapping (and lack a validator param), selectedValidatorAddress becomes undefined, which can leave validator info/balances inconsistent with YieldStats (which falls back to validators?.[0]). Consider adding that fallback and moving the selection below the validator fetch.

🐛 Suggested fix
-  const defaultValidator = yieldItem?.chainId
-    ? DEFAULT_NATIVE_VALIDATOR_BY_CHAIN_ID[yieldItem.chainId]
-    : undefined
-
-  const selectedValidatorAddress = (() => {
-    if (
-      yieldId === COSMOS_ATOM_NATIVE_STAKING_YIELD_ID ||
-      yieldId === SOLANA_SOL_NATIVE_MULTIVALIDATOR_STAKING_YIELD_ID ||
-      (yieldId?.includes('solana') && yieldId?.includes('native'))
-    ) {
-      return defaultValidator
-    }
-    return validatorParam || defaultValidator
-  })()
-
-  const isStaking = yieldItem?.mechanics.type === 'staking'
-  const shouldFetchValidators = isStaking && yieldItem?.mechanics.requiresValidatorSelection
-  const { data: validators } = useYieldValidators(yieldItem?.id ?? '', shouldFetchValidators)
+  const isStaking = yieldItem?.mechanics.type === 'staking'
+  const shouldFetchValidators = isStaking && yieldItem?.mechanics.requiresValidatorSelection
+  const { data: validators } = useYieldValidators(yieldItem?.id ?? '', shouldFetchValidators)
+
+  const defaultValidator =
+    (yieldItem?.chainId && DEFAULT_NATIVE_VALIDATOR_BY_CHAIN_ID[yieldItem.chainId]) ??
+    validators?.[0]?.address
+
+  const selectedValidatorAddress = (() => {
+    if (
+      yieldId === COSMOS_ATOM_NATIVE_STAKING_YIELD_ID ||
+      yieldId === SOLANA_SOL_NATIVE_MULTIVALIDATOR_STAKING_YIELD_ID ||
+      (yieldId?.includes('solana') && yieldId?.includes('native'))
+    ) {
+      return defaultValidator
+    }
+    return validatorParam || defaultValidator
+  })()
🤖 Fix all issues with AI agents
In
@.claude/skills/react-best-practices/references/react-performance-guidelines.md:
- Around line 18-115: The TOC anchors in "Table of Contents" (e.g.,
"#1-eliminating-waterfalls", "#11") do not match the generated heading slugs and
bolded lines like "**Impact: CRITICAL**" are being used as pseudo-headings,
triggering MD051 and MD036; update the TOC entries to match actual heading IDs
(or add explicit anchor tags immediately above headings such as <a
name="1-eliminating-waterfalls"></a>) and replace bold pseudo-headings under
sections like "## 1. Eliminating Waterfalls" and "### 1.1 Defer Await Until
Needed" with real heading levels (e.g., "#### Impact: CRITICAL" or "####
Incorrect:" / "#### Correct:") or add a markdownlint disable directive for MD036
at the top of the file if you intentionally want to keep the bold styling;
ensure the anchors and heading text exactly match (case and punctuation) so
MD051 is resolved.

In @.claude/skills/react-best-practices/references/rules/bundle-conditional.md:
- Around line 15-24: The example in AnimationPlayer uses an undefined
setEnabled; update the component signature to accept an onDisable callback prop
(e.g., function onDisable(): void) and call onDisable() inside the import .catch
handler instead of setEnabled(false), and include onDisable in the useEffect
dependency array so the effect is stable; reference the AnimationPlayer
function, the useEffect block, and the .catch handler when making this change.

In
@.claude/skills/react-best-practices/references/rules/bundle-defer-third-party.md:
- Around line 32-37: Replace the Next.js-specific dynamic import pattern using
dynamic(...) and the Analytics symbol with React's lazy + Suspense pattern:
change the import so Analytics is created via React.lazy(() =>
import('@vercel/analytics/react').then(m => ({ default: m.Analytics }))) and
render it wrapped in a Suspense fallback when used; optionally use the project's
makeSuspenseful utility (makeSuspenseful in src/utils/makeSuspenseful.tsx)
instead of raw Suspense for consistency. Ensure you remove dynamic(...) usage
and update all render sites to import Suspense and render <Analytics /> inside
the fallback wrapper.

In @.claude/skills/react-best-practices/references/rules/bundle-preload.md:
- Line 50: Update the sentence that claims "The `typeof window !== 'undefined'`
check prevents bundling preloaded modules for SSR" to clarify runtime vs
build-time: explain that the `typeof window !== 'undefined'` guard prevents
server-side execution at runtime but does not stop the module from being
included in the overall bundle; instead, modern bundlers will emit it as a
client-only chunk that bypasses server-side loading, which reduces server bundle
size and can speed builds. Keep reference to the exact symbol `typeof window !==
'undefined'` and reword the paragraph to state that it prevents server execution
while resulting in a client chunk rather than implying it prevents bundling.

In
@.claude/skills/react-best-practices/references/rules/client-event-listeners.md:
- Around line 57-66: The useSWRSubscription call is missing a closing
parenthesis; add the missing ')' after the subscription callback so the
invocation of useSWRSubscription('global-keydown', () => { ... }) is properly
closed; locate the useSWRSubscription(...) block that defines handler (the
KeyboardEvent listener referencing keyCallbacks and keyCallbacks.get) and ensure
the returned cleanup arrow function and the outer callback are closed and then
append the final ')' to close the useSWRSubscription call.
- Around line 38-55: The documentation's useKeyboardShortcut example and text
must state that the callback passed to useKeyboardShortcut needs to be memoized
to avoid effect thrashing: update the docs around the useKeyboardShortcut
function and the usage example to either wrap event handlers in React's
useCallback (e.g., show Profile using handleP/useCallback and
handleK/useCallback) or add a clear note immediately after the
useKeyboardShortcut code block explaining "callbacks passed to
useKeyboardShortcut must be memoized (e.g., via useCallback) to prevent constant
registration/unregistration." Ensure you reference the useKeyboardShortcut
function and the callback dependency when making the change.

In @.claude/skills/react-best-practices/references/rules/client-swr-dedup.md:
- Around line 45-54: The example uses useSWRMutation('/api/user', updateUser)
but never defines updateUser; add a concise description of the expected mutation
function signature and behavior (what params it receives and that it should
perform the API call and return the result) and include a short example
implementation for updateUser referenced by useSWRMutation so readers can see
the proper pattern for mutations used by trigger. Reference the updateUser
function, useSWRMutation hook, and trigger callback in the explanation.
- Around line 35-43: Add documentation above the snippet clarifying what
useImmutableSWR does (e.g., how it differs from useSWR, that it disables
revalidation/refresh for truly immutable data), when to use it versus standard
useSWR, and what "immutable" means in this context (data that never changes
after initial fetch); also ensure fetcher is defined or imported and mention its
expected shape (a function that takes a URL and returns parsed data). Update the
example block to include a one-line comment like "useImmutableSWR disables
revalidation for data that never changes" and either import or reference a
defined fetcher so readers know how to supply it.
- Around line 27-33: The example uses useSWR('/api/users', fetcher) but never
defines or explains fetcher; add a short definition or explanatory note for
fetcher used by useSWR (e.g., a function that accepts a URL and returns parsed
JSON) and update the UserList example to reference that defined fetcher so
readers know what to pass as the second argument to useSWR (mention the symbol
fetcher and the component UserList in the change).

In @.claude/skills/react-best-practices/references/rules/download_rules.sh:
- Around line 1-63: The script download_rules.sh currently uses silent curl (-s)
for every download and always prints "Downloaded all rule files successfully!"
even on failures; update the script to fail fast by enabling strict mode (set
-euo pipefail), replace curl -s with curl --fail --silent --show-error and add
retries (e.g., --retry and --retry-connrefused) for each download command that
references BASE_URL/*, and ensure each curl failure stops the script before the
final echo (or check exit status of each curl and exit non-zero) so the success
message is only printed when all downloads actually succeed.

In
@.claude/skills/react-best-practices/references/rules/js-combine-iterations.md:
- Around line 8-18: Update the guidance in the "Combine Multiple Array
Iterations" rule to reframe it as a context-dependent optimization rather than
labeling `.filter()` usage as outright "Incorrect": change the "Incorrect"
heading to "Readable but potentially inefficient" and change the "Correct"
heading to "Optimized for large arrays", add a short note recommending the
mutation/single-loop approach only when profiling shows a bottleneck (e.g., >10k
items or hot render paths), explicitly call out the React tradeoff around
referential equality and memoization (mention dependency arrays/memo/useEffect),
and advise preferring TypeScript-first, declarative `.filter()` for readability
and avoiding `let` assignments unless proven necessary.

In @.claude/skills/react-best-practices/references/rules/js-hoist-regexp.md:
- Around line 25-35: The example is missing the escapeRegex helper and includes
an unused EMAIL_REGEX that will confuse readers; add a small escapeRegex utility
(referenced by Highlighter and the useMemo call creating regex) so
escapeRegex(query) is defined and used, and either remove or move EMAIL_REGEX
into a separate, self-contained example (or comment that it’s unrelated) to
avoid confusion—ensure the Highlighter example references only escapeRegex and
keep EMAIL_REGEX shown elsewhere with a clear label.

In @.claude/skills/react-best-practices/references/rules/rendering-hoist-jsx.md:
- Around line 28-42: The examples reference an undefined loading variable;
update the examples to define or obtain loading (e.g., declare a local const
loading = true/false, accept loading as a prop on Container, or derive it from a
hook like useIsFetching) so the Container and loadingSkeleton examples are
executable; modify the Container function (and any usage of loadingSkeleton) to
use that defined loading value.
- Around line 12-26: Update the "Incorrect" example to demonstrate JSX being
recreated inside the component body rather than using a separate component
function; replace or remove the LoadingSkeleton component and instead show
Container defining a local JSX value (e.g. const loadingSkeleton = <div
className="animate-pulse h-20 bg-gray-200" />) and using {loading &&
loadingSkeleton} so the example clearly illustrates recreation on every render
(refer to the LoadingSkeleton and Container examples and the loadingSkeleton
variable to locate the code to change).

In
@.claude/skills/react-best-practices/references/rules/rerender-defer-reads.md:
- Around line 8-10: Update the "Defer State Reads to Usage Point" guidance to
include critical context clarifying when to apply the pattern: state reads
(e.g., searchParams, localStorage) should only be deferred if the component's UI
does not need to react to changes (purely event-driven reads), the code runs in
a client-side context (not SSR or RSC), and bypassing the framework routing
system is acceptable; explicitly call out these three constraints and warn that
components needing reactive updates should not use this optimization.

In
@.claude/skills/react-best-practices/references/rules/rerender-dependencies.md:
- Around line 38-44: The example only enables mobile mode when isMobile becomes
true but never disables it when isMobile becomes false; update the useEffect to
handle both transitions by calling the appropriate API to set mobile mode to the
current state (e.g., replace enableMobileMode() with a call that accepts the
boolean or call setMobileMode(isMobile)), ensuring the effect uses isMobile in
its dependency array so toggling width enables and disables mobile mode
correctly in the useEffect associated with isMobile.

In @.claude/skills/react-best-practices/references/rules/server-cache-react.md:
- Around line 1-26: Add a short "Requirements" note clarifying that
React.cache() (the cache import from 'react' used in getCurrentUser) requires
React 19+ and only works in Server Components; explicitly state it will not work
in Client Components or with React 18 to prevent runtime errors and confusion,
and place this note directly after the title/metadata so readers see
compatibility constraints before the usage example.

In @.claude/skills/react-best-practices/SKILL.md:
- Around line 113-115: Update the heading and occurrences to hyphenate
"data-fetching": change "### 4. Client-Side Data Fetching (MEDIUM-HIGH)" to "###
4. Client-Side Data‑Fetching (MEDIUM-HIGH)" and replace the inline "data
fetching" phrase in the list item ("Automatic deduplication and efficient data
fetching patterns.") with "data‑fetching" to ensure consistent hyphenation;
locate these strings by searching for the exact heading "Client-Side Data
Fetching" and the sentence "Automatic deduplication and efficient data fetching
patterns." and apply the hyphenated form.

In `@src/lib/yieldxyz/providerDescriptions.ts`:
- Around line 1-46: PROVIDER_DESCRIPTIONS currently hardcodes user-facing
strings; move each provider description into the translation files under the
yieldXYZ namespace (e.g. keys like "yieldXYZ.providers.morpho.description"),
replace the description fields in PROVIDER_DESCRIPTIONS with a descriptionKey
(e.g. "yieldXYZ.providers.morpho.description"), and update the consumer
(YieldProviderInfo.tsx) to call useTranslate().t(descriptionKey) (or
t(descriptionKey)) when rendering the description; ensure keys match between the
translation JSON and the descriptionKey values and keep provider ids (morpho,
morpho-aave, lido, etc.) unchanged.

In `@src/pages/Yields/components/YieldPositionCard.tsx`:
- Around line 131-136: The claimable/pending UI is rendered whenever
claimableBalance exists even if its amount is zero; update the guard used for
rendering (currently hasClaimable) to check the aggregated amount or canClaim
instead of mere presence. Specifically, change hasClaimable to verify
bnOrZero(claimableBalance?.aggregatedAmount).gt(0) or use
claimableBalance?.canClaim (whichever semantic fits) and then use that updated
boolean where the pending/claimable section is rendered so zero-amount
claimables do not show.
🧹 Nitpick comments (30)
src/components/Layout/Header/NavBar/NavigationDropdown.tsx (1)

140-153: LGTM - Badge implementation is clean.

The HStack wrapper with conditional badge rendering follows Chakra patterns well and uses proper translation for the "New" label. The badge styling is consistent with the design system.

Minor optional consideration: The HStack is now always rendered even when isNew is false. If you want to avoid the extra DOM node, you could conditionally wrap:

♻️ Optional: Avoid HStack when no badge
-              <HStack spacing={2}>
-                <Text>{translate(item.label)}</Text>
-                {item.isNew && (
-                  <Badge
-                    colorScheme='blue'
-                    fontSize='2xs'
-                    variant='solid'
-                    borderRadius='full'
-                    px={1.5}
-                  >
-                    {translate('common.new')}
-                  </Badge>
-                )}
-              </HStack>
+              {item.isNew ? (
+                <HStack spacing={2}>
+                  <Text>{translate(item.label)}</Text>
+                  <Badge
+                    colorScheme='blue'
+                    fontSize='2xs'
+                    variant='solid'
+                    borderRadius='full'
+                    px={1.5}
+                  >
+                    {translate('common.new')}
+                  </Badge>
+                </HStack>
+              ) : (
+                <Text>{translate(item.label)}</Text>
+              )}
src/components/Layout/Header/Header.tsx (1)

86-95: Style objects recreated on every render.

Per project coding guidelines on aggressive memoization, these style objects should be memoized or moved outside the component as constants since they have no dependencies.

♻️ Suggested fix: Move to module scope
+const searchBoxDisplay = {
+  base: 'none',
+  '2xl': 'flex',
+  xl: 'none',
+}
+
+const iconButtonDisplay = {
+  base: 'flex',
+  '2xl': 'none',
+}
+
 export const Header = memo(() => {
   // ...
-  const searchBoxDisplay = {
-    base: 'none',
-    '2xl': 'flex',
-    xl: 'none',
-  }
-
-  const iconButtonDisplay = {
-    base: 'flex',
-    '2xl': 'none',
-  }
.claude/skills/react-best-practices/references/rules/js-early-exit.md (1)

14-50: Align the semantic behavior of both examples to clarify the teaching point.

The "incorrect" and "correct" examples have different return semantics beyond just efficiency:

  • Incorrect version (lines 14-32): Overwrites errorMessage on each validation failure, ultimately returning the last error encountered across all users.
  • Correct version (lines 37-49): Returns immediately with the first error encountered.

This semantic difference (last error vs. first error) obscures the primary teaching point about unnecessary computation. Consider revising the incorrect example to accumulate the first error without overwriting, so both versions report the same error—then the only difference is efficiency.

📝 Suggested revision to align semantics

Modify the incorrect example to capture only the first error without overwriting:

 function validateUsers(users: User[]) {
   let hasError = false
   let errorMessage = ''
   
   for (const user of users) {
     if (!user.email) {
       hasError = true
-      errorMessage = 'Email required'
+      if (!errorMessage) errorMessage = 'Email required'
     }
     if (!user.name) {
       hasError = true
-      errorMessage = 'Name required'
+      if (!errorMessage) errorMessage = 'Name required'
     }
     // Continues checking all users even after error found
   }
   
   return hasError ? { valid: false, error: errorMessage } : { valid: true }
 }

Alternatively, add a note in the documentation acknowledging that the incorrect version reports the last error while the correct version reports the first error, making it clear that the behavior differs in more ways than just performance.

.claude/skills/react-best-practices/references/rules/js-hoist-regexp.md (1)

37-43: Make the global regex warning more actionable.

The warning accurately documents the mutable lastIndex issue, but doesn't guide developers on how to handle it. Consider adding remediation options.

💡 Suggested addition
 **Warning:** Global regex (`/g`) has mutable `lastIndex` state:
 
 ```typescript
 const regex = /foo/g
 regex.test('foo')  // true, lastIndex = 3
 regex.test('foo')  // false, lastIndex = 0

+Solutions:
+
+typescript +// Option 1: Omit /g flag when not needed +const regex = /foo/i // stateless + +// Option 2: Reset lastIndex before each use +const regex = /foo/g +regex.lastIndex = 0 +regex.test('foo') // predictable + +// Option 3: Create a new regex for each use +const regex = useMemo(() => new RegExp('foo', 'g'), []) +


</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/client-event-listeners.md (1)</summary><blockquote>

`58-62`: **Add error isolation when dispatching callbacks.**

Line 60 dispatches all callbacks without error handling. If one callback throws, subsequent callbacks in the Set won't execute.


<details>
<summary>🛡️ Proposed error isolation</summary>

```diff
     const handler = (e: KeyboardEvent) => {
       if (e.metaKey && keyCallbacks.has(e.key)) {
-        keyCallbacks.get(e.key)!.forEach(cb => cb())
+        keyCallbacks.get(e.key)!.forEach(cb => {
+          try {
+            cb()
+          } catch (error) {
+            console.error('Keyboard shortcut callback failed:', error)
+          }
+        })
       }
     }
.claude/skills/react-best-practices/references/rules/js-tosorted-immutable.md (1)

14-23: Consider clarifying the mutation timing in the "incorrect" example.

The example correctly demonstrates the problem, but readers might wonder "when does the mutation happen?" since it's inside useMemo. Adding a brief note that the mutation occurs each time the dependency changes (i.e., whenever users reference changes) would make the issue clearer.

📝 Suggested clarification
 **Incorrect (mutates original array):**
 
 ```typescript
 function UserList({ users }: { users: User[] }) {
-  // Mutates the users prop array!
+  // Mutates the users prop array whenever the dependency changes!
+  // This violates React's immutability contract
   const sorted = useMemo(
     () => users.sort((a, b) => a.name.localeCompare(b.name)),
     [users]
   )
   return <div>{sorted.map(renderUser)}</div>
 }
</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/rerender-defer-reads.md (1)</summary><blockquote>

`5-5`: **Consider adding localStorage example or removing from tags.**

The tags include `localStorage` but the document only demonstrates the pattern with `searchParams`. For completeness, consider either:
1. Adding a localStorage example showing the same pattern (reading in event handler vs subscribing to storage events)
2. Removing localStorage from the tags if out of scope



<details>
<summary>💡 Example localStorage addition</summary>

```markdown
**Incorrect (subscribes to all localStorage changes):**

```tsx
function SaveButton({ data }: { data: Data }) {
  const [savedCount, setSavedCount] = useState(() => 
    Number(localStorage.getItem('saveCount') || '0')
  )
  
  useEffect(() => {
    const handleStorageChange = () => {
      setSavedCount(Number(localStorage.getItem('saveCount') || '0'))
    }
    window.addEventListener('storage', handleStorageChange)
    return () => window.removeEventListener('storage', handleStorageChange)
  }, [])

  const handleSave = () => {
    save(data)
    localStorage.setItem('saveCount', String(savedCount + 1))
  }

  return <button onClick={handleSave}>Save</button>
}

Correct (reads on demand, no subscription):

function SaveButton({ data }: { data: Data }) {
  const handleSave = () => {
    const currentCount = Number(localStorage.getItem('saveCount') || '0')
    save(data)
    localStorage.setItem('saveCount', String(currentCount + 1))
  }

  return <button onClick={handleSave}>Save</button>
}
</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/rendering-svg-precision.md (2)</summary><blockquote>

`8-10`: **Add guidance on visual trade-offs and precision selection.**

The introduction correctly notes that optimal precision depends on viewBox size but doesn't explain the relationship or warn about visual impact. Consider adding:
- How to choose precision based on viewBox dimensions (e.g., viewBox 0-100 vs 0-1000)
- Warning that reducing precision changes the visual output
- When to avoid aggressive reduction (intricate paths, icons requiring pixel-perfect rendering at specific sizes)



<details>
<summary>📝 Suggested enhancement</summary>

```diff
 ## Optimize SVG Precision
 
-Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
+Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size—larger viewBoxes (e.g., 0-1000) tolerate lower precision than smaller ones (e.g., 0-100).
+
+**Important:** Reducing precision alters coordinates and may change the visual output. Test the result to ensure the change is imperceptible. Avoid aggressive reduction for intricate paths or icons that must be pixel-perfect at specific sizes.

12-22: Consider more nuanced framing of precision examples.

The labels "Incorrect (excessive precision)" vs "Correct (1 decimal place)" are too absolute. High precision isn't incorrect—it's just inefficient for most use cases. Additionally, presenting 1 decimal place as universally "correct" without context may mislead readers.

📝 Suggested refinement
-**Incorrect (excessive precision):**
+**Before (excessive precision for most use cases):**
 
 ```svg
 <path d="M 10.293847 20.847362 L 30.938472 40.192837" />

-Correct (1 decimal place):
+After (optimized to 1 decimal place):

<path d="M 10.3 20.8 L 30.9 40.2" />

+This reduction is typically imperceptible but saves bytes. Adjust precision based on viewBox scale and visual requirements.

</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/js-batch-dom-css.md (1)</summary><blockquote>

`26-39`: **Mixed CSS and JavaScript syntax in single code block.**

The code block at lines 27-38 combines CSS (`.highlighted-box`) and JavaScript (`function updateElementStyles`) under a TypeScript identifier. This may cause syntax highlighting issues in some editors/viewers. Consider splitting into two separate blocks.



<details>
<summary>Suggested split</summary>

```diff
-```typescript
-// CSS file
-.highlighted-box {
-  width: 100px;
-  height: 200px;
-  background-color: blue;
-  border: 1px solid black;
-}
-
-// JavaScript
-function updateElementStyles(element: HTMLElement) {
-  element.classList.add('highlighted-box')
-}
-```
+```css
+/* styles.css */
+.highlighted-box {
+  width: 100px;
+  height: 200px;
+  background-color: blue;
+  border: 1px solid black;
+}
+```
+
+```typescript
+function updateElementStyles(element: HTMLElement) {
+  element.classList.add('highlighted-box')
+}
+```
.claude/skills/react-best-practices/references/rules/bundle-dynamic-imports.md (1)

24-35: Consider adding React.lazy alternative for non-Next.js contexts.

The example uses next/dynamic which is Next.js specific. Per learnings, the project also uses React.lazy for code splitting. Consider documenting both patterns or clarifying when each applies.

Add React.lazy alternative
 const MonacoEditor = dynamic(
   () => import('./monaco-editor').then(m => m.MonacoEditor),
   { ssr: false }
 )

 function CodePanel({ code }: { code: string }) {
   return <MonacoEditor value={code} />
 }

+Alternative (React.lazy with Suspense):
+
+```tsx
+import { lazy, Suspense } from 'react'
+
+const MonacoEditor = lazy(() =>

  • import('./monaco-editor').then(m => ({ default: m.MonacoEditor }))
    +)

+function CodePanel({ code }: { code: string }) {

  • return (
  • <Suspense fallback={
    Loading editor...
    }>
  •  <MonacoEditor value={code} />
    
  • )
    +}
    +```
</details>

</blockquote></details>
<details>
<summary>.claude/skills/react-best-practices/references/rules/rendering-content-visibility.md (1)</summary><blockquote>

`38-38`: **Consider adding guidance on when to prefer virtualization.**

Per project learnings, virtualization is recommended for lists with 100+ items. `content-visibility` is a lighter-weight alternative but doesn't reduce DOM node count. Consider adding a note clarifying when to use each approach—e.g., virtualization for very large lists where memory/DOM size matters, `content-visibility` for moderate lists where simpler implementation is preferred.

</blockquote></details>
<details>
<summary>src/pages/Yields/components/YieldExplainers.tsx (2)</summary><blockquote>

`99-110`: **Consider using a stable key instead of array index.**

Using `index` as the key works but can cause issues if the explainers list changes dynamically. Since `textKey` is unique per explainer item, it would be a more stable key.


<details>
<summary>Suggested improvement</summary>

```diff
-        <HStack key={index} spacing={3} align='flex-start'>
+        <HStack key={explainer.textKey} spacing={3} align='flex-start'>

19-72: Create a type guard for mechanics type validation.

Since mechanics.type is defined as string rather than a discriminated union, TypeScript won't enforce exhaustiveness checks if new types are added. Consider creating a type guard function to document and validate the supported mechanics types at runtime, helping prevent silent failures if new types are introduced:

const isSupportedMechanicsType = (type: string): type is 'liquid-staking' | 'native-staking' | 'pooled-staking' | 'staking' | 'restaking' | 'vault' | 'lending' => {
  return ['liquid-staking', 'native-staking', 'pooled-staking', 'staking', 'restaking', 'vault', 'lending'].includes(type)
}

This approach makes the supported types explicit and enables better IDE support for refactoring.

.claude/skills/react-best-practices/references/rules/async-defer-await.md (1)

62-78: Documentation is well-structured but consider noting the trade-off.

The "correct" example defers fetchPermissions until after checking if the resource exists, which is good. However, it's worth noting that the optimal order depends on which check is cheaper/more likely to short-circuit. In this example, if most resources exist but few users have edit permissions, fetching permissions first might be better.

Consider adding a brief note about evaluating which check to perform first based on:

  1. Cost of each operation
  2. Likelihood of each condition failing

This is optional since the core pattern is correctly demonstrated.

.claude/skills/react-best-practices/references/rules/rerender-transitions.md (1)

14-23: Consider clarifying the "blocking" terminology.

The "incorrect" example doesn't technically "block" the UI (the event handler returns immediately). Rather, it triggers synchronous high-priority re-renders on every scroll event, which can cause jank in complex UIs. Consider rephrasing to "causes frequent synchronous re-renders" for technical accuracy.

Also, both examples are missing the useState import for completeness.

.claude/skills/react-best-practices/references/rules/js-cache-property-access.md (3)

14-18: Consider modern loop patterns to align with project conventions.

The example uses let i in a traditional for loop, which conflicts with the project's preference to avoid let assignments. Consider demonstrating with a for...of loop or array methods:

for (const item of arr) {
  process(obj.config.settings.value)
}

This maintains readability while following project conventions.

Based on learnings, the project prefers avoiding let variable assignments.


12-12: Clarify the "3 lookups" claim.

The statement "3 lookups × N iterations" may be misleading—each iteration performs one property chain traversal (obj.config.settings.value), not three separate lookups. While the chain involves three property accesses internally, modern JS engines optimize this. Consider rephrasing to "N property chain traversals" for accuracy.


23-27: Apply consistent loop pattern in the correct example.

For consistency with the suggestion above, consider using for...of here as well:

const value = obj.config.settings.value
for (const _ of arr) {
  process(value)
}

This demonstrates the caching benefit without the let declaration.

.claude/skills/react-best-practices/references/rules/async-api-routes.md (1)

38-38: Consider adding context for the better-all tool reference.

The reference to "Dependency-Based Parallelization" and the better-all tool lacks context. Consider either:

  1. Adding a link or brief explanation of what better-all is
  2. Including this information in the referenced section
  3. Providing a code example showing its usage

This will help readers evaluate whether they need this additional tool.

.claude/skills/react-best-practices/references/rules/rendering-hydration-no-flicker.md (1)

80-82: Document React version compatibility and modern alternatives.

Consider adding a note that:

  1. This inline script pattern may produce hydration warnings in React 18+
  2. React 19 offers better alternatives (streaming SSR with Suspense, use client directive)
  3. This pattern is primarily useful for legacy React applications

This helps readers make informed decisions based on their React version.

.claude/skills/react-best-practices/references/rules/advanced-event-handler-refs.md (1)

8-38: Consider documenting useEffectEvent as the modern solution.

The ref pattern shown is correct, but React 19.2 introduced useEffectEvent specifically for this use case. Consider adding it as the preferred approach with the ref pattern as a fallback for older versions:

// Modern approach (React 19.2+)
function useWindowEvent(event: string, handler: () => void) {
  const stableHandler = useEffectEvent(handler)
  
  useEffect(() => {
    window.addEventListener(event, stableHandler)
    return () => window.removeEventListener(event, stableHandler)
  }, [event])
}

The current ref pattern remains valid as a fallback for projects not yet on React 19.2.

Based on React 19 documentation showing useEffectEvent is now stable.

.claude/skills/react-best-practices/references/rules/rendering-animate-svg-wrapper.md (1)

14-45: Documentation assumes Tailwind CSS usage.

The examples use className="animate-spin", which is a Tailwind CSS utility class. If the project doesn't use Tailwind CSS, consider adding a note or providing a vanilla CSS alternative for the animation.

📝 Optional: Add vanilla CSS alternative

You could add a note like:

**Note:** The examples use Tailwind CSS (`animate-spin`). If not using Tailwind, add this CSS:

```css
`@keyframes` spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.animate-spin {
  animation: spin 1s linear infinite;
}
.claude/skills/react-best-practices/references/rules/rerender-lazy-state-init.md (1)

37-43: Consider clarifying that lazy initializers don't track prop changes.

The FilteredList example uses items prop in the lazy initializer, but useState only runs this once. If items changes after mount, searchIndex won't update. This could mislead readers into thinking lazy initialization handles prop-derived state.

Consider either:

  1. Adding a note that this pattern is for truly static initial values, or
  2. Using an example without prop dependencies (like the localStorage one)
src/lib/yieldxyz/getYieldDisplayName.test.ts (1)

6-11: Add an explicit return type for mockYield.

This keeps the test helper aligned with the project’s TS typing rules and avoids implicit return types. As per coding guidelines, explicit return types are required in TypeScript helpers.

♻️ Proposed change
-const mockYield = (providerId: string, tokenSymbol: string, metadataName: string) =>
+const mockYield = (providerId: string, tokenSymbol: string, metadataName: string): AugmentedYieldDto =>
   ({
     providerId,
     token: { symbol: tokenSymbol },
     metadata: { name: metadataName },
   }) as AugmentedYieldDto
src/pages/Yields/components/YieldItem.tsx (1)

78-110: Consider memoizing the stats computation.

The stats object is computed on every render via an IIFE. Since this involves array operations (map, reduce, filter) for group yields and creates a new object each time, it could cause unnecessary re-renders of child components. Given the team's aggressive memoization practice, wrapping this in useMemo with [data, yieldProviders] dependencies would be more consistent with codebase conventions.

♻️ Suggested refactor
- const stats = (() => {
+ const stats = useMemo(() => {
    if (isSingle) {
      const y = data.yieldItem
      return {
        apy: y.rewardRate.total,
        apyLabel: y.rewardRate.rateType,
        tvlUsd: y.statistics?.tvlUsd ?? '0',
        providers: [{ id: y.providerId, logo: data.providerIcon }],
        chainIds: y.chainId ? [y.chainId] : [],
        count: 1,
        name: y.metadata.name,
        canEnter: y.status.enter,
      }
    }
    const yields = data.yields
    const maxApy = Math.max(0, ...yields.map(y => y.rewardRate.total))
    const totalTvlUsd = yields
      .reduce((acc, y) => acc.plus(bnOrZero(y.statistics?.tvlUsd)), bnOrZero(0))
      .toFixed()
    const providerIds = [...new Set(yields.map(y => y.providerId))]
    const chainIds = [...new Set(yields.map(y => y.chainId).filter(Boolean))] as string[]

    return {
      apy: maxApy,
      apyLabel: 'APY',
      tvlUsd: totalTvlUsd,
      providers: providerIds.map(id => ({ id, logo: yieldProviders?.[id]?.logoURI })),
      chainIds,
      count: yields.length,
      name: data.assetName,
      canEnter: true,
    }
- })()
+ }, [data, isSingle, yieldProviders])
src/pages/Yields/components/YieldStats.tsx (1)

47-66: Consider memoizing validator lookups for consistency.

The selectedValidatorAddress and selectedValidator computations involve array find operations that run on every render. While the performance impact is likely minimal for a single-instance component, memoizing these would align with the codebase's aggressive memoization conventions.

♻️ Optional refactor for consistency
+ import { memo, useMemo } from 'react'
- import { memo } from 'react'

- const selectedValidatorAddress = (() => {
+ const selectedValidatorAddress = useMemo(() => {
    if (
      yieldItem.id === COSMOS_ATOM_NATIVE_STAKING_YIELD_ID ||
      yieldItem.id === SOLANA_SOL_NATIVE_MULTIVALIDATOR_STAKING_YIELD_ID ||
      (yieldItem.id.includes('solana') && yieldItem.id.includes('native'))
    ) {
      return defaultValidator
    }
    return validatorParam || defaultValidator
- })()
+ }, [yieldItem.id, defaultValidator, validatorParam])

- const selectedValidator = (() => {
+ const selectedValidator = useMemo(() => {
    if (!selectedValidatorAddress) return undefined
    const inList = validators?.find(v => v.address === selectedValidatorAddress)
    if (inList) return inList
    const inBalances = balances?.raw.find(b => b.validator?.address === selectedValidatorAddress)
      ?.validator
    if (inBalances) return inBalances
    return undefined
- })()
+ }, [selectedValidatorAddress, validators, balances?.raw])
src/pages/Yields/components/YieldOpportunityStats.tsx (1)

56-123: Memoize derived totals to avoid repeated reductions each render.

activeValueUsd, idleValueUsd, and weighted APY/earnings calculations iterate over arrays and rebuild Sets on every render. Wrapping them in useMemo (and reusing the memoized results for formatted strings) keeps renders cheap and aligns with the repo’s memoization policy. Also consider renaming idleValueUsd to reflect user-currency semantics to avoid confusion.

♻️ Proposed refactor
-import { memo } from 'react'
+import { memo, useMemo } from 'react'
...
-  const activeValueUsd = positions.reduce((acc, position) => {
-    const positionBalances = balances?.[position.id]
-    if (!positionBalances) return acc
-    const activeBalances = positionBalances.filter(b => b.type === 'active' || b.type === 'locked')
-    return activeBalances.reduce((sum, b) => sum.plus(bnOrZero(b.amountUsd)), acc)
-  }, bnOrZero(0))
+  const activeValueUsd = useMemo(() => {
+    return positions.reduce((acc, position) => {
+      const positionBalances = balances?.[position.id]
+      if (!positionBalances) return acc
+      const activeBalances = positionBalances.filter(b => b.type === 'active' || b.type === 'locked')
+      return activeBalances.reduce((sum, b) => sum.plus(bnOrZero(b.amountUsd)), acc)
+    }, bnOrZero(0))
+  }, [positions, balances])
...
-  const idleValueUsd = (() => {
+  const idleValueUsd = useMemo(() => {
     if (!isConnected || !allYields) return bnOrZero(0)
     ...
-  })()
+  }, [isConnected, allYields, portfolioBalances])
...
-  const { weightedApy, potentialEarningsValue } = (() => {
+  const { weightedApy, potentialEarningsValue } = useMemo(() => {
     if (!isConnected || !yields?.byInputAssetId || !portfolioBalances) {
       return { weightedApy: 0, potentialEarningsValue: bnOrZero(0) }
     }
     ...
-  })()
+  }, [isConnected, yields?.byInputAssetId, portfolioBalances])
As per coding guidelines, memoize derived values and object creations.
src/pages/Yields/components/YieldInfoCard.tsx (1)

44-87: Memoize derived display values (icon source, APY, title).

These values are deterministic from props and currently recompute each render, including JSX element creation. Memoizing them keeps render work stable and matches the project’s memoization guidance.

♻️ Proposed refactor
-import { memo } from 'react'
+import { memo, useMemo } from 'react'
...
-    const iconSource = resolveYieldInputAssetIcon(yieldItem)
-    const apy = bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2)
-    const yieldTitle = titleOverride ?? getYieldDisplayName(yieldItem)
+    const iconSource = useMemo(() => resolveYieldInputAssetIcon(yieldItem), [yieldItem])
+    const apy = useMemo(
+      () => bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2),
+      [yieldItem.rewardRate.total],
+    )
+    const yieldTitle = useMemo(
+      () => titleOverride ?? getYieldDisplayName(yieldItem),
+      [titleOverride, yieldItem],
+    )
As per coding guidelines, memoize derived values and object creations.
src/pages/Yields/components/YieldHero.tsx (1)

56-85: Memoize derived values for render stability.

Icon source, APY, and balance-derived flags are recomputed every render. Memoizing them keeps rendering cheaper and consistent with the repo’s aggressive memoization guidance.

♻️ Proposed refactor
-  const iconSource = resolveYieldInputAssetIcon(yieldItem)
-  const apy = bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2)
-  const hasExitBalance = bnOrZero(userBalanceCrypto).gt(0)
+  const { iconSource, apy, hasExitBalance } = useMemo(() => {
+    return {
+      iconSource: resolveYieldInputAssetIcon(yieldItem),
+      apy: bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2),
+      hasExitBalance: bnOrZero(userBalanceCrypto).gt(0),
+    }
+  }, [yieldItem, userBalanceCrypto])
As per coding guidelines, memoize derived values and object creations.

gomesalexandre and others added 2 commits January 16, 2026 16:58
Fixes potential runtime error when allBalances[id] is undefined,
which would cause .minus() to throw or produce NaN.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Check aggregatedAmount > 0 for hasClaimable, consistent with
hasWithdrawable check, to avoid showing empty claimable UI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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