Skip to content

chore: bump node version#152

Merged
antoncoding merged 13 commits intomasterfrom
opt/version
Aug 20, 2025
Merged

chore: bump node version#152
antoncoding merged 13 commits intomasterfrom
opt/version

Conversation

@antoncoding
Copy link
Copy Markdown
Owner

@antoncoding antoncoding commented Aug 18, 2025

Summary by CodeRabbit

  • New Features

    • Added friendly app-wide and page-level error screens with Retry actions.
  • Improvements

    • Migrated UI components to HeroUI for a consistent look and interactions (onPress actions).
    • Upgraded runtime and framework (Node.js and Next.js) for performance and stability.
    • Markets page initializes filters server-side and persists sort settings.
    • More resilient data fetching to reduce crashes from subgraph/GraphQL errors.
    • Refined wallet options; improved SSR behavior and theming.
  • Bug Fixes

    • Reduced hydration warnings and SSR-related issues.
    • Cleans up and unregisters old service workers to prevent stale caches.
  • Chores

    • Dependency, linting, and tooling updates; switched to pnpm.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 18, 2025

📝 Walkthrough

Walkthrough

Node/tooling upgraded, package manager switched, and Tailwind theme moved to HeroUI. Project-wide UI migration from NextUI to HeroUI with Button API shifting to onPress. Added error boundaries. Markets page now parses search params server-side and passes initial props. Subgraph fetchers gained graceful error handling. Providers/wallet config refactored. Misc SSR/typing tweaks.

Changes

Cohort / File(s) Summary of Changes
Tooling & Runtime
/.nvmrc, /package.json, /.yarnrc.yml, /tailwind.config.ts, /.eslintrc.js, /.claude/settings.local.json, /public/serviceWorker.js
Node 18.14→22.12; deps upgraded (Next.js 15, GraphQL 16, wagmi 2, TS 5.6); switch NextUI→HeroUI theme plugin; packageManager yarn→pnpm; ESLint rule toggles; add permissive Claude settings; add service worker to clear caches and unregister.
UI library migration to HeroUI
app/**/* (Tables, Cards, Tooltip, Switch, Dropdown, Pagination, Input, Select, Slider, Progress), src/components/**/* (Tooltip, Modal, Provider)
Replace all @nextui-org/* imports with @heroui/react equivalents. No functional logic changes; import sources and minor style-prop mappings updated.
Button API: onClick → onPress
app/**/*.tsx, src/components/**/*.tsx, notably app/markets/**, app/positions/**, app/rewards/**, src/components/Borrow/*, src/components/common/Button.tsx
Update Button handlers to onPress across the app; remove event param and stopPropagation where present. Base Button now sourced from HeroUI.
Error boundaries
/app/error.tsx, /app/global-error.tsx, /app/layout.tsx
Add client error and global error components with reset handlers; log errors; enable suppressHydrationWarning on html/body.
Markets server-side params & state
/app/markets/page.tsx, /app/markets/components/markets.tsx, /app/markets/components/utils.ts
MarketPage made async; parse/validate searchParams server-side and pass as initial props. Markets component now accepts initial props, uses SSR-safe useLocalStorage for sort state; URL updates rebuilt from component state. Minor ESLint directive simplification.
Async route params
/app/history/[account]/page.tsx, /app/positions/report/[account]/page.tsx
Page components now async; params is awaited (typed as Promise<...>).
Subgraph fetch robustness
/src/data-sources/subgraph/fetchers.ts, market.ts, liquidations.ts, transactions.ts, historical.ts, /src/utils/subgraph-urls.ts
Stop throwing on GraphQL errors; log and return partials; wrap fetches in try/catch with null/empty fallbacks; make transformers accept nullable data; minor optional chaining; type mapped to Partial<Record<...>>.
Providers & Wallet config
/src/OnchainProviders.tsx, /src/components/providers/ThemeProvider.tsx, /src/store/createWagmiConfig.ts
QueryClient defaults (bounded retries/backoff). Defer wagmi config until runtime; skip if missing projectId. Swap NextUIProvider→HeroUIProvider. Simplify wallet groups; remove Safe connector; SSR-safe wallet list.
SSR-safe storage and minor types
/src/hooks/useLocalStorage.ts, /src/hooks/useUserPositionsSummaryData.ts, /src/utils/gtag.ts
useLocalStorage: SSR guard + post-mount hydration + guarded storage events. Type alias tweaks (Record<>). Remove unused ESLint disable.
Oracle whitelist/types
/src/config/oracle-whitelist.ts
Change whitelist type to Partial<Record<...>>; add optional warningCodes to entries (used in Polygon data).
Reports: Date components
/app/positions/report/components/ReportContent.tsx, ReportTable.tsx, /src/components/common/DatePicker.tsx
DatePicker/DateValue moved to HeroUI; consolidate classNames; add explicit any casts with lint disables for DateValue.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant NextApp as Next.js App Router
  participant Page as Page/Server Component
  participant Markets as Markets (Client)
  Note over NextApp,Page: Markets route
  User->>NextApp: Navigate /markets?network=...&collaterals=...&loanAssets=...
  NextApp->>Page: Call MarketPage({ searchParams })
  Page->>Page: Parse/validate params (server)
  Page->>Markets: Render with {initialNetwork, initialCollaterals, initialLoanAssets}
  Markets->>Markets: Init state (SSR-safe useLocalStorage)
  User->>Markets: Interact (filter/sort)
  Markets->>NextApp: router.push(updated query)
Loading
sequenceDiagram
  autonumber
  participant React as React Runtime
  participant ErrorBoundary as app/error.tsx
  participant GlobalError as app/global-error.tsx
  participant User as User
  Note over React,ErrorBoundary: Client error handling
  React-->>ErrorBoundary: Render with error
  ErrorBoundary->>ErrorBoundary: useEffect(console.error)
  User->>ErrorBoundary: Click "Retry"
  ErrorBoundary->>React: reset() to re-render subtree
  Note over React,GlobalError: Unhandled/app-level errors render GlobalError similarly
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

Buttons now press, not click—how quaint!
HeroUI rides in, with fresher paint.
Markets parse early, errors corralled,
Subgraphs whisper, “don’t throw”—just logged and tallied.
New nodes hum, providers align—
Ship it clean, on pnpm time.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch opt/version

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Aug 18, 2025

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

Project Deployment Preview Comments Updated (UTC)
monarch Ready Ready Preview Comment Aug 19, 2025 5:08am

Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (2)
.nvmrc (1)

1-1: Optional: consider pinning strategy.

If you want reproducibility, pin the full patch (e.g., 22.12.0). If you prefer auto-updating to the latest LTS, use lts/* in .nvmrc. Current style (major.minor) is OK if that’s intentional.

package.json (1)

90-90: Type bump to @types/node ^22 approved—verify type-check

  • tsconfig.json currently has lib: ["dom","dom.iterable","esnext"], no explicit types.
  • No obvious DOM/Node global name conflicts found in source.
  • Run yarn tsc --noEmit to confirm there are no new errors.
  • If you hit DOM vs Node type clashes, tighten your config to:
    compilerOptions.lib: ["es2022"]
    compilerOptions.types: ["node","jest"]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9005ebf and 69da670.

📒 Files selected for processing (2)
  • .nvmrc (1 hunks)
  • package.json (1 hunks)
🔇 Additional comments (1)
.nvmrc (1)

1-1: No other Node version pins found—Node 22 bump OK

I ran the provided script and didn’t find any Node versions pinned in package.json, workflows, Dockerfiles or platform configs. Ready to roll with Node 22.

Copy link
Copy Markdown

@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: 5

🔭 Outside diff range comments (5)
app/positions/components/FromAndToMarkets.tsx (1)

291-300: Bug: PressEvent doesn’t have stopPropagation; this will throw

NextUI’s onPress receives a React Aria PressEvent, which doesn’t expose stopPropagation. Clicking “Max” will error at runtime.

Recommended fix (remove event usage; row onClick stays idempotent):

-                            onPress={(e) => {
-                              e.stopPropagation();
-                              onFromMarketSelect(marketPosition.market.uniqueKey);
-                              if (maxTransferableAmount > 0n) {
-                                onSelectMax?.(
-                                  marketPosition.market.uniqueKey,
-                                  Number(maxTransferableAmount),
-                                );
-                              }
-                            }}
+                            onPress={() => {
+                              onFromMarketSelect(marketPosition.market.uniqueKey);
+                              if (maxTransferableAmount > 0n) {
+                                onSelectMax?.(
+                                  marketPosition.market.uniqueKey,
+                                  Number(maxTransferableAmount),
+                                );
+                              }
+                            }}

If you must block the row’s onClick, alternatively switch this one control back to onClick:

-                            onPress={(e) => {
-                              e.stopPropagation();
+                            onClick={(e) => {
+                              e.stopPropagation();
                               onFromMarketSelect(marketPosition.market.uniqueKey);
                               if (maxTransferableAmount > 0n) {
                                 onSelectMax?.(
                                   marketPosition.market.uniqueKey,
                                   Number(maxTransferableAmount),
                                 );
                               }
                             }}
app/positions/components/PositionsSummaryTable.tsx (1)

366-385: Prevent row toggle when clicking “Rebalance”

The row has onClick to expand (Line 266). Clicking the Rebalance button will also trigger the row’s onClick due to event bubbling. Stop propagation on the button to avoid accidental expand/collapse.

Apply this diff:

         <Button
           size="sm"
           variant="interactive"
           className="text-xs"
-          onPress={() => {
+          onPress={() => {
             if (!isOwner) {
               toast.error(
                 'No authorization',
                 'You can only rebalance your own positions',
               );
               return;
             }
             setSelectedGroupedPosition(groupedPosition);
             setShowRebalanceModal(true);
           }}
+          onClick={(e) => e.stopPropagation()}
           disabled={!isOwner}
         >
           Rebalance
         </Button>
app/markets/components/MarketTableBody.tsx (1)

51-56: Prevent row toggle when clicking “Supply”

The table row toggles onClick (Lines 52-55). The Supply button’s onPress doesn’t stop propagation, so clicks will also toggle the row. Stop propagation on the button.

Apply this diff:

               <Button
                 size="sm"
                 variant="interactive"
                 className="text-xs"
-                onPress={() => {
+                onPress={() => {
                   setSelectedMarket(item);
                   setShowSupplyModal(true);
                 }}
+                onClick={(e) => e.stopPropagation()}
               >
                 Supply
               </Button>

Also applies to: 170-181

app/layout.tsx (1)

41-42: GoogleAnalytics is outside — invalid HTML structure

This can cause unpredictable rendering. Move the component inside body.

Apply this diff:

-      </body>
-      <GoogleAnalytics />
+        <GoogleAnalytics />
+      </body>
src/data-sources/subgraph/liquidations.ts (1)

36-41: Add $skip to the liquidation‐check query and guard against infinite loops

To avoid fetching the same page endlessly, update both the GraphQL query and the pagination loops:

• In src/graphql/morpho-subgraph-queries.ts, extend getSubgraphMarketsWithLiquidationCheck to accept and pass skip:

 export const subgraphMarketsWithLiquidationCheckQuery = `
- query getSubgraphMarketsWithLiquidationCheck($first: Int, $where: Market_filter) {
+ query getSubgraphMarketsWithLiquidationCheck($first: Int, $skip: Int, $where: Market_filter) {
     markets(
       first: $first,
+      skip: $skip,
       where: $where
     ) {
       id
       liquidates { id }
     }
   }
 `

• In src/data-sources/subgraph/liquidations.ts (around lines 36–41, 49–61 and 77–81), add a defensive break when no new keys are found:

   while (true) {
+    const prevCount = liquidatedKeys.size;
     const variables = { first: pageSize, skip, where: { inputToken_not_in: blacklistTokens } };
     const { markets } = await fetchSubgraph(subgraphMarketsWithLiquidationCheckQuery, variables);

     markets.forEach(market => {
       if (market.liquidates?.length) liquidatedKeys.add(market.id);
     });

     if (markets.length < pageSize) break;
+    if (liquidatedKeys.size === prevCount) {
+      console.warn(`Pagination misconfigured at skip=${skip}. Breaking to avoid infinite loop.`);
+      break;
+    }
     skip += pageSize;
   }

Repeat this guard in each pagination loop.

🧹 Nitpick comments (19)
app/market/[chainId]/[marketid]/content.tsx (1)

205-211: Harden window.open against reverse tabnabbing

Add noopener,noreferrer when opening a new tab.

-              onPress={() => {
-                void window.open(
-                  getMarketURL(market.uniqueKey, market.morphoBlue.chain.id),
-                  '_blank',
-                );
-              }}
+              onPress={() => {
+                void window.open(
+                  getMarketURL(market.uniqueKey, market.morphoBlue.chain.id),
+                  '_blank',
+                  'noopener,noreferrer',
+                );
+              }}
app/positions/components/RebalanceActionInput.tsx (1)

69-70: Optional: disable when amount <= 0

Prevents an avoidable validation round-trip.

-        isDisabled={!amount || !selectedFromMarketUniqueKey || !selectedToMarketUniqueKey}
+        isDisabled={
+          !amount ||
+          Number(amount) <= 0 ||
+          !selectedFromMarketUniqueKey ||
+          !selectedToMarketUniqueKey
+        }
app/positions/components/onboarding/RiskSelection.tsx (1)

207-213: Minor: align Button import path for consistency

Most files import Button from '@/components/common'. This file uses '@/components/common/Button'. Consider aligning to the shared barrel to keep imports consistent.

If desired, update the import:

// before
import { Button } from '@/components/common/Button';
// after
import { Button } from '@/components/common';
package.json (1)

98-102: Align ESLint core with eslint-config-next 15

eslint-config-next@15 typically expects a more recent ESLint (>=8.57). You’re on 8.27, which can cause peer warnings or rule resolution issues.

Apply this diff to bump ESLint:

-    "eslint": "^8.27.0",
+    "eslint": "^8.57.0",
app/positions/report/[account]/page.tsx (1)

10-12: Avoid Promise-typed params in Page; keep sync if not needed

In App Router, params is a plain object. Typing it as Promise and awaiting adds noise and may confuse type tooling.

Apply this diff:

-export default async function ReportPage({ params }: { params: Promise<{ account: Address }> }) {
-  const p = await params;
-  return <ReportContent account={p.account} />;
+export default function ReportPage({ params }: { params: { account: Address } }) {
+  return <ReportContent account={params.account} />;

If you intentionally rely on Promise-typed params due to a new Next feature, point me to the docs and I’ll adapt the suggestion.

src/components/SupplyModalContent.tsx (2)

142-170: onPress migration LGTM; minor nits for robustness

  • Call switchToNetwork via a function and ignore its Promise to avoid unhandled rejections.
  • Use isDisabled (consistent with your Button/NextUI API) instead of disabled for proper styling/variants.

Apply this diff:

-                      <Button
-                        onPress={switchToNetwork}
+                      <Button
+                        onPress={() => void switchToNetwork()}
                         className="ml-2 min-w-32"
                         variant="secondary"
                       >
                         Switch Chain
                       </Button>
@@
-                      <Button
-                        disabled={!isConnected || isLoadingPermit2 || supplyPending}
+                      <Button
+                        isDisabled={!isConnected || isLoadingPermit2 || supplyPending}
                         onPress={() => void approveAndSupply()}
                         className="ml-2 min-w-32"
                         variant="cta"
                       >
                         Supply
                       </Button>
@@
-                      <Button
-                        disabled={
+                      <Button
+                        isDisabled={
                           !isConnected || supplyPending || inputError !== null || !supplyAmount
                         }
                         onPress={() => void signAndSupply()}
                         className="ml-2 min-w-32"
                         variant="cta"

29-33: Fix missing dependency in useCallback

onSuccess uses refetch but it’s not in the deps array; can capture stale ref.

Apply:

-  }, [onClose]);
+  }, [onClose, refetch]);
app/global-error.tsx (1)

15-18: Consider not exposing raw error messages to users

Raw error messages can leak internals. Show a generic message in production; keep detail in console.

Example:

{process.env.NODE_ENV === 'development' ? (error?.message ?? 'Unknown error') : null}
app/rewards/components/RewardTable.tsx (1)

208-241: onPress event: avoid relying on stopPropagation

NextUI onPress typically passes a PressEvent (no stopPropagation). This may be a no-op or TS-incompatible depending on your Button typing. You already prevent row propagation on the Link; consider dropping stopPropagation here.

Apply:

-                          onPress={(e) => {
-                            void (async () => {
-                              e.stopPropagation();
+                          onPress={() => {
+                            void (async () => {
                               if (!account) {

Also, waiting a fixed 1s after switchToNetwork is brittle. Prefer awaiting the switch if your hook returns a Promise or fire sendTransaction in onNetworkSwitched.

Confirm the onPress event type in src/components/common/Button.tsx and whether switchToNetwork returns a Promise. If yes, replace the timeout with await switchToNetwork().

app/error.tsx (2)

4-4: Verify Barrel export for Button path.

This imports from '@/components/common'. Other files import from '@/components/common/Button'. For consistency and to avoid path drift, consider standardizing.


7-10: Optional: capture more error context.

If available, also log error.digest to aid debugging in Next.js.

-    console.error('App error boundary caught:', error);
+    console.error('App error boundary caught:', { error, digest: (error as any)?.digest });
app/positions/components/onboarding/SuccessPage.tsx (1)

39-41: Grammar nit: “automate reallocate” → “automatically reallocate”.

Small copy tweak.

-          {allowAgentSetting && 'You can set Monarch Agent to automate reallocate your positions.'}
+          {allowAgentSetting && 'You can set Monarch Agent to automatically reallocate your positions.'}
public/serviceWorker.js (1)

16-16: Drop the empty fetch handler.

A no-op fetch listener is unnecessary; remove to keep the SW minimal.

-self.addEventListener('fetch', () => {});
src/OnchainProviders.tsx (1)

41-43: Useless Fragment; return children directly.

Minor cleanup (also covered by the fix above if you adopt it).

-      return <>{children}</>;
+      return children;
app/layout.tsx (1)

26-31: Hydration suppression on html/body — consider scoping it tighter

Setting suppressHydrationWarning on both html and body can hide real mismatches. If the mismatch is only due to theme/fonts, prefer limiting suppression to the smallest surface (e.g., theme root or specific nodes).

Can you confirm which elements still mismatch without the global suppression? If it’s only theme class toggling, we can move suppression to the theme wrapper.

src/data-sources/subgraph/fetchers.ts (1)

19-28: GraphQL errors now non-throwing — tighten typing and logs

The behavior change is fine. Two improvements:

  • Include apiUrl in the warning for quicker triage.
  • Consider a typed GraphQLResponse return in the future to force call sites to handle errors.

Apply this diff for better logging context:

-    console.warn('Subgraph API GraphQL Error (non-fatal):', result.errors);
+    console.warn('Subgraph API GraphQL Error (non-fatal)', {
+      apiUrl,
+      errors: (result as any).errors,
+    });

Ensure all callers either inspect response.errors or have surrounding guards (as you did in markets/liquidations).

src/data-sources/subgraph/liquidations.ts (1)

36-40: Parity with markets filter: include zero address

For consistency with fetchSubgraphMarkets, also exclude the zero address here.

-      where: { inputToken_not_in: blacklistTokens },
+      where: { inputToken_not_in: [...blacklistTokens, '0x0000000000000000000000000000000000000000'] },
src/data-sources/subgraph/market.ts (2)

305-324: Log GraphQL errors (non-fatal) to aid triage

Fetcher no longer throws on GraphQL errors. Log them here too for market-specific context.

     const response = await subgraphGraphqlFetcher<SubgraphMarketQueryResponse>(
       subgraphApiUrl,
       subgraphMarketQuery,
       {
         id: uniqueKey.toLowerCase(),
       },
     );
 
+    if (response.errors?.length) {
+      console.warn('Subgraph GraphQL errors (non-fatal):', response.errors);
+    }

357-377: Minor: avoid double-cast and log GraphQL errors for context

  • The unknown as Record<string, unknown> cast is unnecessary; a single cast is enough.
  • Add an optional log for non-fatal GraphQL errors.
-    const response = await subgraphGraphqlFetcher<SubgraphMarketsQueryResponse>(
+    const response = await subgraphGraphqlFetcher<SubgraphMarketsQueryResponse>(
       subgraphApiUrl,
       subgraphMarketsQuery,
-      variables as unknown as Record<string, unknown>,
+      variables as Record<string, unknown>,
     );
 
+    if (response.errors?.length) {
+      console.warn('Subgraph GraphQL errors (non-fatal):', response.errors);
+    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 69da670 and 306f0c8.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (30)
  • app/error.tsx (1 hunks)
  • app/global-error.tsx (1 hunks)
  • app/layout.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/content.tsx (1 hunks)
  • app/markets/components/MarketSettingsModal.tsx (2 hunks)
  • app/markets/components/MarketTableBody.tsx (1 hunks)
  • app/markets/components/markets.tsx (2 hunks)
  • app/positions/components/FromAndToMarkets.tsx (1 hunks)
  • app/positions/components/PositionsContent.tsx (3 hunks)
  • app/positions/components/PositionsSummaryTable.tsx (2 hunks)
  • app/positions/components/RebalanceActionInput.tsx (1 hunks)
  • app/positions/components/RebalanceModal.tsx (1 hunks)
  • app/positions/components/SuppliedMarketsDetail.tsx (2 hunks)
  • app/positions/components/onboarding/Modal.tsx (1 hunks)
  • app/positions/components/onboarding/RiskSelection.tsx (1 hunks)
  • app/positions/components/onboarding/SuccessPage.tsx (1 hunks)
  • app/positions/report/[account]/page.tsx (1 hunks)
  • app/rewards/components/RewardTable.tsx (1 hunks)
  • package.json (4 hunks)
  • public/serviceWorker.js (1 hunks)
  • src/OnchainProviders.tsx (1 hunks)
  • src/components/Borrow/AddCollateralAndBorrow.tsx (3 hunks)
  • src/components/Borrow/WithdrawCollateralAndRepay.tsx (2 hunks)
  • src/components/SupplyModalContent.tsx (3 hunks)
  • src/components/WithdrawModalContent.tsx (1 hunks)
  • src/components/layout/header/AccountConnect.tsx (1 hunks)
  • src/components/providers/ThemeProvider.tsx (1 hunks)
  • src/data-sources/subgraph/fetchers.ts (1 hunks)
  • src/data-sources/subgraph/liquidations.ts (1 hunks)
  • src/data-sources/subgraph/market.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/Borrow/AddCollateralAndBorrow.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-12-16T02:01:51.219Z
Learnt from: antoncoding
PR: antoncoding/monarch#97
File: app/positions/components/onboarding/OnboardingContext.tsx:36-43
Timestamp: 2024-12-16T02:01:51.219Z
Learning: In `app/positions/components/onboarding/OnboardingContext.tsx`, the `defaultStep` variable is no longer needed and can be removed.

Applied to files:

  • app/positions/components/PositionsContent.tsx
📚 Learning: 2024-12-17T10:51:07.277Z
Learnt from: antoncoding
PR: antoncoding/monarch#99
File: src/components/AgentSetupProcessModal.tsx:38-51
Timestamp: 2024-12-17T10:51:07.277Z
Learning: In `src/components/AgentSetupProcessModal.tsx`, the modal is intentionally designed without a close button.

Applied to files:

  • app/positions/components/PositionsContent.tsx
  • app/positions/components/onboarding/Modal.tsx
  • app/positions/components/onboarding/SuccessPage.tsx
📚 Learning: 2024-10-23T16:17:02.841Z
Learnt from: antoncoding
PR: antoncoding/monarch#77
File: src/graphql/queries.ts:168-193
Timestamp: 2024-10-23T16:17:02.841Z
Learning: In `src/graphql/queries.ts`, handling only `MarketTransferTransactionData` is intentional at this time.

Applied to files:

  • src/data-sources/subgraph/liquidations.ts
  • src/data-sources/subgraph/market.ts
🧬 Code Graph Analysis (15)
app/global-error.tsx (2)
app/error.tsx (1)
  • Error (6-21)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/positions/components/PositionsContent.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/positions/components/onboarding/Modal.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
src/components/Borrow/WithdrawCollateralAndRepay.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/markets/components/MarketSettingsModal.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/market/[chainId]/[marketid]/content.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/positions/report/[account]/page.tsx (1)
app/positions/report/components/ReportContent.tsx (1)
  • ReportContent (34-269)
app/error.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/layout.tsx (1)
app/fonts.ts (3)
  • zen (3-18)
  • inter (20-35)
  • monospace (37-41)
src/data-sources/subgraph/liquidations.ts (2)
src/data-sources/subgraph/fetchers.ts (1)
  • subgraphGraphqlFetcher (1-31)
src/graphql/morpho-subgraph-queries.ts (1)
  • subgraphMarketsWithLiquidationCheckQuery (241-258)
src/components/WithdrawModalContent.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/positions/components/onboarding/SuccessPage.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
src/data-sources/subgraph/market.ts (2)
src/data-sources/subgraph/fetchers.ts (1)
  • subgraphGraphqlFetcher (1-31)
src/utils/subgraph-types.ts (2)
  • SubgraphMarketQueryResponse (86-91)
  • SubgraphMarketsQueryResponse (78-83)
src/OnchainProviders.tsx (1)
src/store/createWagmiConfig.ts (1)
  • createWagmiConfig (24-67)
src/components/layout/header/AccountConnect.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
🪛 ESLint
app/global-error.tsx

[error] 7-7: elements must have the lang prop.

(jsx-a11y/html-has-lang)

src/OnchainProviders.tsx

[error] 42-42: Fragments should contain more than one child - otherwise, there’s no need for a Fragment at all.

(react/jsx-no-useless-fragment)

🪛 Biome (2.1.2)
app/error.tsx

[error] 6-6: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🔇 Additional comments (24)
app/market/[chainId]/[marketid]/content.tsx (1)

194-201: onPress migration looks good

Handlers map 1:1 from onClick to onPress; no behavior change.

app/positions/components/onboarding/Modal.tsx (1)

84-86: LGTM on onPress switch

Using NextUI’s Button with onPress is consistent with their API.

app/positions/components/RebalanceActionInput.tsx (1)

67-68: LGTM on onPress switch

No event usage; safe migration.

app/positions/components/RebalanceModal.tsx (1)

303-306: LGTM on onPress switch

Refresh still does the same thing, just via onPress.

app/markets/components/markets.tsx (1)

432-441: onPress migration looks good

Both buttons correctly switch to onPress; handlers and accessibility labels remain intact.

Also applies to: 443-452

app/positions/components/PositionsContent.tsx (1)

93-97: Buttons migrated to onPress — OK

Handlers are unchanged and semantics preserved for Agent setup, onboarding, and refresh.

Also applies to: 99-107, 162-171

app/positions/components/PositionsSummaryTable.tsx (1)

166-175: Refresh Button: onPress change is correct

No behavior changes; disabled state still respected.

src/components/layout/header/AccountConnect.tsx (1)

44-47: Connect Button: onPress migration is correct

Matches the Button API change; handleClicked remains the same.

src/components/WithdrawModalContent.tsx (2)

167-170: onPress migration looks good

Switch Chain button correctly moved to onPress; no behavior change.


173-173: Async handler usage is safe

void withdraw() avoids unhandled promise warnings and matches onPress signature.

src/components/Borrow/WithdrawCollateralAndRepay.tsx (2)

344-351: onPress handler logic is correct

Conditional approve vs sign path preserved; void calls prevent promise leakage. isDisabled gating above is consistent.


330-333: Replace unsupported solid variant with a valid one

The Button wrapper only supports: default, cta, secondary, interactive, ghost. Change variant="solid" to match other “Switch Chain” buttons:

• File: src/components/Borrow/WithdrawCollateralAndRepay.tsx
Lines ~330

-  <Button onPress={switchToNetwork} className="min-w-32" variant="solid">
+  <Button onPress={switchToNetwork} className="min-w-32" variant="secondary">
⛔ Skipped due to learnings
Learnt from: antoncoding
PR: antoncoding/monarch#94
File: src/components/supplyModal.tsx:449-452
Timestamp: 2024-12-08T12:10:17.127Z
Learning: The 'solid' variant is defined in the new `Button` component and is a valid variant.
app/positions/components/SuppliedMarketsDetail.tsx (2)

228-232: onPress migration: Withdraw action

State updates preserved; no regressions.


238-244: onPress migration: Supply action

Looks good; consistent with Button API.

app/markets/components/MarketSettingsModal.tsx (2)

237-246: Preset entries: onPress migration is correct

Using NextUI Button; onPress is the right event. No logic changes.


259-261: Custom entries submit: onPress migration is correct

Event prop updated; handler unchanged. Enter key handling remains intact.

app/positions/components/onboarding/RiskSelection.tsx (1)

207-213: onPress migration for Details button is correct

Preserves stopPropagation and new tab open behavior.

package.json (4)

49-49: Next.js 15 upgrade looks good

Upgrade to next@15.1.6 is aligned with the Node bump and TS update.


130-130: Yarn 4 setup OK — node-modules mode confirmed
Your .yarnrc.yml already sets

  • nodeLinker: node-modules
  • yarnPath: .yarn/releases/yarn-4.1.0.cjs

Since you’re using the classic node_modules linker, no PnP bridges (pnpify) are needed for Jest (you have jest.config.js).
Everything looks configured correctly.


88-88: ts-jest TypeScript compatibility confirmed
ts-jest@29.1.1 declares "typescript": ">=4.3 <6", so your TypeScript ~5.6.3 is fully supported. No further changes needed here.


131-133: Resolution on tr46: no active dependents

Your yarn.lock doesn’t list tr46 or whatwg-url, so the "resolutions": { "tr46": "^4.0.0" } entry is effectively a no-op today. There are no packages pulling in tr46, so this bump won’t break anything.

app/positions/components/onboarding/SuccessPage.tsx (1)

45-52: onPress migration looks good.

Button handler migration to onPress matches the updated Button API.

public/serviceWorker.js (1)

1-14: No Service Worker registration detected
I searched for all navigator.serviceWorker.register(…) calls and found none. This SW file isn’t being re-registered on page load and won’t immediately unregister itself.

src/components/providers/ThemeProvider.tsx (1)

15-16: LGTM: suppressHydrationWarning is appropriate here.

This helps avoid hydration mismatches with theme class switching.

Comment thread app/error.tsx Outdated
Comment thread app/global-error.tsx Outdated
Comment thread public/serviceWorker.js
Comment thread src/components/WithdrawModalContent.tsx
Comment thread src/OnchainProviders.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (9)
src/components/layout/header/AccountDropdown.tsx (1)

64-65: Update DropdownItem handlers: use onPress and harden external links

Please apply these changes for consistency with the new React Aria API and to mitigate reverse tab-nabbing:

• src/components/layout/header/AccountDropdown.tsx

<DropdownItem
  key="copy"
- onClick={handleCopyAddress}
+ onPress={handleCopyAddress}
  endContent={<CopyIcon className="h-4 w-4" />}
/>

<DropdownItem
  key="explorer"
  endContent={<ExternalLinkIcon className="h-4 w-4" />}
- onClick={() => window.open(getExplorerURL(address, chainId ?? 1), '_blank')}
+ onPress={() =>
+   window.open(
+     getExplorerURL(address, chainId ?? 1),
+     '_blank',
+     'noopener,noreferrer'
+   )
+ }
>
  View on Explorer
</DropdownItem>

<DropdownItem
  key="logout"
- onClick={handleDisconnectWallet}
+ onPress={handleDisconnectWallet}
  endContent={<ExitIcon className="h-4 w-4" />}
  className="text-red-500 data-[hover=true]:text-red-500"
/>

• src/components/layout/header/Navbar.tsx

<DropdownItem
  key="docs"
  endContent={<RiBookLine className="h-4 w-4" />}
- onClick={() => window.open(EXTERNAL_LINKS.docs, '_blank')}
+ onPress={() =>
+   window.open(EXTERNAL_LINKS.docs, '_blank', 'noopener,noreferrer')
+ }
/>
…
<DropdownItem
  key="discord"
  endContent={<RiDiscordFill className="h-4 w-4" />}
- onClick={() => window.open(EXTERNAL_LINKS.discord, '_blank')}
+ onPress={() =>
+   window.open(EXTERNAL_LINKS.discord, '_blank', 'noopener,noreferrer')
+ }
/>
…
<DropdownItem
  key="github"
  endContent={<RiGithubFill className="h-4 w-4" />}
- onClick={() => window.open(EXTERNAL_LINKS.github, '_blank')}
+ onPress={() =>
+   window.open(EXTERNAL_LINKS.github, '_blank', 'noopener,noreferrer')
+ }
/>
…
<DropdownItem
  key="theme"
  endContent={mounted && (theme === 'dark' ? <LuSunMedium size={16}/> : <FaRegMoon size={14}/>)}
- onClick={toggleTheme}
+ onPress={toggleTheme}
>
  {theme === 'dark' ? 'Light Theme' : 'Dark Theme'}
</DropdownItem>
src/utils/subgraph-urls.ts (1)

29-34: Fix type-safety: omit keys when URL is undefined

With Partial<Record<SupportedNetworks, string>>, values must be string when present. Currently you assign potentially undefined values to present keys, which violates the type (and can fail under strict TS).

Build the object conditionally so keys only exist when the URL exists.

Apply this diff:

-export const SUBGRAPH_URLS: Partial<Record<SupportedNetworks, string>> = {
-  [SupportedNetworks.Base]: baseSubgraphUrl,
-  [SupportedNetworks.Mainnet]: mainnetSubgraphUrl,
-  [SupportedNetworks.Polygon]: polygonSubgraphUrl,
-  [SupportedNetworks.Unichain]: unichainSubgraphUrl,
-};
+export const SUBGRAPH_URLS: Partial<Record<SupportedNetworks, string>> = {
+  ...(baseSubgraphUrl ? { [SupportedNetworks.Base]: baseSubgraphUrl } : {}),
+  ...(mainnetSubgraphUrl ? { [SupportedNetworks.Mainnet]: mainnetSubgraphUrl } : {}),
+  ...(polygonSubgraphUrl ? { [SupportedNetworks.Polygon]: polygonSubgraphUrl } : {}),
+  ...(unichainSubgraphUrl ? { [SupportedNetworks.Unichain]: unichainSubgraphUrl } : {}),
+};
src/data-sources/subgraph/transactions.ts (1)

117-124: Bug: undefined marketUniqueKeys yields empty results

When filters.marketUniqueKeys is undefined, the current predicate evaluates to undefined (falsy) and filters out everything. Expected behavior (per comment) is “all markets.”

Apply this diff:

-  // marketUniqueKeys is empty: all markets
-  const filteredTransactions =
-    filters.marketUniqueKeys?.length === 0
-      ? allTransactions
-      : allTransactions.filter(
-          (tx) => filters.marketUniqueKeys?.includes(tx.data.market.uniqueKey),
-        );
+  // marketUniqueKeys absent or empty: all markets
+  const marketKeys = filters.marketUniqueKeys;
+  const filteredTransactions =
+    !marketKeys || marketKeys.length === 0
+      ? allTransactions
+      : allTransactions.filter((tx) => marketKeys.includes(tx.data.market.uniqueKey));
src/config/oracle-whitelist.ts (1)

18-22: Trim trailing spaces in addresses

Trailing spaces in feed addresses will cause mismatches downstream and make links/copypasta error-prone.

Apply this diff:

-        address: '0x3Ea1eC855fBda8bA0396975eC260AD2e9B2Bc01c ',
+        address: '0x3Ea1eC855fBda8bA0396975eC260AD2e9B2Bc01c',
...
-        address: '0xaca1222008C6Ea624522163174F80E6e17B0709A  ',
+        address: '0xaca1222008C6Ea624522163174F80E6e17B0709A',

Also applies to: 32-36

app/positions/report/components/ReportContent.tsx (5)

45-48: Bug: set() result ignored; twoMonthsAgo doesn’t zero minute/second

@internationalized/date objects are immutable; set() returns a new instance. The current code discards it.

-  const twoMonthsAgo = useMemo(() => {
-    const date = now(getLocalTimeZone()).subtract({ months: 2 });
-    date.set({ minute: 0, second: 0 });
-    return date;
-  }, []);
+  const twoMonthsAgo = useMemo(() => {
+    return now(getLocalTimeZone()).subtract({ months: 2 }).set({ minute: 0, second: 0 });
+  }, []);

96-103: Bug: comparing ZonedDateTime with < and >; use compare()

ZonedDateTime doesn’t support JS relational operators. Use compare() like elsewhere in this file.

   const handleStartDateChange = (date: ZonedDateTime) => {
     // Ensure time is set to exact hour
     const exactHourDate = date.set({ minute: 0, second: 0 });
-    if (exactHourDate > endDate) {
+    if (exactHourDate.compare(endDate) > 0) {
       setEndDate(exactHourDate);
     }
     setStartDate(exactHourDate);
   };
 
   const handleEndDateChange = (date: ZonedDateTime) => {
     // Ensure time is set to exact hour
     const exactHourDate = date.set({ minute: 0, second: 0 });
-    if (exactHourDate < startDate) {
+    if (exactHourDate.compare(startDate) < 0) {
       setStartDate(exactHourDate);
     }
     setEndDate(exactHourDate);
   };

Also applies to: 105-112


82-86: Use value equality for date changes; avoid referential checks

These are new instances; !== will often be true even for equal moments. Use compare() like in isReportCurrent.

-      reportState.asset.address !== selectedAsset.address ||
-      reportState.startDate !== startDate ||
-      reportState.endDate !== endDate
+      reportState.asset.address !== selectedAsset.address ||
+      reportState.startDate.compare(startDate) !== 0 ||
+      reportState.endDate.compare(endDate) !== 0

169-181: Guard against positions being undefined in useMemo

positions may be undefined while loading; positions.forEach(...) will throw before the loading branch renders.

-    positions.forEach((position) => {
+    (positions ?? []).forEach((position) => {
       const key = `${position.market.loanAsset.address}-${position.market.morphoBlue.chain.id}`;
       if (!assetMap.has(key)) {
         assetMap.set(key, {
           symbol: position.market.loanAsset.symbol,
           address: position.market.loanAsset.address as Address,
           chainId: position.market.morphoBlue.chain.id,
           decimals: position.market.loanAsset.decimals,
         });
       }
     });

239-241: Use onPress, not onClick, with our Button wrapper

For consistency with the Button API migration and NextUI semantics.

-              <Button
-                onClick={() => {
+              <Button
+                onPress={() => {
                   void handleGenerateReport();
                 }}
♻️ Duplicate comments (1)
src/OnchainProviders.tsx (1)

39-48: Fix confirmed: WagmiProvider no longer renders with undefined config

Returning children when projectId is missing and lazily caching wagmiConfig addresses the earlier runtime issue.

🧹 Nitpick comments (16)
src/components/layout/header/AccountDropdown.tsx (1)

23-29: Handle clipboard failures.

Navigator clipboard can reject (permissions, HTTP). Surface an error toast.

   const handleCopyAddress = useCallback(() => {
     if (address) {
-      void navigator.clipboard.writeText(address).then(() => {
-        toast.success('Address copied', 'Address copied to clipboard');
-      });
+      void navigator.clipboard
+        .writeText(address)
+        .then(() => {
+          toast.success('Address copied', 'Address copied to clipboard');
+        })
+        .catch(() => {
+          toast.error('Copy failed', 'Unable to write to clipboard');
+        });
     }
   }, [address, toast]);
src/data-sources/subgraph/market.ts (2)

300-303: Consider returning safe fallbacks instead of throwing on missing URL

Elsewhere (historical, transactions) we degrade gracefully when the subgraph URL is absent. Throwing here can bubble and break SSR.

Apply this diff:

   if (!subgraphApiUrl) {
     console.error(`Subgraph URL for network ${network} is not defined.`);
-    throw new Error(`Subgraph URL for network ${network} is not defined.`);
+    return null;
   }

And similarly for the list fetcher:

   if (!subgraphApiUrl) {
     console.error(`Subgraph URL for network ${network} is not defined.`);
-    throw new Error(`Subgraph URL for network ${network} is not defined.`);
+    return [];
   }

Also applies to: 345-348


112-116: Remove leftover debug log

This targeted log will spam the console in prod.

Apply this diff:

-  if (
-    marketId.toLowerCase() === '0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836'
-  ) {
-    console.log('subgraphMarket', subgraphMarket);
-  }
src/config/oracle-whitelist.ts (1)

116-121: Normalize oracle address lookup to avoid case mismatches

Whitelist keys are lowercase; incoming addresses may be checksum/mixed-case. Normalize input to ensure consistent hits.

Apply this diff:

 export const getWhitelistedOracleData = (
   oracleAddress: Address,
   network: SupportedNetworks,
 ): WhitelistedOracleData | undefined => {
-  return oracleWhitelist[network]?.[oracleAddress];
+  const list = oracleWhitelist[network];
+  if (!list) return undefined;
+  return list[oracleAddress] ?? list[oracleAddress.toLowerCase() as Address];
 };
app/positions/report/components/ReportContent.tsx (1)

212-214: Avoid any-casts for DatePicker value; use DateValue typing

ZonedDateTime is a DateValue; prefer a typed cast to DateValue over any. This keeps type-safety and removes the lint disable.

-                  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-                  value={startDate as any}
+                  value={startDate as DateValue}
...
-                  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-                  value={endDate as any}
+                  value={endDate as DateValue}

Follow-up: consider updating DatePicker’s props to generically accept ZonedDateTime so you can also drop the as any on onChange.

Also applies to: 225-227

app/history/[account]/page.tsx (1)

9-11: Don’t type params as a Promise; await is unnecessary for page props

In App Router, params is a plain object. Typing it as a Promise isn’t aligned with Next.js and can confuse TS tooling. await params won’t break at runtime but hurts DX.

Proposed fix:

-export default async function HistoryPage({ params }: { params: Promise<{ account: string }> }) {
-  const p = await params;
-  return <HistoryContent account={p.account} />;
+export default async function HistoryPage({ params }: { params: { account: string } }) {
+  return <HistoryContent account={params.account} />;
 }

Please double-check Next.js 15 docs for page component props to confirm params isn’t a Promise.

package.json (1)

130-134: Add engines.node to enforce Node 22; verify if tr46/punycode resolutions are still needed

  • Add an engines field so CI and package managers enforce the Node bump.
  • The resolutions for tr46/punycode might be redundant with the upgraded tree; keep if needed, but worth verifying to reduce lockfile churn.

Suggested additions (outside this hunk):

   "private": true,
+  "engines": {
+    "node": ">=22.12.0"
+  },

If you keep the resolutions, consider documenting the reason in the PR or a comment to help future maintainers.

Can you confirm whether these resolutions address a specific issue (e.g., URL/IDNA behavior under Node 22) and are still necessary after the Next 15 upgrade?

app/rewards/components/RewardTable.tsx (1)

208-239: Avoid race with setState before switching network; await actual switch instead of a fixed delay

You set targetChainId, then immediately call switchToNetwork() which likely reads the previous state. The 1s timeout is brittle.

Prefer passing the target chain directly (if supported) and awaiting the switch. Example:

-                              if (chainId !== distribution.distributor.chain_id) {
-                                // Set the target chain ID and switch
-                                setTargetChainId(distribution.distributor.chain_id);
-                                switchToNetwork();
-                                // Wait for network switch
-                                await new Promise((resolve) => setTimeout(resolve, 1000));
-                              }
+                              if (chainId !== distribution.distributor.chain_id) {
+                                // Switch directly to the required chain and await completion
+                                await switchToNetwork(distribution.distributor.chain_id);
+                              }

If switchToNetwork doesn’t accept a parameter, consider adjusting the hook to:

  • accept a chainId argument, or
  • return a Promise resolved via its onNetworkSwitched callback, so you can await it here instead of setTimeout.

This will remove potential flakiness when claiming on a different network.

Please confirm the signature of useMarketNetwork().switchToNetwork and whether it can be updated to accept a target chain ID or return a Promise.

src/hooks/useLocalStorage.ts (1)

26-37: Post-mount hydration is correct; consider dropping local-storage-fallback

The rehydrate-after-mount pattern avoids SSR mismatches. Given the new SSR guards, you can likely remove the local-storage-fallback dependency and use window.localStorage directly when typeof window !== 'undefined'. This reduces deps and makes intent clear. I can draft a small refactor if you want.

Happy to push a minimal refactor to native localStorage if desired.

app/markets/components/markets.tsx (1)

271-287: Tighten sort column validation; optionally reset direction when switching columns

Object.values on numeric enums includes both numbers and strings; filter to numbers to avoid false positives. Also consider resetting direction when changing columns (keeps UX predictable).

Apply within the callback:

   const titleOnclick = useCallback(
     (column: number) => {
-      // Validate that column is a valid SortColumn value
-      const isValidColumn = Object.values(SortColumn).includes(column);
-      if (!isValidColumn) {
+      // Validate numeric enum values only
+      const isValidColumn = (Object.values(SortColumn) as unknown[]).some(
+        (v) => typeof v === 'number' && v === column,
+      );
+      if (!isValidColumn) {
         console.error(`Invalid sort column value: ${column}`);
         return;
       }
 
-      setSortColumn(column);
-
-      if (column === sortColumn) {
-        setSortDirection(-sortDirection);
-      }
+      if (column === sortColumn) {
+        setSortDirection((d) => -d);
+      } else {
+        setSortColumn(column);
+        // Optional: reset direction on new column
+        setSortDirection(-1);
+      }
     },
     [sortColumn, sortDirection, setSortColumn, setSortDirection],
   );

If you prefer avoiding recomputing valid values per call, define a Set once:

// Place inside component, above titleOnclick
const validSortColumns = useMemo(
  () => new Set<number>((Object.values(SortColumn) as unknown[]).filter((v): v is number => typeof v === 'number')),
  [],
);

Then replace the validation with: if (!validSortColumns.has(column)) …

src/OnchainProviders.tsx (2)

12-32: Harden retry logic: guard error type before reading message

error may be non-Error (string/unknown). Guarding avoids edge cases and type noise.

   defaultOptions: {
     queries: {
-      retry: (failureCount, error) => {
-        // Don't retry on GraphQL errors, network errors, or client errors
-        if (error?.message?.includes('GraphQL') || error?.message?.includes('Network response was not ok')) {
-          return false;
-        }
-        // Retry up to 2 times for other errors
-        return failureCount < 2;
-      },
+      retry: (failureCount, error) => {
+        // Normalize error message safely
+        const msg =
+          typeof error === 'string'
+            ? error
+            : (error && typeof error === 'object' && 'message' in error && typeof (error as any).message === 'string'
+                ? (error as any).message
+                : '');
+        // Don't retry on GraphQL errors or failed fetch responses
+        if (msg.includes('GraphQL') || msg.includes('Network response was not ok')) {
+          return false;
+        }
+        // Retry up to 2 times for other errors
+        return failureCount < 2;
+      },
       retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
       // Prevent queries from throwing and crashing the app
       throwOnError: false,
     },

37-37: Remove stray ESLint disable

This directive targets promise-function-async but the following function isn’t async. Safe to drop.

-// eslint-disable-next-line @typescript-eslint/promise-function-async
.eslintrc.js (4)

43-45: Consider keeping array-as-prop rule at least as a warning

New arrays in props often cause needless re-renders. A warn level keeps signal without blocking.

-    'react-perf/jsx-no-new-array-as-prop': 'off',
+    'react-perf/jsx-no-new-array-as-prop': 'warn',

73-73: Turning off no-explicit-any globally can mask bugs

If you need flexibility, prefer 'warn' and allow exceptions via inline ts-expect-error.

-    '@typescript-eslint/no-explicit-any': 'off',
+    '@typescript-eslint/no-explicit-any': ['warn', { fixToUnknown: false }],

123-126: Prefer guiding nullish-coalescing and optional chaining rather than disabling

These rules help readability and safety. Downgrade to warn to keep nudge without friction.

-    '@typescript-eslint/prefer-nullish-coalescing': 'off',
+    '@typescript-eslint/prefer-nullish-coalescing': 'warn',
-    '@typescript-eslint/prefer-optional-chain': 'off',
+    '@typescript-eslint/prefer-optional-chain': 'warn',

136-138: Avoid disabling exhaustive-deps globally

This hides real bugs. Keep it on (or warn) and suppress locally where intentional.

-    // Hook dependency warnings are intentionally skipped to ensure flow
-    'react-hooks/exhaustive-deps': 'off',
+    // Prefer local disables where necessary
+    'react-hooks/exhaustive-deps': 'warn',
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 306f0c8 and 638b810.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • .claude/settings.local.json (1 hunks)
  • .eslintrc.js (4 hunks)
  • .yarnrc.yml (1 hunks)
  • app/error.tsx (1 hunks)
  • app/global-error.tsx (1 hunks)
  • app/history/[account]/page.tsx (1 hunks)
  • app/markets/components/markets.tsx (4 hunks)
  • app/markets/components/utils.ts (1 hunks)
  • app/markets/page.tsx (2 hunks)
  • app/positions/components/FromAndToMarkets.tsx (1 hunks)
  • app/positions/components/onboarding/RiskSelection.tsx (2 hunks)
  • app/positions/report/components/ReportContent.tsx (2 hunks)
  • app/rewards/components/RewardTable.tsx (1 hunks)
  • package.json (4 hunks)
  • src/OnchainProviders.tsx (1 hunks)
  • src/components/WithdrawModalContent.tsx (1 hunks)
  • src/components/layout/header/AccountDropdown.tsx (1 hunks)
  • src/config/oracle-whitelist.ts (1 hunks)
  • src/data-sources/subgraph/historical.ts (1 hunks)
  • src/data-sources/subgraph/market.ts (2 hunks)
  • src/data-sources/subgraph/transactions.ts (2 hunks)
  • src/hooks/useLocalStorage.ts (3 hunks)
  • src/hooks/useUserPositionsSummaryData.ts (1 hunks)
  • src/utils/gtag.ts (0 hunks)
  • src/utils/subgraph-urls.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/utils/gtag.ts
✅ Files skipped from review due to trivial changes (2)
  • app/markets/components/utils.ts
  • src/hooks/useUserPositionsSummaryData.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/global-error.tsx
  • app/positions/components/FromAndToMarkets.tsx
  • app/error.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-23T16:17:02.841Z
Learnt from: antoncoding
PR: antoncoding/monarch#77
File: src/graphql/queries.ts:168-193
Timestamp: 2024-10-23T16:17:02.841Z
Learning: In `src/graphql/queries.ts`, handling only `MarketTransferTransactionData` is intentional at this time.

Applied to files:

  • src/data-sources/subgraph/transactions.ts
  • src/data-sources/subgraph/market.ts
📚 Learning: 2024-10-12T09:23:16.495Z
Learnt from: antoncoding
PR: antoncoding/monarch#63
File: app/markets/components/MarketRowDetail.tsx:49-52
Timestamp: 2024-10-12T09:23:16.495Z
Learning: When rendering oracle feeds in `ExpandedMarketDetail` (`app/markets/components/MarketRowDetail.tsx`), prefer explicit rendering over iterating keys when dealing with a small number of feeds.

Applied to files:

  • app/positions/components/onboarding/RiskSelection.tsx
🧬 Code Graph Analysis (9)
app/history/[account]/page.tsx (1)
app/history/components/HistoryContent.tsx (1)
  • HistoryContent (8-25)
src/data-sources/subgraph/transactions.ts (3)
src/data-sources/subgraph/types.ts (1)
  • SubgraphAccountData (51-57)
src/hooks/useUserTransactions.ts (2)
  • TransactionFilters (8-18)
  • TransactionResponse (20-27)
src/utils/types.ts (1)
  • UserTransaction (29-41)
src/config/oracle-whitelist.ts (1)
src/utils/networks.ts (1)
  • SupportedNetworks (52-52)
src/utils/subgraph-urls.ts (1)
src/utils/networks.ts (1)
  • SupportedNetworks (52-52)
app/markets/components/markets.tsx (1)
src/hooks/useLocalStorage.ts (1)
  • useLocalStorage (4-78)
app/positions/components/onboarding/RiskSelection.tsx (1)
src/utils/types.ts (1)
  • Market (262-310)
src/OnchainProviders.tsx (1)
src/store/createWagmiConfig.ts (1)
  • createWagmiConfig (24-67)
src/components/WithdrawModalContent.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
src/data-sources/subgraph/market.ts (2)
src/data-sources/subgraph/fetchers.ts (1)
  • subgraphGraphqlFetcher (1-31)
src/utils/subgraph-types.ts (2)
  • SubgraphMarketQueryResponse (86-91)
  • SubgraphMarketsQueryResponse (78-83)
🔇 Additional comments (17)
src/components/layout/header/AccountDropdown.tsx (1)

56-56: Good call adding a stable key.

NextUI’s DropdownMenu items expect unique keys; this avoids React warnings and selection glitches.

src/utils/subgraph-urls.ts (1)

29-29: Type migration looks good

Switch to Partial<Record<...>> is idiomatic and consistent with the rest of the PR.

src/data-sources/subgraph/historical.ts (1)

135-143: Good defensive check with optional chaining

Using !response?.data aligns with the fetcher’s non-throwing behavior and prevents crashes on partial/missing payloads.

src/data-sources/subgraph/transactions.ts (2)

16-29: Graceful handling for null/undefined subgraph data

Early-return keeps the transformer robust and avoids branching later. Nice.


212-213: Call-site update is correct

Passing result?.data?.account matches the transformer’s new nullable input and avoids exceptions.

src/data-sources/subgraph/market.ts (2)

305-325: try/catch around subgraph fetch: good

Catches network/parse errors and returns null with context logs. The follow-up checks for missing market are tidy.


358-382: Graceful handling for markets list: good

Validates data shape, logs a concise warning, fetches price hints once, and maps. Solid.

src/config/oracle-whitelist.ts (2)

14-14: Type migration to Partial<Record<...>> looks good

Matches the pattern used across the PR and keeps network keys optional.


88-95: Verify feed id for WBTC/USD entry

The id is set to the zero address while address is non-zero. Likely a copy/paste error.

Do you want me to fix the id to 0xaca1222008C6Ea624522163174F80E6e17B0709A to match the address (or another correct feed id)?

.yarnrc.yml (1)

3-3: LGTM: explicit npm registry

Setting npmRegistryServer to the npm registry is fine and consistent with Yarn 4 + nodeLinker=node-modules usage.

app/markets/page.tsx (1)

14-18: LGTM: Suspense boundary added

Wrapping MarketContent with Suspense and a simple fallback is a safe improvement.

src/components/WithdrawModalContent.tsx (1)

167-174: LGTM: onPress + isDisabled adoption

Thanks for aligning with the Button API (onPress, isDisabled). Matches prior guidance and wrapper behavior.

package.json (1)

49-49: Tooling upgrades look consistent

Next.js 15.4.7 + TS 5.6 + @types/node 22 + eslint-config-next 15.x aligns well. No blockers spotted here.

Also applies to: 88-88, 101-101, 128-128

app/positions/components/onboarding/RiskSelection.tsx (1)

206-213: onPress migration looks good

The Button’s onPress update and handler signature change are consistent with the new Button API.

src/hooks/useLocalStorage.ts (1)

11-15: SSR guards look good

Early return of initialValue on SSR and guarding the storage event listener prevent server-side crashes. Nice.

Also applies to: 59-60

app/markets/components/markets.tsx (2)

66-67: Good move: persist sort state via SSR-safe useLocalStorage

This aligns with the repo’s SSR patterns and avoids hydration issues while keeping cross-tab sync.


418-427: Button event prop migration looks consistent

onPress matches the Button API change and avoids mixing onClick and onPress in the same view.

Also applies to: 429-436

Comment thread .claude/settings.local.json Outdated
Comment on lines +89 to 92
const handleMarketDetails = (market: Market) => {
const marketPath = `/market/${market.morphoBlue.chain.id}/${market.uniqueKey}`;
window.open(marketPath, '_blank');
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix reverse tabnabbing when using window.open

Opening a new tab without noopener allows the new page to access window.opener. Use noopener,noreferrer and null out the opener.

-  const handleMarketDetails = (market: Market) => {
-    const marketPath = `/market/${market.morphoBlue.chain.id}/${market.uniqueKey}`;
-    window.open(marketPath, '_blank');
-  };
+  const handleMarketDetails = (market: Market) => {
+    const marketPath = `/market/${market.morphoBlue.chain.id}/${market.uniqueKey}`;
+    const newWin = window.open(marketPath, '_blank', 'noopener,noreferrer');
+    if (newWin) newWin.opener = null;
+  };
🤖 Prompt for AI Agents
In app/positions/components/onboarding/RiskSelection.tsx around lines 89 to 92,
the call to window.open() with target '_blank' is vulnerable to reverse
tabnabbing; change it to open with noopener and noreferrer and then explicitly
null out the opener on the returned window. Concretely, call window.open with a
thirdFeatures string that includes "noopener,noreferrer" (or use an anchor with
rel="noopener noreferrer") and assign the result to a variable, then if that
variable is non-null set its opener property to null to fully prevent access to
window.opener.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (15)
src/components/FeedInfo/OracleFeedInfo.tsx (1)

48-55: Add rel="noopener noreferrer" to external link

Avoid reverse tabnabbing on target="_blank" links.

Apply:

       <Link
         className="group flex w-full items-center gap-1 text-right no-underline hover:underline"
         href={getExplorerURL(feed.address as Address, chainId)}
-        target="_blank"
+        target="_blank"
+        rel="noopener noreferrer"
       >
app/markets/components/MarketRowDetail.tsx (1)

30-38: Harden external link

Add rel to the anchor opening the block explorer.

Apply:

           <a
             className="group flex items-center gap-1 no-underline hover:underline"
             href={getExplorerURL(market.oracleAddress, market.morphoBlue.chain.id)}
-            target="_blank"
+            target="_blank"
+            rel="noopener noreferrer"
           >
app/positions/components/agent/SetupAgent.tsx (3)

139-144: BigInt comparison bug: comparing bigint to number

cap.amount is bigint (you pass maxUint256 elsewhere). Comparing to 0 (number) will error in TS/JS. Use 0n for BigInt.

-  pendingCaps.some((cap) => cap.market.uniqueKey === market.uniqueKey && cap.amount > 0);
+  pendingCaps.some((cap) => cap.market.uniqueKey === market.uniqueKey && cap.amount > 0n);

345-351: Modal rendered inside map() creates multiple instances

When showProcessModal is true, AgentSetupProcessModal is rendered once per group. That can cause duplicated portals/overlays and accessibility issues. Render it once outside the map.

Remove the modal from inside the map:

-              {showProcessModal && (
-                <AgentSetupProcessModal
-                  currentStep={currentStep}
-                  onClose={() => setShowProcessModal(false)}
-                />
-              )}

Then render it once near the bottom (e.g., just before the final action buttons):

{showProcessModal && (
  <AgentSetupProcessModal
    currentStep={currentStep}
    onClose={() => setShowProcessModal(false)}
  />
)}

461-466: Use onPress with the shared Button component

The project migrated Button to onPress. This onClick likely won’t fire.

-                        <Button
-                          variant="light"
-                          size="sm"
-                          onClick={() => setShowAllMarkets(true)}
-                          className="w-full"
-                        >
+                        <Button
+                          variant="light"
+                          size="sm"
+                          onPress={() => setShowAllMarkets(true)}
+                          className="w-full"
+                        >
app/history/components/HistoryTable.tsx (1)

410-417: Add rel="noopener noreferrer" for external link

External target="_blank" without rel can allow tabnabbing.

-                      <Link
+                      <Link
                         href={getExplorerTxURL(tx.hash, market.morphoBlue.chain.id)}
                         target="_blank"
+                        rel="noopener noreferrer"
                         className="flex items-center gap-1 text-sm hover:underline"
                       >
src/components/layout/header/AccountDropdown.tsx (1)

62-82: Use onPress (or menu onAction) instead of onClick for DropdownItem

HeroUI/NextUI v2 components are press-event based; onClick can miss keyboard/touch interactions.

Apply this diff:

-        <DropdownItem
-          key="copy"
-          onClick={handleCopyAddress}
+        <DropdownItem
+          key="copy"
+          onPress={handleCopyAddress}
           endContent={<CopyIcon className="h-4 w-4" />}
         >
           Copy Address
         </DropdownItem>

         <DropdownItem
           key="explorer"
           endContent={<ExternalLinkIcon className="h-4 w-4" />}
-          onClick={() => window.open(getExplorerURL(address, chainId ?? 1), '_blank')}
+          onPress={() => {
+            const url = getExplorerURL(address, chainId ?? 1);
+            const w = window.open(url, '_blank', 'noopener,noreferrer');
+            if (w) w.opener = null;
+          }}
         >
           View on Explorer
         </DropdownItem>

         <DropdownItem
           key="logout"
-          onClick={handleDisconnectWallet}
+          onPress={handleDisconnectWallet}
           endContent={<ExitIcon className="h-4 w-4" />}
           className="text-red-500 data-[hover=true]:text-red-500"
         >
           Log out
         </DropdownItem>
app/positions/components/PositionsSummaryTable.tsx (2)

166-175: Button disable prop should be 'isDisabled' (Heroui), not 'disabled'

Using 'disabled' on the Heroui-based Button wrapper likely has no effect.

Apply:

-        <Button
+        <Button
           variant="light"
           size="sm"
           onPress={handleManualRefresh}
-          disabled={isRefetching}
+          isDisabled={isRefetching}
           className="font-zen text-secondary opacity-80 transition-all duration-200 ease-in-out hover:opacity-100"
         >

366-385: Rebalance Button: use 'isDisabled' instead of 'disabled'

Aligns with Heroui prop naming; prevents the button from staying clickable for non-owners.

                         <Button
                           size="sm"
                           variant="interactive"
                           className="text-xs"
                           onPress={() => {
                             if (!isOwner) {
                               toast.error(
                                 'No authorization',
                                 'You can only rebalance your own positions',
                               );
                               return;
                             }
                             setSelectedGroupedPosition(groupedPosition);
                             setShowRebalanceModal(true);
                           }}
-                          disabled={!isOwner}
+                          isDisabled={!isOwner}
                         >
                           Rebalance
                         </Button>
package.json (1)

5-19: Scripts still use yarn while packageManager is pnpm — update to pnpm.

These will fail in CI/dev if yarn isn’t present.

Apply:

   "scripts": {
-    "build": "rm -rf .next && next build",
-    "check": "yarn format && yarn lint && yarn stylelint",
+    "build": "rm -rf .next && next build",
+    "check": "pnpm run format && pnpm run lint && pnpm run stylelint",
     "dev": "next dev",
     "format": "prettier --log-level warn --write .",
     "format:check": "prettier --check .",
     "lint": "next lint --fix",
     "lint:check": "next lint",
     "start": "next start",
     "stylelint": "stylelint '**/*.css' --fix",
     "stylelint:check": "stylelint '**/*.css'",
     "test": "jest .",
     "test:coverage": "jest . --coverage",
-    "test:coverage:open": "yarn test:coverage && open coverage/lcov-report/index.html"
+    "test:coverage:open": "pnpm run test:coverage && open coverage/lcov-report/index.html"
   },
app/positions/report/components/ReportContent.tsx (1)

45-48: Bug: ZonedDateTime.set() is immutable; current code discards the result

This returns the unadjusted value (minutes/seconds not zeroed). Use the returned instance.

-  const twoMonthsAgo = useMemo(() => {
-    const date = now(getLocalTimeZone()).subtract({ months: 2 });
-    date.set({ minute: 0, second: 0 });
-    return date;
-  }, []);
+  const twoMonthsAgo = useMemo(
+    () =>
+      now(getLocalTimeZone())
+        .subtract({ months: 2 })
+        .set({ minute: 0, second: 0 }),
+    [],
+  );
app/positions/components/onboarding/SetupPositions.tsx (3)

76-94: Clear error state on valid input (Execute can stay disabled otherwise)

If the user fixes an invalid amount, error isn’t cleared. This keeps the Execute button disabled even after valid input.

Apply this diff:

   try {
     // Validate the new amount can be converted to BigInt
     parseUnits(cleanValue || '0', tokenDecimals);
+    setError(null);
     setTotalAmount(cleanValue);
   } catch (e) {
     setError('Invalid amount');
   }

225-234: Await network switch; avoid brittle fixed sleep

Not awaiting switchToNetwork() and sleeping 1s is racy. If it returns a Promise (most chain switchers do), await it and drop the sleep.

-    if (needSwitchChain && selectedToken) {
-      try {
-        switchToNetwork();
-        // Wait for network switch to complete
-        await new Promise((resolve) => setTimeout(resolve, 1000));
-      } catch (switchError) {
-        toast.error('Failed to switch network', 'Please try again');
-        return;
-      }
-    }
+    if (needSwitchChain && selectedToken) {
+      try {
+        // If switchToNetwork returns a promise, await it; otherwise Promise.resolve is a no-op.
+        await Promise.resolve(switchToNetwork());
+      } catch (switchError) {
+        toast.error('Failed to switch network', 'Please try again');
+        return;
+      }
+    }

275-282: Guard next/image src; empty string will throw

next/image errors on empty src. Provide a fallback when logoURI is missing.

-              <Image
-                src={selectedToken.logoURI ?? ''}
-                alt={selectedToken.symbol}
-                width={20}
-                height={20}
-                className="rounded-full"
-              />
+              {selectedToken.logoURI ? (
+                <Image
+                  src={selectedToken.logoURI}
+                  alt={selectedToken.symbol}
+                  width={20}
+                  height={20}
+                  className="rounded-full"
+                />
+              ) : (
+                <div className="flex h-5 w-5 items-center justify-center rounded-full bg-gray-300 text-[10px]">
+                  {selectedToken.symbol?.slice(0, 2)?.toUpperCase() ?? '?'}
+                </div>
+              )}
src/components/Borrow/AddCollateralAndBorrow.tsx (1)

86-92: BigInt math: avoid BigInt(10 ** N); use BigInt literals

Using BigInt(10 ** N) relies on Number intermediates and can introduce precision issues. Use 10n ** Nn to keep everything in BigInt space.

-        (BigInt(currentPosition.state.collateral) * oraclePrice) / BigInt(10 ** 36);
+        (BigInt(currentPosition.state.collateral) * oraclePrice) / (10n ** 36n);

-      const ltv = (currentBorrowValue * BigInt(10 ** 18)) / currentCollateralValue;
+      const ltv = (currentBorrowValue * (10n ** 18n)) / currentCollateralValue;

-    const newCollateralValueInLoan = (newCollateral * oraclePrice) / BigInt(10 ** 36);
+    const newCollateralValueInLoan = (newCollateral * oraclePrice) / (10n ** 36n);

-      const ltv = (newBorrow * BigInt(10 ** 18)) / newCollateralValueInLoan;
+      const ltv = (newBorrow * (10n ** 18n)) / newCollateralValueInLoan;

Also applies to: 104-109

♻️ Duplicate comments (1)
.claude/settings.local.json (1)

5-5: Replace invalid Next lint allow-list entry with concrete commands

"Bash(npx next lint:*)" isn’t a valid command pattern for this permissions schema. Allow explicit invocations instead.

-      "Bash(npx next lint:*)",
+      "Bash(npx next lint)",
+      "Bash(npx next lint --fix)",
🧹 Nitpick comments (40)
.claude/settings.local.json (3)

19-19: Confirm the web fetch domain scope is sufficient

If the UI migration needs docs or assets from non-www subdomains (e.g., heroui.com root, cdn), you may need additional entries. If www.heroui.com is all you need, this is fine.


1-23: Did you intend to commit a “local” settings file?

The filename suggests developer-local config. If this wasn’t meant to be versioned, add it to .gitignore and commit a settings.local.example.json instead.


4-18: Scope allow-list to only the commands you actually run

Your current .claude/settings.local.json is very broad and includes unused tools (pnpm, find, generic node, git checkout, etc.) and wildcards. Let’s tighten it to exactly the scripts in your package.json plus any other CLI checks you need:

Recommended permissions.allow block:

"permissions": {
  "allow": [
    "Bash(yarn install)",
    "Bash(yarn dev)",
    "Bash(yarn build)",
    "Bash(NEXT_TELEMETRY_DISABLED=1 yarn build)",
    "Bash(yarn check)",
    "Bash(yarn format)",
    "Bash(yarn format:check)",
    "Bash(yarn lint)",
    "Bash(yarn lint:check)",
    "Bash(yarn stylelint)",
    "Bash(yarn stylelint:check)",
    "Bash(yarn test)",
    "Bash(yarn test:coverage)",
    "Bash(yarn test:coverage:open)",
    "Bash(yarn start)",
    "Bash(node -v)",
    "Bash(node --version)"
  ]
}

• Removed all :* patterns to enforce least privilege
• Dropped pnpm, find, git checkout, generic node:*, npm run wildcards

Optional helper to regenerate from your scripts:

jq -r '.scripts | keys[] | "Bash(yarn \(.))"' package.json
app/positions/components/onboarding/AssetSelection.tsx (3)

110-115: Nit: add type="button" and drop redundant role="button"

Prevents unintended form submission if nested in a form; native button already has the correct role.

-            <motion.button
-              aria-label={`Select ${token.symbol} on ${getNetworkName(token.network)}`}
-              role="button"
+            <motion.button
+              type="button"
+              aria-label={`Select ${token.symbol} on ${getNetworkName(token.network)}`}
               key={`${token.symbol}-${token.network}-${idx}`}
               onClick={() => handleTokenSelect(token)}
               className="group relative flex items-start gap-4 rounded border border-gray-200 bg-white p-4 text-left transition-all duration-300 hover:border-primary dark:border-gray-700 dark:bg-gray-800/50 dark:hover:bg-gray-800"

101-106: Avoid nested interactive elements (Button inside Link)

This renders a button inside an anchor in the DOM. Prefer a polymorphic Button that renders as a Link.

-          <Link href="/markets">
-            <Button color="primary" className="rounded">
-              View Markets
-            </Button>
-          </Link>
+          <Button as={Link} href="/markets" color="primary" className="rounded">
+            View Markets
+          </Button>

If your Button doesn’t support as/href, consider making a styled anchor or updating the Button to be polymorphic.


113-113: Prefer a stable key (address + network) over index

Improves reconciliation stability.

-              key={`${token.symbol}-${token.network}-${idx}`}
+              key={`${token.address}-${token.network}`}
app/markets/components/NetworkFilter.tsx (1)

18-24: Prefer onSelectionChange over onChange for HeroUI/NextUI Select

Relying on e.target.value is brittle here; the Select is not a native select. Use onSelectionChange and derive the single key. This also avoids SSR/hydration surprises.

Apply this diff:

-      onChange={(e) => {
-        if (!e.target.value) setSelectedNetwork(null);
-        const newId = Number(e.target.value);
-        if (newId && isSupportedChain(newId)) {
-          setSelectedNetwork(newId);
-        }
-      }}
+      onSelectionChange={(keys) => {
+        const key =
+          keys === 'all'
+            ? null
+            : Array.from(keys as Set<string>)[0] ?? null;
+        if (!key) {
+          setSelectedNetwork(null);
+          return;
+        }
+        const newId = Number(key);
+        if (isSupportedChain(newId)) {
+          setSelectedNetwork(newId);
+        }
+      }}
src/components/FeedInfo/OracleFeedInfo.tsx (1)

44-47: Tooltip styling: prefer classNames over className

HeroUI/NextUI Tooltips typically style the popover via classNames.content. Using className may be ignored.

Proposed change:

     <Tooltip
       content={feed.description ?? getSlicedAddress(feed.address as Address)}
-      className="rounded-sm"
+      classNames={{ content: 'rounded-sm' }}
     >
app/markets/components/MarketRowDetail.tsx (1)

43-55: Tooltip popover styling: use classNames.content

Ensures the max-width/rounded/padding apply to the tooltip panel across libraries.

Apply:

-              <Tooltip
-                content={
+              <Tooltip
+                content={
                   <TooltipContent
                     icon={<QuestionMarkCircledIcon className="h-4 w-4" />}
                     title="Feed Routes"
                     detail="Feed routes show how asset prices are derived from different oracle providers"
                   />
-                }
-                className="max-w-[400px] rounded-sm p-2"
-              >
+                }
+                classNames={{ content: 'max-w-[400px] rounded-sm p-2' }}
+              >
app/admin/stats/page.tsx (1)

53-74: Noise from debug logs

Consider gating console logs with an env flag or removing to keep the UI quiet in production.

Example:

-        console.log(
+        if (process.env.NEXT_PUBLIC_DEBUG === '1') console.log(
           `Fetching statistics for timeframe: ${timeframe}, network: ${
             getNetworkName(selectedNetwork) ?? 'Unknown'
           }`,
         );
-        const startTime = performance.now();
+        const startTime = process.env.NEXT_PUBLIC_DEBUG === '1' ? performance.now() : 0;
...
-        const endTime = performance.now();
-        console.log(`Statistics fetched in ${endTime - startTime}ms:`, allStats);
-        console.log('Platform stats:', allStats.platformStats);
-        console.log('Asset metrics count:', allStats.assetMetrics.length);
+        if (process.env.NEXT_PUBLIC_DEBUG === '1') {
+          const endTime = performance.now();
+          console.log(`Statistics fetched in ${endTime - startTime}ms:`, allStats);
+          console.log('Platform stats:', allStats.platformStats);
+          console.log('Asset metrics count:', allStats.assetMetrics.length);
+        }
src/components/RiskNotificationModal.tsx (1)

40-40: Make non-dismissable intent explicit

Using onClose={() => {}} blocks close handlers implicitly. Prefer isDismissable={false} for clarity and to prevent backdrop/esc key closing.

Apply:

-    <Modal isOpen={isOpen} onClose={() => {}} hideCloseButton size="3xl" scrollBehavior="inside">
+    <Modal
+      isOpen={isOpen}
+      onClose={() => {}}
+      isDismissable={false}
+      hideCloseButton
+      size="3xl"
+      scrollBehavior="inside"
+    >
app/markets/components/Pagination.tsx (2)

2-2: Minor: rename alias for clarity

The alias still says NextUI. Optional rename to avoid confusion during the HeroUI migration.

Apply:

-import { Pagination as NextUIPagination } from '@heroui/react';
+import { Pagination as HeroUIPagination } from '@heroui/react';

And update usage:

-        <NextUIPagination
+        <HeroUIPagination

31-33: Controlled Pagination: drop initialPage

With a controlled page prop, initialPage is redundant and can confuse future readers.

Apply:

-          initialPage={1}
app/market/[chainId]/[marketid]/RateChart.tsx (2)

4-5: UI lib migration looks fine; consider merging imports

The switch to @heroui/react for Card/Progress aligns with the PR goal. You can merge these two imports into one for tidiness.

-import { Card, CardHeader, CardBody } from '@heroui/react';
-import { Progress } from '@heroui/react';
+import { Card, CardHeader, CardBody, Progress } from '@heroui/react';

57-57: Remove stray console.log

Leftover debug log can be noisy in production.

-  console.log('market', market);
+  // console.log('market', market);
app/rewards/components/RewardContent.tsx (1)

80-81: Fix small typos in comments

Keeps the codebase clean.

-          // go through all posible keys of rewad object: for_supply, for_borrow, for_collateral}
+          // go through all possible keys of reward object: for_supply, for_borrow, for_collateral
src/components/TokenIcon.tsx (1)

6-13: symbol prop is unused; consider using it for fallback alt/content

Either use symbol when token lookup fails (for better a11y) or remove the prop if it’s not needed.

Example usage in fallback:

// Fallback to placeholder
return (
  <div
    aria-label={symbol ?? 'token'}
    className="rounded-full bg-gray-300 dark:bg-gray-700"
    style={{ width, height }}
  />
);
app/positions/components/agent/SetupAgent.tsx (1)

203-221: Simplify effect; the mounted flag is unnecessary

The effect body is synchronous; the mounted guard doesn’t add value.

-  useEffect(() => {
-    let mounted = true;
+  useEffect(() => {
     if (!hasPreselected && groupedMarkets.length > 0 && targetNetwork) {
       groupedMarkets.forEach((group) => {
         group.activeMarkets.forEach((market) => {
           if (!isInPending(market) && !isInPendingRemove(market)) {
             addToPendingCaps(market, maxUint256);
           }
         });
       });
-      if (mounted) {
-        setHasPreselected(true);
-      }
+      setHasPreselected(true);
     }
-
-    return () => {
-      mounted = false;
-    };
   }, [
     hasPreselected,
     groupedMarkets,
     isInPending,
     isInPendingRemove,
     addToPendingCaps,
     targetNetwork,
   ]);
app/market/[chainId]/[marketid]/components/PositionStats.tsx (1)

207-217: Make Switch controlled to avoid state drift

Using defaultSelected makes the Switch uncontrolled and can desync from viewMode over time. HeroUI supports isSelected and onValueChange returning the boolean.

Apply:

-          <Switch
-            defaultSelected={viewMode === 'user'}
+          <Switch
+            isSelected={viewMode === 'user'}
             size="sm"
             color="primary"
             classNames={{
               wrapper: 'mx-0',
               thumbIcon: 'p-0 mr-0',
             }}
-            onChange={toggleView}
+            onValueChange={(selected) => setViewMode(selected ? 'user' : 'global')}
             thumbIcon={ThumbIcon}
           />
app/history/components/HistoryTable.tsx (1)

353-358: Use Next.js Link for internal navigation (optional)

For client-side routing and prefetch, prefer next/link for internal routes.

-                      <Link
-                        href={`/market/${market.morphoBlue.chain.id}/${market.uniqueKey}`}
-                        className="font-monospace text-xs hover:underline"
-                      >
+                      <NextLink
+                        href={`/market/${market.morphoBlue.chain.id}/${market.uniqueKey}`}
+                        className="font-monospace text-xs hover:underline"
+                      >
                         {market.uniqueKey.slice(2, 8)}
-                      </Link>
+                      </NextLink>

Also add this import at the top of the file:

import NextLink from 'next/link';
tailwind.config.ts (1)

78-83: Stale selector: consider covering HeroUI’s button root

The selector still targets .nextui-button. If HeroUI changed the root class, styles may not apply.

Apply this diff to safely cover both:

-        'button, .nextui-button': {
+        'button, .nextui-button, .ui-button': {
           '@apply rounded': {}, // This makes all buttons rounded by default
         },

If HeroUI doesn’t use .ui-button, keep as-is. Verify in the browser DevTools which class is present on Button root.

src/components/layout/header/AccountDropdown.tsx (1)

36-44: Improve accessibility of the trigger

A div as trigger lacks semantics. Add role/button affordances.

-        <div
+        <div
+          role="button"
+          aria-label="Open account menu"
           className={clsx(
             'flex h-8 w-8 cursor-pointer items-center justify-center',
             'transition-all duration-150 ease-in-out hover:-translate-y-[2px]',
           )}
         >
src/components/common/DatePicker.tsx (1)

4-11: Type the classNames and allow overrides; spread remaining props

This guards against key typos and reduces boilerplate when DatePicker surfaces new props.

-function StyledDatePicker(props: DatePickerProps) {
-  const classNames = {
+function StyledDatePicker(props: DatePickerProps) {
+  const { classNames: classNamesProp, ...rest } = props;
+  const classNames: NonNullable<DatePickerProps['classNames']> = {
     base: 'h-[88px]', // Reserve space for error message
     inputWrapper: ['bg-surface', 'rounded-sm', 'w-64', 'h-14'],
     label: 'text-xs text-gray-500',
     input: 'text-sm',
     errorMessage: 'text-xs text-red-500 absolute bottom-0 left-0',
     selectorButton: ['hover:text-primary'],
-  };
+  , ...(classNamesProp ?? {}) };
 
   return (
     <DatePicker
-      label={props.label}
-      variant={props.variant}
-      value={props.value}
+      {...rest}
       classNames={classNames}
-      minValue={props.minValue}
-      maxValue={props.maxValue}
-      onChange={props.onChange}
-      isInvalid={props.isInvalid}
-      errorMessage={props.errorMessage}
-      granularity={props.granularity}
     />
   );
 }

Also applies to: 18-18

src/components/common/Button.tsx (1)

44-46: isDisabled should be boolean, not string

As written, the compound variant may never match.

-    {
-      isDisabled: 'true',
-      class: 'opacity-50 cursor-not-allowed pointer-events-none',
-    },
+    {
+      isDisabled: true,
+      class: 'opacity-50 cursor-not-allowed pointer-events-none',
+    },
package.json (1)

22-31: Audit @heroui/ imports*

A scan of all .ts/.tsx files shows only @heroui/react is ever imported. You can safely remove these unused sub-packages from package.json to slim your bundle:

• @heroui/accordion
• @heroui/button
• @heroui/checkbox
• @heroui/input
• @heroui/system
• @heroui/table
• @heroui/theme
• @heroui/tooltip

app/markets/components/markets.tsx (1)

174-192: Preserve unrelated query params and avoid pushing a bare “?”.

Rebuilding from an empty URLSearchParams drops any unrelated query keys (e.g., utm) and pushing “?” when empty is noisy.

-  const updateUrlParams = useCallback(
-    (collaterals: string[], loanAssets: string[], network: SupportedNetworks | null) => {
-      const params = new URLSearchParams();
-      
-      if (collaterals.length > 0) {
-        params.set('collaterals', collaterals.join(','));
-      }
-      if (loanAssets.length > 0) {
-        params.set('loanAssets', loanAssets.join(','));
-      }
-      if (network) {
-        params.set('network', network.toString());
-      }
-      
-      const newParams = params.toString();
-      router.push(`?${newParams}`, { scroll: false });
-    },
-    [router],
-  );
+  const updateUrlParams = useCallback(
+    (collaterals: string[], loanAssets: string[], network: SupportedNetworks | null) => {
+      const params = new URLSearchParams(
+        typeof window !== 'undefined' ? window.location.search : '',
+      );
+
+      if (collaterals.length > 0) params.set('collaterals', collaterals.join(','));
+      else params.delete('collaterals');
+
+      if (loanAssets.length > 0) params.set('loanAssets', loanAssets.join(','));
+      else params.delete('loanAssets');
+
+      if (network) params.set('network', network.toString());
+      else params.delete('network');
+
+      const qs = params.toString();
+      const path =
+        typeof window !== 'undefined' ? window.location.pathname : '';
+      router.push(qs ? `${path}?${qs}` : path, { scroll: false });
+    },
+    [router],
+  );
app/markets/page.tsx (1)

34-41: Trim/dedupe parsed lists.

Small polish to avoid empty strings/duplicates leaking into client state.

-  const collaterals = params.collaterals 
-    ? params.collaterals.split(',').filter(Boolean) 
-    : [];
+  const collaterals = params.collaterals
+    ? Array.from(new Set(params.collaterals.split(',').map(s => s.trim()).filter(Boolean)))
+    : [];
     
-  const loanAssets = params.loanAssets 
-    ? params.loanAssets.split(',').filter(Boolean) 
-    : [];
+  const loanAssets = params.loanAssets
+    ? Array.from(new Set(params.loanAssets.split(',').map(s => s.trim()).filter(Boolean)))
+    : [];
app/positions/report/components/ReportTable.tsx (5)

4-5: Combine HeroUI imports; confirm Table API parity

Merge these two imports into one and verify the Table classNames API matches HeroUI’s (e.g., "th", "wrapper" keys).

-import { DateValue } from '@heroui/react';
-import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react';
+import {
+  DateValue,
+  Table,
+  TableHeader,
+  TableBody,
+  TableColumn,
+  TableRow,
+  TableCell,
+} from '@heroui/react';

303-313: Add rel="noopener noreferrer" for external link security

Targeting _blank without rel is a security footgun. Add rel to prevent tabnabbing.

-                                    <Link
+                                    <Link
                                       href={getExplorerTxURL(
                                         tx.hash,
                                         marketReport.market.morphoBlue.chain.id,
                                       )}
                                       target="_blank"
+                                      rel="noopener noreferrer"
                                       className="flex items-center gap-1 text-gray-500 hover:text-gray-700"
                                     >

24-33: Stop passing unused props to MarketSummaryBlock

amount and avgCapital aren’t used inside MarketSummaryBlock. Drop them from the prop type and callsite.

 type MarketInfoBlockProps = {
   market: Market;
   interestEarned: bigint;
-  amount: bigint;
   decimals: number;
   symbol: string;
   apy: number;
-  avgCapital: bigint;
   hasActivePosition: boolean;
 };
                     <MarketSummaryBlock
                       market={marketReport.market}
                       interestEarned={marketReport.interestEarned}
-                      amount={marketReport.endBalance}
                       decimals={asset.decimals}
                       symbol={asset.symbol}
                       apy={marketReport.apy}
-                      avgCapital={marketReport.avgCapital}
                       hasActivePosition={hasActivePosition}
                     />

Also applies to: 225-235


196-205: Avoid Number() coercion on BigInt during sort

Coercing BigInt deltas to Number can overflow. Compare BigInts directly.

-              // Then sort by interest earned
-              return Number(b.interestEarned - a.interestEarned);
+              // Then sort by interest earned
+              if (b.interestEarned === a.interestEarned) return 0;
+              return b.interestEarned > a.interestEarned ? 1 : -1;

59-61: Comment vs. code mismatch: actually trim trailing zeros

You say “remove trailing zeros” but return toFixed(2) unchanged. Implement trimming.

-  // Otherwise, round to 2 decimal places and remove trailing zeros
-  return days.toFixed(2);
+  // Otherwise, round to 2 decimal places and remove trailing zeros
+  return parseFloat(days.toFixed(2)).toString();
app/positions/report/components/ReportContent.tsx (2)

212-216: Remove any-casts around DatePicker; keep type-safety

Avoid disabling ESLint and casting to any. Adapt the handler inline to the expected DateValue.

-                  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-                  value={startDate as any}
-                  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-                  onChange={handleStartDateChange as any}
+                  value={startDate as DateValue}
+                  onChange={(d) => handleStartDateChange(d as ZonedDateTime)}
-                  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-                  value={endDate as any}
-                  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-                  onChange={handleEndDateChange as any}
+                  value={endDate as DateValue}
+                  onChange={(d) => handleEndDateChange(d as ZonedDateTime)}

If you’d like, I can help adjust the local DatePicker component to be generic over T extends DateValue so callers don’t need casts.

Also applies to: 225-229


167-181: Guard against positions being undefined in useMemo

positions can be undefined (you treat it as such elsewhere). Prevent a runtime error.

-    positions.forEach((position) => {
+    const list = positions ?? [];
+    list.forEach((position) => {
       const key = `${position.market.loanAsset.address}-${position.market.morphoBlue.chain.id}`;
       if (!assetMap.has(key)) {
         assetMap.set(key, {
           symbol: position.market.loanAsset.symbol,
           address: position.market.loanAsset.address as Address,
           chainId: position.market.morphoBlue.chain.id,
           decimals: position.market.loanAsset.decimals,
         });
       }
     });

If useUserPositions already guarantees data is [], feel free to skip—otherwise this avoids crashes on initial render.

app/positions/components/onboarding/SetupPositions.tsx (3)

96-107: Use functional setState for Set to avoid stale closures

Minor, but this avoids capturing stale lockedAmounts and is cheaper on re-renders.

-  const toggleLockAmount = useCallback(
-    (marketKey: string) => {
-      const newLockedAmounts = new Set(lockedAmounts);
-      if (lockedAmounts.has(marketKey)) {
-        newLockedAmounts.delete(marketKey);
-      } else {
-        newLockedAmounts.add(marketKey);
-      }
-      setLockedAmounts(newLockedAmounts);
-    },
-    [lockedAmounts],
-  );
+  const toggleLockAmount = useCallback((marketKey: string) => {
+    setLockedAmounts((prev) => {
+      const next = new Set(prev);
+      if (next.has(marketKey)) next.delete(marketKey);
+      else next.add(marketKey);
+      return next;
+    });
+  }, []);

62-74: Avoid Number-based math for token amounts (precision loss)

Using Number(totalAmount) can lose precision for large values/decimals. Do math in units with BigInt and format back.

-    if (totalAmount && Object.keys(percentages).length > 0) {
-      const newAmounts = Object.entries(percentages).reduce(
-        (acc, [key, percentage]) => {
-          acc[key] = ((percentage / 100) * Number(totalAmount)).toFixed(tokenDecimals);
-          return acc;
-        },
-        {} as Record<string, string>,
-      );
-      setAmounts(newAmounts);
-    }
+    if (totalAmount && Object.keys(percentages).length > 0) {
+      try {
+        const totalUnits = parseUnits(totalAmount, tokenDecimals);
+        const newAmounts = Object.entries(percentages).reduce((acc, [key, percentage]) => {
+          const units = (totalUnits * BigInt(Math.round(percentage * 10000))) / BigInt(100 * 10000);
+          acc[key] = formatUnits(units, tokenDecimals);
+          return acc;
+        }, {} as Record<string, string>);
+        setAmounts(newAmounts);
+      } catch {
+        // ignore until totalAmount is valid
+      }
+    }

333-335: Defensive guard for Slider’s onChange value type

HeroUI’s onChange can be number | number[] (range). You’re not using range, but a tiny guard prevents accidental NaN if the prop changes.

-                              onChange={(value) =>
-                                handlePercentageChange(market.uniqueKey, Number(value))
-                              }
+                              onChange={(value) => {
+                                const v = Array.isArray(value) ? value[0] : value;
+                                handlePercentageChange(market.uniqueKey, Number(v));
+                              }}
src/components/Borrow/AddCollateralAndBorrow.tsx (3)

341-343: Use a wrapper to avoid unhandled rejections (consistency with other buttons)

Elsewhere you wrap async calls with () => void .... Do the same here for consistency and to avoid unhandled rejections if switchToNetwork returns a Promise.

-                <Button onPress={switchToNetwork} className="min-w-32" variant="solid">
+                <Button onPress={() => void switchToNetwork()} className="min-w-32" variant="solid">

225-233: Compare BigInt with BigInt literals (0n)

Minor type-safety/consistency tweak: use 0n when comparing BigInt values.

-                    borrowAmount > 0 || collateralAmount > 0 ? newLTV : currentLTV,
+                    borrowAmount > 0n || collateralAmount > 0n ? newLTV : currentLTV,

And similarly a few lines below:

-                      (Number(borrowAmount > 0 || collateralAmount > 0 ? newLTV : currentLTV) /
+                      (Number(borrowAmount > 0n || collateralAmount > 0n ? newLTV : currentLTV) /

373-377: Strict equality for BigInt

Use === with BigInt to avoid implicit coercion.

-                  {collateralAmount > 0n && borrowAmount == 0n ? 'Add Collateral' : 'Borrow'}
+                  {collateralAmount > 0n && borrowAmount === 0n ? 'Add Collateral' : 'Borrow'}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 638b810 and 0d36b0c.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (56)
  • .claude/settings.local.json (1 hunks)
  • .yarnrc.yml (0 hunks)
  • app/admin/stats/components/AssetMetricsTable.tsx (1 hunks)
  • app/admin/stats/components/StatsOverviewCards.tsx (1 hunks)
  • app/admin/stats/page.tsx (1 hunks)
  • app/history/components/HistoryTable.tsx (1 hunks)
  • app/info/components/info.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/RateChart.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/VolumeChart.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/components/BorrowsTable.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/components/PositionStats.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/components/SuppliesTable.tsx (1 hunks)
  • app/market/[chainId]/[marketid]/content.tsx (2 hunks)
  • app/markets/components/AdvancedSearchBar.tsx (1 hunks)
  • app/markets/components/MarketRowDetail.tsx (1 hunks)
  • app/markets/components/MarketSettingsModal.tsx (3 hunks)
  • app/markets/components/MarketTableBody.tsx (2 hunks)
  • app/markets/components/NetworkFilter.tsx (1 hunks)
  • app/markets/components/Pagination.tsx (1 hunks)
  • app/markets/components/RiskIndicator.tsx (1 hunks)
  • app/markets/components/markets.tsx (8 hunks)
  • app/markets/page.tsx (2 hunks)
  • app/positions/components/FromAndToMarkets.tsx (2 hunks)
  • app/positions/components/PositionsSummaryTable.tsx (3 hunks)
  • app/positions/components/RebalanceCart.tsx (1 hunks)
  • app/positions/components/RebalanceModal.tsx (2 hunks)
  • app/positions/components/SuppliedMarketsDetail.tsx (3 hunks)
  • app/positions/components/agent/Main.tsx (1 hunks)
  • app/positions/components/agent/SetupAgent.tsx (1 hunks)
  • app/positions/components/agent/SetupAgentModal.tsx (1 hunks)
  • app/positions/components/onboarding/AssetSelection.tsx (1 hunks)
  • app/positions/components/onboarding/Modal.tsx (2 hunks)
  • app/positions/components/onboarding/RiskSelection.tsx (3 hunks)
  • app/positions/components/onboarding/SetupPositions.tsx (1 hunks)
  • app/positions/report/components/ReportContent.tsx (3 hunks)
  • app/positions/report/components/ReportTable.tsx (1 hunks)
  • app/rewards/components/InfoCard.tsx (1 hunks)
  • app/rewards/components/RewardContent.tsx (1 hunks)
  • app/rewards/components/RewardTable.tsx (2 hunks)
  • app/settings/page.tsx (1 hunks)
  • package.json (6 hunks)
  • src/components/Borrow/AddCollateralAndBorrow.tsx (4 hunks)
  • src/components/FeedInfo/OracleFeedInfo.tsx (1 hunks)
  • src/components/OracleVendorBadge.tsx (1 hunks)
  • src/components/RiskNotificationModal.tsx (1 hunks)
  • src/components/SupplyModalContent.tsx (4 hunks)
  • src/components/TokenIcon.tsx (1 hunks)
  • src/components/common/Button.tsx (1 hunks)
  • src/components/common/DatePicker.tsx (1 hunks)
  • src/components/common/MarketInfoBlock.tsx (1 hunks)
  • src/components/layout/header/AccountDropdown.tsx (2 hunks)
  • src/components/layout/header/Navbar.tsx (1 hunks)
  • src/components/providers/ThemeProvider.tsx (2 hunks)
  • src/store/createWagmiConfig.ts (0 hunks)
  • tailwind.config.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • src/store/createWagmiConfig.ts
  • .yarnrc.yml
✅ Files skipped from review due to trivial changes (2)
  • app/admin/stats/components/AssetMetricsTable.tsx
  • app/rewards/components/InfoCard.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/components/SupplyModalContent.tsx
  • app/positions/components/SuppliedMarketsDetail.tsx
  • app/positions/components/onboarding/RiskSelection.tsx
  • app/markets/components/MarketSettingsModal.tsx
  • app/rewards/components/RewardTable.tsx
  • app/positions/components/onboarding/Modal.tsx
  • app/market/[chainId]/[marketid]/content.tsx
  • app/positions/components/FromAndToMarkets.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-12-16T02:01:51.219Z
Learnt from: antoncoding
PR: antoncoding/monarch#97
File: app/positions/components/onboarding/OnboardingContext.tsx:36-43
Timestamp: 2024-12-16T02:01:51.219Z
Learning: In `app/positions/components/onboarding/OnboardingContext.tsx`, the `defaultStep` variable is no longer needed and can be removed.

Applied to files:

  • app/positions/components/onboarding/SetupPositions.tsx
📚 Learning: 2024-12-17T10:51:07.277Z
Learnt from: antoncoding
PR: antoncoding/monarch#99
File: src/components/AgentSetupProcessModal.tsx:38-51
Timestamp: 2024-12-17T10:51:07.277Z
Learning: In `src/components/AgentSetupProcessModal.tsx`, the modal is intentionally designed without a close button.

Applied to files:

  • app/positions/components/agent/SetupAgentModal.tsx
📚 Learning: 2024-10-12T09:23:16.495Z
Learnt from: antoncoding
PR: antoncoding/monarch#63
File: app/markets/components/MarketRowDetail.tsx:49-52
Timestamp: 2024-10-12T09:23:16.495Z
Learning: When rendering oracle feeds in `ExpandedMarketDetail` (`app/markets/components/MarketRowDetail.tsx`), prefer explicit rendering over iterating keys when dealing with a small number of feeds.

Applied to files:

  • app/markets/components/markets.tsx
🧬 Code Graph Analysis (3)
src/components/Borrow/AddCollateralAndBorrow.tsx (1)
src/components/common/Button.tsx (1)
  • Button (4-53)
app/markets/page.tsx (3)
app/positions/[account]/page.tsx (1)
  • MarketPage (12-14)
app/info/page.tsx (1)
  • MarketPage (12-14)
src/utils/networks.ts (1)
  • SupportedNetworks (52-52)
app/markets/components/markets.tsx (3)
src/utils/networks.ts (1)
  • SupportedNetworks (52-52)
src/utils/tokens.ts (2)
  • ERC20Token (17-29)
  • UnknownERC20Token (31-37)
src/hooks/useLocalStorage.ts (1)
  • useLocalStorage (4-78)
🔇 Additional comments (51)
app/positions/components/onboarding/AssetSelection.tsx (2)

142-156: Confirm Tooltip prop compatibility after the swap

HeroUI’s Tooltip generally keeps NextUI’s API. If styling doesn’t apply as expected, consider using classNames={{ content: '...' }} or contentProps for the overlay.


2-2: No leftover NextUI imports detected

HeroUI Tooltip import is correct, and no @nextui-org/react references remain.

app/admin/stats/components/StatsOverviewCards.tsx (1)

2-2: No stray NextUI imports found—ready to merge.

All Card/CardBody imports have been successfully switched to @heroui/react.

src/components/common/MarketInfoBlock.tsx (1)

2-2: Tooltip import migrated to HeroUI — API matches

content prop and className usage align with @heroui/react Tooltip.

app/info/components/info.tsx (1)

6-6: Button import moved to HeroUI and onPress is already in use

Props (isIconOnly, variant="flat", color="primary", onPress) match HeroUI’s API.

app/markets/components/AdvancedSearchBar.tsx (1)

3-3: Input import migrated to HeroUI — props line up

onValueChange, endContent, and classNames keys (inputWrapper, input) are supported. The forwarded ref should still resolve to HTMLInputElement.

If TypeScript complains about the ref type after the library swap, adjust to:

const inputRef = useRef<HTMLInputElement>(null);

(then confirm no TS errors around the ref assignment).

app/markets/components/RiskIndicator.tsx (1)

1-1: Tooltip import swap to @heroui/react is fine

content prop and styling remain compatible.

app/market/[chainId]/[marketid]/VolumeChart.tsx (1)

4-4: Card import migrated to @heroui/react — looks good

No behavior change; Card, CardHeader, CardBody usage is compatible.

src/components/OracleVendorBadge.tsx (1)

2-2: Tooltip import migrated to @heroui/react — looks good

Usage (content prop, className) remains compatible.

app/markets/components/NetworkFilter.tsx (1)

2-2: Select/SelectItem import migrated to @heroui/react — looks good

Matches the broader UI migration.

app/positions/components/agent/SetupAgentModal.tsx (2)

2-2: Modal import migrated to @heroui/react — looks good

No structural changes; props and classNames remain the same.


165-165: Confirm close button prop compatibility with HeroUI

Design calls for no close button (learned from prior PR). Verify that closeButton={false} is still respected in @heroui/react. If not, switch to the supported prop (often hideCloseButton) on the correct element (Modal or ModalContent), e.g.:

<Modal ...>
  <ModalContent hideCloseButton className="p-4">
    ...
  </ModalContent>
</Modal>

Also confirm backdrop/esc behavior matches expectations (no accidental dismissal if that’s undesired).

app/settings/page.tsx (1)

3-3: Switch import migrated to @heroui/react — looks good

defaultSelected and onValueChange align with the expected API.

src/components/FeedInfo/OracleFeedInfo.tsx (1)

1-1: HeroUI Tooltip import looks fine

Import switch aligns with the migration. No API differences surfaced here.

app/markets/components/MarketRowDetail.tsx (1)

1-1: HeroUI Tooltip import looks fine

Matches the UI library migration.

app/admin/stats/page.tsx (1)

4-5: HeroUI imports: good

Button and Dropdown moved to @heroui/react; usage matches expected props.

src/components/RiskNotificationModal.tsx (1)

12-12: HeroUI import switch looks good

Modal, Button, Checkbox props match the new package.

app/markets/components/Pagination.tsx (1)

2-2: HeroUI Pagination import: OK

Import alias remains; component usage unchanged.

app/market/[chainId]/[marketid]/RateChart.tsx (1)

244-256: Double-check Progress API parity after library switch

Props like showValueLabel and classNames.value/base/label should match HeroUI’s Progress API. Worth a quick sanity check since subtle prop name changes can slip in during library migrations.

src/components/layout/header/Navbar.tsx (1)

4-4: Dropdown import migration LGTM; verify event handling expectations

The import swap to @heroui/react is consistent. Since other components migrated onClick→onPress/onAction, confirm DropdownItem still supports onClick here or consider centralizing with DropdownMenu onAction.

app/rewards/components/RewardContent.tsx (2)

4-4: Switch/Tooltip import migration LGTM; confirm prop compatibility

isSelected/onValueChange for Switch and content/placement for Tooltip should be API-compatible in HeroUI. Please confirm at runtime.


270-276: Nice: controlled Switch wiring is correct

Using isSelected + onValueChange keeps it controlled and predictable.

src/components/TokenIcon.tsx (1)

2-2: Tooltip import migration LGTM

No behavioral changes apparent.

app/positions/components/agent/SetupAgent.tsx (1)

9-9: UI lib import migration LGTM

Importing Checkbox/Dropdown suite from @heroui/react matches the repo-wide change.

app/market/[chainId]/[marketid]/components/PositionStats.tsx (1)

2-3: UI import migration to @heroui/react looks good

Card and Switch swap reads clean. No other API mismatches spotted at a glance.

app/positions/components/agent/Main.tsx (1)

1-1: Tooltip migration to @heroui/react is fine

Props usage (content, className) matches HeroUI’s Tooltip API.

app/market/[chainId]/[marketid]/components/BorrowsTable.tsx (1)

2-2: Table/Link/Pagination migration to @heroui/react looks correct

Table props (classNames, bottomContent) and Pagination (onChange, page/total) align with HeroUI.

src/components/providers/ThemeProvider.tsx (1)

3-16: HeroUIProvider substitution LGTM

Provider move to HeroUI is correct and placement within NextThemesProvider is fine.

app/history/components/HistoryTable.tsx (1)

3-3: Consolidated imports to @heroui/react look good

Chip, Link, Pagination, and Table family from HeroUI are consistent with usage below.

app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx (1)

2-2: HeroUI import swap looks good

Consolidated import aligns with the repo-wide migration. No usage issues spotted.

tailwind.config.ts (1)

6-6: Theme plugin/content migration looks correct

  • heroui() plugin and content glob updated properly.
  • Should integrate with the rest of the HeroUI changes without breaking token generation.

Also applies to: 12-13, 55-76

src/components/layout/header/AccountDropdown.tsx (1)

4-4: UI import migration LGTM

Dropdown components now sourced from @heroui/react; usage remains consistent.

src/components/common/DatePicker.tsx (1)

1-1: Import migration OK

Switch to @heroui/react matches the rest of the PR.

src/components/common/Button.tsx (1)

2-2: Import source updated — looks good

extendVariants is available from @heroui/react; no API changes here.

app/positions/components/RebalanceCart.tsx (1)

2-2: ✅ Dependency swap complete

  • package.json lists “@heroui/react”: “^2.4.2” and no “@nextui-org/react”
  • All component imports now reference “@heroui/react”
app/markets/components/MarketTableBody.tsx (2)

2-2: Tooltip import migrated to @heroui/react

Looks consistent with the broader UI migration.


170-179: Approve: Button handlers migrated to onPress
Repo-wide search found no <Button onClick=…> usages; the common Button correctly forwards onPress. All set.

app/positions/components/RebalanceModal.tsx (2)

2-2: Modal import switched to @heroui/react

Props align with Heroui Modal (isOpen, onClose, isDismissable, classNames). No behavioral changes.


302-310: Refresh Button: onPress migration

Event change only; behavior intact. Spinner and disable logic are preserved.

app/positions/components/PositionsSummaryTable.tsx (1)

10-10: Heroui import consolidation

Dropdown/Tooltip/Switch and the aliased Button now come from @heroui/react. Reads clean.

app/market/[chainId]/[marketid]/components/SuppliesTable.tsx (1)

2-2: HeroUI import looks fine; double-check prop parity (Pagination/Table).

Given the library swap, confirm Pagination’s onChange(page:number) and Table’s classNames/structure are still identical in @heroui/react to avoid subtle runtime/typing mismatches. If type-check passes here, you’re good.

package.json (1)

100-121: Node 22 compatibility check for test tooling.

Jest 29.x historically lagged on brand-new Node majors. You may be fine, but verify locally/CI that jest, ts-jest, and jest-environment-jsdom run cleanly on Node 22.

app/markets/components/markets.tsx (2)

399-418: onPress migration looks correct.

Consistent with the Button wrapper switch; matches other changes in the PR.


298-313: State-derived market link construction is solid.

Keeps navigation consistent with current filters; opening in a new tab is intentional here.

app/markets/page.tsx (1)

12-24: Confirm Next 15’s searchParams-as-Promise usage; consider reverting to plain object for clarity.

Awaiting a non-promise is harmless at runtime, but the Promise typing may confuse. If not required, simplify.

Option:

-type PageProps = {
-  searchParams: Promise<{
+type PageProps = {
+  searchParams?: {
     network?: string;
     collaterals?: string;
     loanAssets?: string;
     [key: string]: string | string[] | undefined;
-  }>;
-}
+  };
+}
 
-export default async function MarketPage({ searchParams }: PageProps) {
-  // Await the searchParams Promise in Next.js 15
-  const params = await searchParams;
+export default function MarketPage({ searchParams = {} }: PageProps) {
+  const params = searchParams;
app/positions/report/components/ReportContent.tsx (1)

4-4: DateValue import migration looks good

Importing DateValue from @heroui/react aligns with the library switch.

app/positions/components/onboarding/SetupPositions.tsx (2)

2-2: Slider import migration looks good

HeroUI’s Slider API used here (value, onChange, minValue/maxValue, isDisabled, classNames) matches what's in this file.


210-218: SelectedToken non-null assertion is safe

SetupPositions only renders once the onboarding flow advances to the ‘setup’ step, and the context’s canGoNext guard won’t allow that until selectedToken is non-null (and markets are chosen). No change needed here.

Likely an incorrect or invalid review comment.

src/components/Borrow/AddCollateralAndBorrow.tsx (3)

356-357: Good swap to onPress with void wrapper

onPress={() => void approveAndBorrow()} matches the new API and prevents unhandled Promise rejections.


372-373: Good swap to onPress with void wrapper

onPress={() => void signAndBorrow()} looks correct and consistent.


2-2: Confirmed @heroui/react install & no leftover NextUI imports

  • package.json declares @heroui/react@^2.4.2
  • No @nextui-org/ imports found
  • No legacy <Button onClick=…> usage detected

Comment thread package.json
Comment thread package.json Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/store/createWagmiConfig.ts (2)

23-35: Move wallet selection into the factory to avoid module-scope window checks and trim SSR cost

Picking wallets at import time can cause SSR/CSR divergence and pulls all wallet code into the server bundle. Resolve wallets inside createWagmiConfig instead.

Apply this diff to remove the module-scope constant:

-const wallets = (typeof window !== "undefined") ? [
-  rabbyWallet,
-  metaMaskWallet,
-  rainbowWallet,
-  coinbaseWallet,
-  argentWallet,
-  injectedWallet,
-  trustWallet,
-  ledgerWallet,
-] : [
-  injectedWallet,
-];
+// Wallets are resolved inside createWagmiConfig for SSR safety.

Then, inside createWagmiConfig, add this near the top (example; keep the rest unchanged):

const wallets =
  typeof window !== 'undefined'
    ? [rabbyWallet, metaMaskWallet, rainbowWallet, coinbaseWallet, argentWallet, injectedWallet, trustWallet, ledgerWallet]
    : [injectedWallet];

Also confirm that dropping the Safe connector is intentional and that no UI paths still expect it.


39-42: Nit: pluralize the group label

Minor copy tweak for clarity.

-        groupName: 'Recommended Wallet',
+        groupName: 'Recommended Wallets',
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0d36b0c and c258a27.

📒 Files selected for processing (2)
  • next.config.js (1 hunks)
  • src/store/createWagmiConfig.ts (1 hunks)
🔇 Additional comments (1)
src/store/createWagmiConfig.ts (1)

39-42: RainbowKit v2 detected – no changes needed

Verified you’re on RainbowKit 2.x (and wagmi 2.x), so passing wallet factories directly is correct. Just ensure projectId remains non-empty to keep WalletConnect happy.

Comment thread next.config.js Outdated
Copy link
Copy Markdown

@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: 1> [!CAUTION]

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

🔭 Outside diff range comments (1)
package.json (1)

7-7: Update scripts to pnpm (packageManager is pnpm).

Two scripts still invoke yarn.

Apply:

-    "check": "yarn format && yarn lint && yarn stylelint",
+    "check": "pnpm format && pnpm lint && pnpm stylelint",
...
-    "test:coverage:open": "yarn test:coverage && open coverage/lcov-report/index.html"
+    "test:coverage:open": "pnpm test:coverage && open coverage/lcov-report/index.html"

Optional: replace open with a cross‑platform tool if you care about Windows CI.

Also applies to: 18-18

♻️ Duplicate comments (1)
package.json (1)

131-131: Declare Node engine and move pins to pnpm “overrides”.

Engines help CI/dev match Node 22; with pnpm, use top‑level overrides (not resolutions). This echoes a prior review.

Apply:

   "devDependencies": {
     ...
     "typescript": "~5.6.3"
   },
+  "engines": {
+    "node": ">=22.12"
+  },
+  "overrides": {
+    "tr46": "^4.0.0",
+    "punycode": "^2.3.1"
+  },
   "packageManager": "pnpm@9.15.0"
🧹 Nitpick comments (1)
package.json (1)

6-6: Drop the pre-clean or use a cross‑platform cleaner.

rm -rf .next is Unix-only and usually unnecessary; next build handles output. If you need a clean, prefer rimraf.

Apply one of:

-    "build": "rm -rf .next && next build",
+    "build": "next build",

or

-    "build": "rm -rf .next && next build",
+    "build": "rimraf .next && next build",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c258a27 and 4432088.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json (6 hunks)
🔇 Additional comments (2)
package.json (2)

22-30: Heroui migration deps look consistent.

The @heroui/* set replaces NextUI cleanly and versions look aligned. No issues spotted here.

Also applies to: 36-36


20-77: Confirm removal of third‑party “net”.

The problematic dependency is gone. Good cleanup.

Comment thread package.json
"typescript": "~5.6.3"
},
"packageManager": "yarn@4.1.0"
"packageManager": "pnpm@9.15.0"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify lockfiles and manager switch.

Ensure repo uses pnpm lock and no stray yarn/npm locks remain.

Run:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Lockfiles present:"
fd -a 'pnpm-lock.yaml|yarn.lock|package-lock.json' -t f

echo
echo "Package manager declared in package.json:"
jq -r '.packageManager' package.json

echo
echo "Check scripts referencing yarn:"
rg -n -C1 -g '!**/node_modules/**' -P '\byarn\b' || true

echo
echo "Expectations:"
echo "- pnpm-lock.yaml present"
echo "- No yarn.lock or package-lock.json"
echo "- No scripts still invoking yarn (after fixes)"

Length of output: 1323


Fix stray yarn invocations in package scripts

The repo correctly declares pnpm@9.15.0 and only pnpm-lock.yaml exists, but there are still two scripts calling yarn. Update them to use pnpm (or npm run) instead:

• package.json
– Line 7: "check": "yarn format && yarn lint && yarn stylelint"
– Line 18: "test:coverage:open": "yarn test:coverage && open coverage/lcov-report/index.html"

Suggested diff:

--- a/package.json
+++ b/package.json
@@ -6,4 +6,4 @@
   "build": "rm -rf .next && next build",
-  "check": "yarn format && yarn lint && yarn stylelint",
+  "check": "pnpm format && pnpm lint && pnpm stylelint",
   "dev": "next dev",
@@ -17,4 +17,4 @@
   "test:coverage": "jest . --coverage",
-  "test:coverage:open": "yarn test:coverage && open coverage/lcov-report/index.html"
+  "test:coverage:open": "pnpm test:coverage && open coverage/lcov-report/index.html"
 }
📝 Committable suggestion

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

Suggested change
"packageManager": "pnpm@9.15.0"
"scripts": {
"build": "rm -rf .next && next build",
"check": "pnpm format && pnpm lint && pnpm stylelint",
"dev": "next dev",
"test:coverage": "jest . --coverage",
"test:coverage:open": "pnpm test:coverage && open coverage/lcov-report/index.html"
}

@antoncoding antoncoding merged commit 1a49b1b into master Aug 20, 2025
4 checks passed
@antoncoding antoncoding deleted the opt/version branch August 20, 2025 05:43
@coderabbitai coderabbitai Bot mentioned this pull request Sep 2, 2025
This was referenced Dec 9, 2025
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.

1 participant