Skip to content

Conversation

rodrigooler
Copy link
Collaborator

@rodrigooler rodrigooler commented Sep 4, 2025

Summary by CodeRabbit

  • Refactor

    • Simplified provider composition and app startup; provider helpers added for cleaner wiring.
    • Introduced a new DAO Dashboard layout and metadata generator.
  • Bug Fixes

    • Reduced UI flicker and timing issues across modals, selections, explorer links, comments, transactions, hover effects, animations, and wallet init by deferring certain state updates.
  • Chores

    • Added ignore patterns for PLANS/TASKS, enabled experimental React compiler across apps, added a dev dependency for the compiler, and included generated route type references for improved tooling.

@Copilot Copilot AI review requested due to automatic review settings September 4, 2025 16:56
@rodrigooler rodrigooler requested a review from a team as a code owner September 4, 2025 16:56
Copy link

linear bot commented Sep 4, 2025

@rodrigooler rodrigooler changed the base branch from main to dev September 4, 2025 16:56
Copy link
Contributor

coderabbitai bot commented Sep 4, 2025

Walkthrough

Removed many ESLint suppressions, deferred several state updates (setTimeout/RAF/microtask), moved some memoized logic to render-time or IIFEs, added provider-with-props support with createProviderWithProps, enabled experimental React Compiler across apps, and adjusted several Next.js configs and small hook/component behaviors.

Changes

Cohort / File(s) Summary of Changes
Repo meta
./.gitignore, package.json
Added ignore patterns *PLANS.md, *TASKS.md; added devDependency babel-plugin-react-compiler@19.1.0-rc.3.
Next.js configs
apps/torus-allocator/next.config.mjs, apps/torus-bridge/next.config.mjs, apps/torus-governance/next.config.mjs, apps/torus-page/next.config.mjs, apps/torus-portal/next.config.mjs, apps/torus-wallet/next.config.mjs
Enabled experimental.reactCompiler: true across apps; torus-bridge also added productionBrowserSourceMaps, extended webpack signature to include webpack, NormalModuleReplacementPlugin for client builds, Node builtin fallbacks, and YAML loader adjustments.
Bridge — provider renderer & app context
apps/torus-bridge/src/app/_components/provider-renderer.tsx, apps/torus-bridge/src/context/app-context-provider.tsx
Introduced polymorphic providers (component or {component, props}), runtime type guard, exported createProviderWithProps, and rewired AppContextProvider to pass TorusProvider via createProviderWithProps.
Bridge — transfer/modal & URL loading
apps/torus-bridge/src/app/_components/transfer-details/*, .../transfer-details/index.tsx
Consolidated explorer/address URL fetching into an effect with refined deps and normalization; modal selection effect switched to useLayoutEffect, tracks prev transfer count, and defers state updates via microtask.
Bridge — tokens, forms, buttons
apps/torus-bridge/src/app/_components/tokens/*, .../token-select-field.tsx, .../token-list-modal.tsx, .../transfer-token-form.tsx, .../connect-aware-submit-button.tsx
Removed ESLint suppressions, derived values at render instead of state in some places, simplified memo/useCallback deps; behavior preserved.
Bridge — wallet providers & balance watcher
apps/torus-bridge/src/context/*-wallet-provider.tsx, apps/torus-bridge/src/hooks/use-balance-watcher.ts
Removed or deferred client-mount gating, adjusted memo/effect dependency lists (e.g., toast dependency), and refined mount/update timing.
Governance — hooks & components
apps/torus-governance/hooks/*, apps/torus-governance/src/app/**
Removed lint suppressions, moved helper VoteBar to module scope, replaced some useMemo with IIFEs, added isMobile responsive state in proposal view, and deferred certain state updates.
Portal — permission-graph & graph UI
apps/torus-portal/src/app/(pages)/(permission-graph)/**, apps/torus-portal/src/hooks/*
Adjusted useGraphData return shape (removed isLoading, allocatorAddress), removed lint suppressions, converted some memoized content to pre-rendered elements, and deferred selection synchronization.
Portal — graph-sheet details
apps/torus-portal/src/app/.../graph-sheet-details-*.tsx
Converted inline components to pre-rendered IIFEs and removed ESLint comments; behavior unchanged.
Page — header & animation
apps/torus-page/src/app/_components/hover-header/*, apps/torus-page/src/app/_components/torus-animation.tsx
Memoized handlers/variants in hover-header; removed non-null assertions and added guards; consolidated ShaderMaterial creation into a memo and updated per-frame uniform mutation.
Wallet — UI & hooks
apps/torus-wallet/src/app/**, apps/torus-wallet/src/hooks/*
Deferred layout/state updates via setTimeout/RAF, added cleanups where applicable, changed APR memo deps to depend on query objects, simplified state initializers, and extended transactions hook reset behavior.
UI package
packages/ui/src/components/chart.tsx
Changed first payload access to payload[0] and removed lint suppression comment.
Types & layouts
apps/torus-governance/next-env.d.ts, apps/torus-wallet/next-env.d.ts, apps/torus-governance/src/app/(pages)/dao-dashboard/*
Added triple-slash references to .next route types; added DAO Dashboard layout module with generateMetadata and default export; removed server generateMetadata from DAO page (converted to client).
Tooling
tooling/eslint/react.js
Added rule override: "react-hooks/preserve-manual-memoization": "off".

Sequence Diagram(s)

sequenceDiagram
  participant UI as TransferDetailsDialog
  participant MP as MultiProvider
  participant ST as State

  Note over UI: Effect runs when sender/recipient/originTxHash/multiProvider/origin/destination change
  alt originTxHash present
    UI->>MP: trySync explorer lookup
    MP-->>UI: explorer info / error
    UI->>ST: set originTxUrl (normalized) or noop on error
  end
  UI->>MP: getAddressUrl(sender)
  MP-->>UI: senderUrl
  UI->>ST: set senderUrl
  UI->>MP: getAddressUrl(recipient)
  MP-->>UI: recipientUrl
  UI->>ST: set recipientUrl
Loading
sequenceDiagram
  participant Page as PermissionGraphPage
  participant Router as URL Params
  participant GD as useGraphData
  participant ST as State

  Router-->>Page: id param present/absent
  GD-->>Page: graphData available
  alt id present
    Page->>Page: find node by id
    alt differs from selected
      Page->>ST: setSelectedNode(node) [deferred via setTimeout]
      Page->>ST: setIsSheetOpen(true) [deferred]
    end
  else id absent and node selected
    Page->>ST: clear selection & close sheet [deferred]
  end
Loading
sequenceDiagram
  participant PR as ProviderRenderer
  participant App as AppContextProvider
  participant P as ProviderItem (component or {component,props})

  App->>PR: pass providers array (includes createProviderWithProps item)
  PR->>PR: fold providers right-to-left
  alt provider has props
    PR->>P: render component with bound props and accumulated children
  else
    PR->>P: render component directly with children
  end
  PR-->>App: composed provider tree
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • EdSDR

"I hop through code with a jittery cheer,
Memos unwind, and effects wait a tick.
Providers snug props, graphs whisper clear,
Configs lean new compiler tricks.
Small hops, big fixes — a rabbit’s quick flick. 🐇"

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and concisely reflects the primary work in this changeset: removal of disabled ESLint directives and associated lint-driven refactors across many files (as shown in the raw summary). There are a few notable secondary changes (e.g., a new createProviderWithProps helper and Next.js config updates), so the title is a correct, focused summary of the main intent but does not enumerate all ancillary edits. Overall it is clear and meaningful for a reviewer scanning PR history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/web-345

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 e7f4a44 and d393621.

📒 Files selected for processing (1)
  • tooling/eslint/react.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tooling/eslint/react.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: lint
  • GitHub Check: typecheck

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

❤️ Share

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

@rodrigooler rodrigooler self-assigned this Sep 4, 2025
@rodrigooler rodrigooler marked this pull request as draft September 4, 2025 17:00
@rodrigooler rodrigooler changed the title Fix/web 345 fix: cleanup disabled linting (web-345) Sep 4, 2025
Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-cache Preview deployment will be available at: https://pr-359.torus-cache.torus.network

Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-allocator Preview deployment will be available at: https://pr-359.torus-allocator.torus.network

Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-page Preview deployment will be available at: https://pr-359.torus-page.torus.network

Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-wallet Preview deployment will be available at: https://pr-359.torus-wallet.torus.network

Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-portal Preview deployment will be available at: https://pr-359.torus-portal.torus.network

Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-governance Preview deployment will be available at: https://pr-359.torus-governance.torus.network

Copy link

github-actions bot commented Sep 4, 2025

🚀 torus-bridge Preview deployment will be available at: https://pr-359.torus-bridge.torus.network

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

# Conflicts:
#	apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx
#	apps/torus-portal/src/app/(pages)/(permission-graph)/_components/permission-graph-command.tsx
… transfer details and wallet provider components
@rodrigooler rodrigooler marked this pull request as ready for review September 11, 2025 18:03
…nd enhancing state management in various components
EdSDR
EdSDR previously approved these changes Sep 11, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (11)
apps/torus-wallet/src/app/_components/transactions-sheet.tsx (1)

44-44: Tailwind arbitrary width typo: w-400px] → w-[400px].

The current class is invalid and drops the width style.

Apply this diff:

-      <SheetContent className="w-400px] z-[70] flex h-full flex-col gap-4">
+      <SheetContent className="w-[400px] z-[70] flex h-full flex-col gap-4">
apps/torus-bridge/src/app/_components/transfer-token/_components/transfer-token-form.tsx (1)

44-66: Form resets risk: non-memoized initialValues + enableReinitialize can wipe user input on any re-render.

With a new object each render, Formik may reinitialize repeatedly, and your useEffect depending on initialValues.* can loop navigation updates.

Re-memoize initialValues by inputs:

-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
@@
-  const initialValues = (() => {
+  const initialValues = useMemo(() => {
     if (fromParam && toParam) {
       return {
         origin: fromParam,
         destination: toParam,
         tokenIndex: getIndexForTokenByChainName(warpCore, fromParam),
         amount: "",
         recipient: "",
       };
     }
 
     const firstToken = warpCore.tokens[0];
     const connectedToken = firstToken?.connections?.[0];
 
     return {
       origin: firstToken?.chainName ?? "",
       destination: connectedToken?.token.chainName ?? "",
       tokenIndex: getIndexForToken(warpCore, firstToken),
       amount: "",
       recipient: "",
     };
-  })();
+  }, [warpCore, fromParam, toParam]);

Please verify that typing into fields no longer resets on unrelated state updates (e.g., accounts or tokens refresh).

apps/torus-wallet/src/hooks/useAPR.ts (2)

129-142: Stale flags: useMemo with [queries] won’t update when inner fields change.

isAnyLoading, isAnyError, and isDataComplete won’t recompute as query fields mutate without array identity change. Compute directly (no memo) or depend on individual fields.

-  const isAnyLoading = useMemo(
-    () => queries.some((query) => query.isPending),
-    [queries],
-  );
-
-  const isAnyError = useMemo(
-    () => queries.some((query) => query.isError),
-    [queries],
-  );
-
-  const isDataComplete = useMemo(
-    () => queries.every((query) => !!query.data),
-    [queries],
-  );
+  const isAnyLoading =
+    totalStakeQuery.isPending ||
+    totalIssuanceQuery.isPending ||
+    recyclingPercentageQuery.isPending ||
+    treasuryEmissionFeeQuery.isPending ||
+    incentivesRatioQuery.isPending;
+
+  const isAnyError =
+    totalStakeQuery.isError ||
+    totalIssuanceQuery.isError ||
+    recyclingPercentageQuery.isError ||
+    treasuryEmissionFeeQuery.isError ||
+    incentivesRatioQuery.isError;
+
+  const isDataComplete =
+    !!totalStakeQuery.data &&
+    !!totalIssuanceQuery.data &&
+    !!recyclingPercentageQuery.data &&
+    !!treasuryEmissionFeeQuery.data &&
+    !!incentivesRatioQuery.data;

78-99: Big precision loss converting 1e18-scaled floats via Number → BigInt.

Math.round(x * 1e18) exceeds JS safe-integer range; the rounding is imprecise before BigInt(...). Use a smaller precise scale (e.g., 1e6) or a string-based scaler.

-  const PRECISION = 10n ** 18n; // 10^18 for high precision
+  // Use a safe, precise scale for Number -> BigInt conversions.
+  const PRECISION = 1_000_000n; // 1e6 is within JS number safe integer when rounding

   // Convert ratios to bigint with precision
-  const incentivesRatioScaled = BigInt(
-    Math.round(incentivesRatio * Number(PRECISION)),
-  );
-  const treasuryFeeScaled = BigInt(Math.round(treasuryFee * Number(PRECISION)));
+  const incentivesRatioScaled = BigInt(Math.round(incentivesRatio * Number(PRECISION)));
+  const treasuryFeeScaled = BigInt(Math.round(treasuryFee * Number(PRECISION)));

If higher precision is required, switch to a string scaler (no float math) and parse to BigInt; I can provide that helper.

apps/torus-governance/src/app/(expanded-pages)/agent-application/[id]/_components/agent-application-expanded-vote-bars.tsx (1)

50-57: Guard division by zero and clamp widths to [0, 100].

When there are 0 cadre members, againstWidth becomes Infinity. Also, widths can exceed 100% after reaching thresholds.

   const totalCadreMembers = cadreListData.length;
-  const threshold = Math.floor(totalCadreMembers / 2) + 1;
+  const threshold = Math.floor(totalCadreMembers / 2) + 1;

   // Calculate widths based on the total possible votes (cadre members)
-  const favorableWidth = (favorableVotes / threshold) * 100;
-  const againstWidth = (againstVotes / totalCadreMembers) * 100;
+  const favorableWidthRaw = threshold > 0 ? (favorableVotes / threshold) * 100 : 0;
+  const againstWidthRaw =
+    totalCadreMembers > 0 ? (againstVotes / totalCadreMembers) * 100 : 0;
+  const favorableWidth = Math.max(0, Math.min(100, favorableWidthRaw));
+  const againstWidth = Math.max(0, Math.min(100, againstWidthRaw));
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx (2)

118-132: Fix: connectedNode lookup may fail when source/target are objects.

permission.source/target can be objects; comparing n.id against the object will never match. Compute sourceId/targetId first and use them in the lookup.

-    const isOutgoing = permission.type === "outgoing";
-    const connectedNode = graphData?.nodes.find(
-      (n) => n.id === (isOutgoing ? permission.target : permission.source),
-    );
-    const connectedAddress =
-      connectedNode?.fullAddress ?? connectedNode?.id ?? "";
-
-    const sourceId =
-      typeof permission.source === "object"
-        ? permission.source.id
-        : permission.source;
-    const targetId =
-      typeof permission.target === "object"
-        ? permission.target.id
-        : permission.target;
+    const isOutgoing = permission.type === "outgoing";
+    const sourceId =
+      typeof permission.source === "object" && permission.source
+        ? permission.source.id
+        : permission.source;
+    const targetId =
+      typeof permission.target === "object" && permission.target
+        ? permission.target.id
+        : permission.target;
+    const connectedNode = graphData?.nodes.find(
+      (n) => n.id === (isOutgoing ? targetId : sourceId),
+    );
+    const connectedAddress =
+      connectedNode?.fullAddress ?? connectedNode?.id ?? "";

1-10: Add "use client"; as the first line of the file

apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx is missing the directive and uses client-only hooks (useRouter/usePathname). Add "use client"; as the top-most line.

apps/torus-wallet/src/hooks/useTransactions.tsx (4)

38-45: Do not call setState during render; move resets into an effect keyed by resetKey.

Calling multiple setters in render is a render-phase update that can cause warnings, re-render loops, and inconsistent effect ordering. It also fails to trigger a refetch when lastTransactionTimestamp changes while page is already 1 (page doesn’t change, so the load effect won’t rerun).

Apply this diff to fix and ensure a refetch occurs on resetKey changes:

-  if (currentResetKey !== resetKey) {
-    setCurrentResetKey(resetKey);
-    setPage(1);
-    setTransactions([]);
-    setTotalTransactions(0);
-    setHasMore(false);
-    setIsLoading(false);
-  }
+  useEffect(() => {
+    setCurrentResetKey(resetKey);
+    setPage(1);
+    setTransactions([]);
+    setTotalTransactions(0);
+    setHasMore(false);
+    setIsLoading(false);
+  }, [resetKey]);

Also update the data-loading effect deps to observe resets:

-  }, [address, page, filters, itemsPerPage, getTransactionsByWallet]);
+  }, [address, page, filters, itemsPerPage, getTransactionsByWallet, resetKey, currentResetKey]);

56-87: Replace setTimeout(..., 0) with queueMicrotask for page 1; keep timeout for subsequent pages.

This aligns with the PR’s lint cleanup and avoids zero-timeout macrotasks while preserving the 100ms UX delay on later pages. Maintain cancel-ability by using an abort flag for the microtask path.

-      // Small delay to prevent rapid calls and improve UX
-      const delay = page === 1 ? 0 : 100;
-
-      const timeoutId = setTimeout(() => {
-        const options: TransactionQueryOptions = {
-          page,
-          limit: itemsPerPage,
-          type: filters.type,
-          fromAddress: filters.fromAddress,
-          toAddress: filters.toAddress,
-          hash: filters.hash,
-          startDate: filters.startDate,
-          endDate: filters.endDate,
-          orderBy: filters.orderBy,
-        };
-
-        if (!address) return;
-
-        const result = getTransactionsByWallet(address, options);
-
-        // For page 1, replace all transactions. For other pages, append
-        setTransactions((prev) =>
-          page === 1 ? result.transactions : [...prev, ...result.transactions],
-        );
-        setTotalTransactions(result.total);
-        setHasMore(result.hasMore);
-        setIsLoading(false);
-      }, delay);
-
-      // Return cleanup function to prevent memory leaks
-      return () => clearTimeout(timeoutId);
+      // Small delay to prevent rapid calls on subsequent pages.
+      // For page 1, defer via microtask to avoid zero-timeout.
+      const schedule = (fn: () => void) => {
+        if (page === 1) {
+          let aborted = false;
+          queueMicrotask(() => {
+            if (aborted) return;
+            fn();
+          });
+          return () => {
+            aborted = true;
+          };
+        }
+        const id = setTimeout(fn, 100);
+        return () => clearTimeout(id);
+      };
+
+      const cleanup = schedule(() => {
+        const options: TransactionQueryOptions = {
+          page,
+          limit: itemsPerPage,
+          type: filters.type,
+          fromAddress: filters.fromAddress,
+          toAddress: filters.toAddress,
+          hash: filters.hash,
+          startDate: filters.startDate,
+          endDate: filters.endDate,
+          orderBy: filters.orderBy,
+        };
+
+        if (!address) return;
+
+        const result = getTransactionsByWallet(address, options);
+
+        // For page 1, replace all transactions. For other pages, append
+        setTransactions((prev) =>
+          page === 1 ? result.transactions : [...prev, ...result.transactions],
+        );
+        setTotalTransactions(result.total);
+        setHasMore(result.hasMore);
+        setIsLoading(false);
+      });
+
+      return cleanup;

47-51: Guard the loader until the reset state is applied — add a guard and ensure deps update.

  • Add early-return at the top of the loader useEffect: if (currentResetKey !== resetKey) return;
  • Include currentResetKey (or resetKey) in the effect dependency array so the loader re-runs only after the reset state is applied.
  • Move the render-time reset (currently calling setCurrentResetKey / setPage / setTransactions etc.) out of the render body into a dedicated useEffect that runs on resetKey to avoid setState during render.

File: apps/torus-wallet/src/hooks/useTransactions.tsx — reset block (lines ~38–45) and loader useEffect (starts ~line 47).


47-91: Remaining zero-delay setTimeout(...) usages found — address or justify

Scan uncovered explicit zero-delay timeouts at:

  • apps/torus-portal/src/app/(pages)/(permission-graph)/page.tsx:65
  • apps/torus-portal/src/app/(pages)/network-operations/manage-agent/_components/update-agent-form.tsx:76, 138
  • apps/torus-portal/src/app/(pages)/permissions/create-permission/capability/_components/create-capability-flow/create-capability-flow-hooks/use-permission-select-handler.ts:397
  • apps/torus-governance/src/app/_components/expanded-view-content.tsx:32
  • apps/torus-governance/src/app/_components/shape-network-modal.tsx:86
  • apps/torus-wallet/src/app/(transfers)/_components/faucet/faucet-form.tsx:46
  • packages/ui/src/components/wallet-connection-warning.tsx:22

Note: apps/torus-wallet/src/hooks/useTransactions.tsx still uses a conditional delay (page === 1 ? 0 : 100) which results in a zero-delay on page 1 — decide whether that is acceptable.

Action: remove/replace zero-delay setTimeouts or add a brief justification/comments where retaining 0 is intentional; update PR accordingly.

🧹 Nitpick comments (50)
apps/torus-page/src/app/_components/hover-header/index.tsx (6)

25-30: Use Math.hypot for readability and potential perf gains.
Simpler and avoids duplicate pow calls.

-      return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
+      return Math.hypot(x2 - x1, y2 - y1);

32-57: Throttle mousemove state updates with rAF.
Reduces re-render pressure on hot path and prevents flooding setState.

-    (event: MouseEvent) => {
+    (event: globalThis.MouseEvent) => {
@@
-      setGlowSize(scale / 1.2);
+      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
+      rafRef.current = requestAnimationFrame(() => setGlowSize(scale / 1.2));

Add once near other refs:

// above handlers
const rafRef = useRef<number | null>(null);

59-66: Attach mousemove only when needed and cancel pending rAF on cleanup.
Avoids global listener when header isn’t interacted with; ensures rAF is cleared.

-useEffect(() => {
-  window.addEventListener("mousemove", handleMouseMove);
-
-  return () => {
-    window.removeEventListener("mousemove", handleMouseMove);
-  };
-}, [handleMouseMove]);
+useEffect(() => {
+  if (!isHovered && !isVisible) return;
+  window.addEventListener("mousemove", handleMouseMove);
+  return () => {
+    window.removeEventListener("mousemove", handleMouseMove);
+    if (rafRef.current !== null) {
+      cancelAnimationFrame(rafRef.current);
+      rafRef.current = null;
+    }
+  };
+}, [handleMouseMove, isHovered, isVisible]);

67-79: Hoist static variants to a module-level constant.
This object is static; removing useMemo simplifies and slightly reduces hook work.

Example:

// outside component
const GLOW_VARIANTS: Variants = {
  pulse: (scale: number) => ({
    scale: [scale, scale * 1.2, scale],
    transition: { duration: 2, repeat: Infinity, ease: "easeInOut" },
  }),
};
// usage: variants={GLOW_VARIANTS}

81-88: Use PointerEvent for broader input coverage.
Covers mouse, touch, pen uniformly.

-  const handleClickOutside = useCallback((event: MouseEvent) => {
+  const handleClickOutside = useCallback((event: globalThis.PointerEvent) => {

90-99: Listen for pointerdown instead of mousedown.
Ensures outside-close works on touch devices too.

-    if (isVisible) {
-      document.addEventListener("mousedown", handleClickOutside);
-    }
+    if (isVisible) {
+      document.addEventListener("pointerdown", handleClickOutside);
+    }
@@
-      document.removeEventListener("mousedown", handleClickOutside);
+      document.removeEventListener("pointerdown", handleClickOutside);
apps/torus-wallet/src/app/_components/transactions-sheet.tsx (2)

26-29: rAF deferral is fine; avoid background-tab throttling keeping heavy content mounted.

requestAnimationFrame is throttled in background tabs; closing the sheet and then backgrounding the tab can leave Transactions mounted longer than necessary. Two low-risk options:

  • Prefer queueMicrotask for the closing path (runs regardless of frame cadence):
-      const frameId = requestAnimationFrame(() =>
-        setShouldRenderContent(false),
-      );
-      return () => cancelAnimationFrame(frameId);
+      let canceled = false;
+      queueMicrotask(() => {
+        if (!canceled) setShouldRenderContent(false);
+      });
+      return () => {
+        canceled = true;
+      };
  • Or additionally gate render by isOpen so content unmounts immediately when closed (even if rAF is throttled):
// around lines 53-60
{isOpen && shouldRenderContent && selectedAccount && (
  <Transactions selectedAccount={selectedAccount} />
)}
{isOpen && shouldRenderContent && !selectedAccount && (
  <div className="text-muted-foreground flex flex-1 items-center justify-center">
    No account selected
  </div>
)}

23-24: Extract magic number 150ms into a named constant.

Improves readability and discoverability of the UX timing.

Example:

const OPEN_CONTENT_DELAY_MS = 150;
// ...
const timer = setTimeout(() => setShouldRenderContent(true), OPEN_CONTENT_DELAY_MS);
.gitignore (2)

47-48: Broadened ignore patterns likely unintended — use exact-name matches instead of suffix globs.

*PLANS.md and *TASKS.md will ignore any file ending with those names (e.g., team-PLANS.md, OKR_TASKS.md). If you meant “files named exactly PLANS.md/TASKS.md anywhere,” drop the leading *. If you meant only repo-root files, anchor with /.

Apply one of the following:

Exact-name anywhere:

-*PLANS.md
-*TASKS.md
+PLANS.md
+TASKS.md

Root-only:

-*PLANS.md
-*TASKS.md
+/PLANS.md
+/TASKS.md

23-23: Deduplicate identical ignore entry.

**/.claude/settings.local.json appears twice; keep a single instance.

-**/.claude/settings.local.json

Also applies to: 46-46

apps/torus-bridge/src/app/_components/buttons/connect-aware-submit-button.tsx (1)

46-50: Tighten callback deps look good; drop unnecessary void.

Reducing deps to [setErrors, setTouched] avoids stale closures and redundant timers. The void before setTouched({}) isn’t needed (Formik’s setter returns void).

   const clearErrors = useCallback(() => {
     setErrors({});
-    void setTouched({});
+    setTouched({});
   }, [setErrors, setTouched]);
apps/torus-bridge/src/hooks/use-balance-watcher.ts (1)

21-37: Deps change is correct; consider trimming duplicate checks in condition.

Switching deps to [balance, recipient, toast] is appropriate; refs shouldn’t be in the array. The predicate repeats recipient equality checks; you can simplify without changing behavior.

-    if (
-      recipient &&
-      balance &&
-      prev.recipient === recipient &&
-      prevRecipientBalance.current.balance &&
-      prevRecipientBalance.current.recipient === recipient &&
-      balance.token.equals(prevRecipientBalance.current.balance.token) &&
-      balance.amount > prevRecipientBalance.current.balance.amount
-    ) {
+    if (
+      recipient &&
+      balance &&
+      prev.recipient === recipient &&
+      prevRecipientBalance.current.balance &&
+      balance.token.equals(prevRecipientBalance.current.balance.token) &&
+      balance.amount > prevRecipientBalance.current.balance.amount
+    ) {
apps/torus-bridge/src/app/_components/tokens/token-list-modal.tsx (2)

100-129: Recompute on every render could be costly; memoize if the list gets large.

If warpCore.tokens is sizable, this map/sort/filter each render can cause UI jank. Optional: memoize by inputs.

-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
@@
-  const tokens = (() => {
+  const tokens = useMemo(() => {
     const q = searchQuery.trim().toLowerCase();
     const multiChainTokens = warpCore.tokens.filter((t) =>
       t.isMultiChainToken(),
     );
     const tokensWithRoute = warpCore.getTokensForRoute(origin, destination);
     return (
       multiChainTokens
         .map((t) => ({
           token: t,
           disabled: !tokensWithRoute.includes(t),
         }))
         .sort((a, b) => {
           if (a.disabled && !b.disabled) return 1;
           else if (!a.disabled && b.disabled) return -1;
           else return 0;
         })
         // Filter down to search query
         .filter((t) => {
           if (!q) return t;
           return (
             t.token.name.toLowerCase().includes(q) ||
             t.token.symbol.toLowerCase().includes(q) ||
             t.token.addressOrDenom.toLowerCase().includes(q)
           );
         })
         // Hide/show disabled tokens
         .filter((t) => (config.showDisabledTokens ? true : !t.disabled))
     );
-  })();
+  }, [warpCore, origin, destination, searchQuery, config.showDisabledTokens]);

134-141: Avoid index keys to reduce re-mount churn.

Use a stable key (chainName + addressOrDenom) to prevent unnecessary re-mounts when the list changes.

-        tokens.map((t, i) => (
+        tokens.map((t) => (
           <button
@@
-            key={i}
+            key={`${t.token.chainName}:${t.token.addressOrDenom}`}
apps/torus-governance/src/app/_components/shape-network-modal.tsx (1)

85-91: Prefer queueMicrotask over setTimeout(0) for next-tick deferral.

This PR’s rationale favors microtasks for zero-delay defers. Switching improves consistency and avoids timer clamping.

-    if (shouldBeOpen) {
-      const timer = setTimeout(() => {
-        setIsOpen(true);
-        sessionStorage.removeItem("shapeNetworkModalOpen");
-      }, 0);
-      return () => clearTimeout(timer);
-    }
+    if (shouldBeOpen) {
+      queueMicrotask(() => {
+        setIsOpen(true);
+        sessionStorage.removeItem("shapeNetworkModalOpen");
+      });
+    }
apps/torus-wallet/src/app/(transfers)/_components/faucet/faucet-form.tsx (1)

44-50: Use requestAnimationFrame (or keep synchronous useLayoutEffect) instead of setTimeout(0) for measurement.

Deferring to a macrotask can cause a visible flicker: first render with width 0, then update. Use rAF and cancel on cleanup.

-  useLayoutEffect(() => {
-    if (containerRef.current) {
-      const timer = setTimeout(() => {
-        setWidth(containerRef.current?.offsetWidth ?? 0);
-      }, 0);
-      return () => clearTimeout(timer);
-    }
-  }, []);
+  useLayoutEffect(() => {
+    if (!containerRef.current) return;
+    const id = requestAnimationFrame(() => {
+      setWidth(containerRef.current?.offsetWidth ?? 0);
+    });
+    return () => cancelAnimationFrame(id);
+  }, []);

Optional follow-up: consider a ResizeObserver to keep the menu width in sync on container resizes.

apps/torus-governance/hooks/use-sign-in.tsx (3)

24-33: Deferring with microtask: add unmount guard

queueMicrotask can still fire after unmount. Add a canceled flag so setState is skipped.

   useEffect(() => {
     if (!(viewMode === "dao-portal")) return;

     const auth = localStorage.getItem("authorization");
-    if (!auth) {
-      queueMicrotask(() => setIsUserAuthenticated(false));
-      return;
-    }
+    if (!auth) {
+      let canceled = false;
+      queueMicrotask(() => {
+        if (!canceled) setIsUserAuthenticated(false);
+      });
+      return () => {
+        canceled = true;
+      };
+    }

61-63: Deps array change is safe but unnecessary

setIsUserAuthenticated is stable; including it is harmless but not required. Feel free to drop it to reduce churn.


64-71: Also guard the second microtask against unmount

Same rationale as above.

   useEffect(() => {
     const favoriteWalletAddress = localStorage.getItem("favoriteWalletAddress");
     if (!selectedAccount || favoriteWalletAddress === selectedAccount.address)
       return;

-    queueMicrotask(() => setIsUserAuthenticated(null));
+    let canceled = false;
+    queueMicrotask(() => {
+      if (!canceled) setIsUserAuthenticated(null);
+    });
+    return () => {
+      canceled = true;
+    };
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/force-graph/use-graph-data.ts (2)

26-27: Naming nit: clarify intent

Consider naming to whitelistedAgents for readability.

-  const allAgents = allAgentsData?.filter((agent) => agent.isWhitelisted) ?? [];
+  const whitelistedAgents =
+    allAgentsData?.filter((agent) => agent.isWhitelisted) ?? [];

34-51: Pass a safe default for signals

If createSimplifiedGraphData tolerates empty arrays, prefer a default to avoid internal undefined checks.

-    return createSimplifiedGraphData(
-      allAgents,
+    return createSimplifiedGraphData(
+      whitelistedAgents,
       allPermissions,
       allocatorAddress,
-      allSignals,
+      allSignals ?? [],
       allAgentsData,
     );
apps/torus-governance/src/app/_components/expanded-view-content.tsx (1)

27-36: Use rAF + layout effect instead of setTimeout(0) for visual measurement

For UI-driven measurements/updates, requestAnimationFrame in useLayoutEffect avoids a macrotask delay and flicker, and it’s cancelable.

-  useEffect(() => {
+  useLayoutEffect(() => {
     // Check if the content is overflowing to decide whether to show the expand/collapse button
     if (contentRef.current) {
       const contentHeight = contentRef.current.scrollHeight;
       const maxAllowedHeight = 250;
-      const timer = setTimeout(() => {
-        setIsOverflowing(contentHeight > maxAllowedHeight);
-      }, 0);
-      return () => clearTimeout(timer);
+      const raf = requestAnimationFrame(() => {
+        setIsOverflowing(contentHeight > maxAllowedHeight);
+      });
+      return () => cancelAnimationFrame(raf);
     }
   }, [body]);

Additional change required outside this hunk:

- import { useEffect, useRef, useState } from "react";
+ import { useLayoutEffect, useRef, useState } from "react";
apps/torus-portal/src/app/(pages)/network-operations/manage-agent/_components/update-agent-form.tsx (2)

72-80: Replace setTimeout(0) with rAF for preview updates

Preview image swap is visual; rAF aligns with paint and gives a proper cancel path.

-  const timer = setTimeout(() => {
-      setCurrentImagePreview(currentImageBlobUrl);
-    }, 0);
-  return () => clearTimeout(timer);
+  const raf = requestAnimationFrame(() => {
+    setCurrentImagePreview(currentImageBlobUrl);
+  });
+  return () => cancelAnimationFrame(raf);

121-145: Avoid macrotask and add cleanup for form reset

Use a microtask with an unmount guard; fewer delays and no timer handle to manage.

   useEffect(() => {
     if (agent && agentMetadata) {
       const originalData = {
         name: agent.name ?? "",
         shortDescription: agentMetadata.metadata.short_description || "",
         description: agentMetadata.metadata.description || "",
         website: agentMetadata.metadata.website ?? "",
         apiUrl: agent.apiUrl ?? "",
         imageFile: undefined,
         socials: {
           twitter: agentMetadata.metadata.socials?.twitter ?? "",
           github: agentMetadata.metadata.socials?.github ?? "",
           telegram: agentMetadata.metadata.socials?.telegram ?? "",
           discord: agentMetadata.metadata.socials?.discord ?? "",
         },
       };
-
-      setTimeout(() => {
-        setOriginalFormData(originalData);
-        form.reset(originalData);
-        setHasUnsavedChanges(false);
-      }, 0);
+      let canceled = false;
+      queueMicrotask(() => {
+        if (canceled) return;
+        setOriginalFormData(originalData);
+        form.reset(originalData);
+        setHasUnsavedChanges(false);
+      });
+      return () => {
+        canceled = true;
+      };
     }
   }, [agent, agentMetadata, form]);
apps/torus-governance/hooks/use-agent-applications.tsx (1)

53-127: Tighten the null filter with a type guard

Avoid the Boolean cast trick; a local predicate is clearer and preserves types.

-      .filter(
-        Boolean as unknown as (
-          value: ApplicationResult | null,
-        ) => value is ApplicationResult,
-      )
+      .filter(
+        (v): v is ApplicationResult => v !== null
+      )
apps/torus-portal/src/hooks/use-weekly-usd.ts (3)

27-29: Fix typos in comment.

"dolar Brice (<- lol)" → "dollar price" and "Coingecko" → "CoinGecko".

-  // Queries the Torus dolar Brice (<- lol) from Coingecko
+  // Queries the Torus dollar price from CoinGecko

86-90: Optional: avoid precision loss when amounts can grow.

tokensPerWeek.toNumber() can lose precision for large values. Consider using a decimal-safe helper (e.g., returning a JS number only after bounding) or computing usdValue from a string/Big representation if available.


92-100: Hoist number formatters to avoid re-allocating each render.

Create reusable Intl.NumberFormat instances at module scope (stateless), keeping with the PR’s removal of memoization.

+const TORUS_FORMAT = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+const USD_FORMAT = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, maximumFractionDigits: 2 });

   const displayTokensPerWeek = (() => {
     if (isLoading || isError) return "0.00 TORUS";
-    return (
-      tokensPerWeek.toNumber().toLocaleString("en-US", {
-        minimumFractionDigits: 2,
-        maximumFractionDigits: 2,
-      }) + " TORUS"
-    );
+    return TORUS_FORMAT.format(tokensPerWeek.toNumber()) + " TORUS";
   })();

   const displayUsdValue = (() => {
     if (isLoading || isError) return "$0.00";
-    return usdValue.toLocaleString("en-US", {
-      style: "currency",
-      currency: "USD",
-      minimumFractionDigits: 2,
-      maximumFractionDigits: 2,
-    });
+    return USD_FORMAT.format(usdValue);
   })();

Also applies to: 103-111

apps/torus-governance/src/app/_components/filter-content.tsx (2)

62-81: Tighten pathname checks to avoid false positives.

Using includes("/proposals") and includes("/agents") may match unintended routes (e.g., /proposals-archive). Prefer segment-aware checks.

-    if (pathname.includes("/proposals")) {
+    if (pathname === "/proposals" || pathname.startsWith("/proposals/")) {
       ...
-    } else if (pathname.includes("/agents")) {
+    } else if (pathname === "/agents" || pathname.startsWith("/agents/")) {
       ...

120-123: Optional: avoid history spam on every keystroke.

router.push on each input change can bloat history. Consider router.replace or a small debounce.

-    router.push(`?${query}`);
+    router.replace(`?${query}`);
apps/torus-wallet/src/hooks/useAPR.ts (1)

112-127: Optional: drop queries useMemo entirely.

Since derived flags are now direct, queries can be a plain array or removed.

-  const queries = useMemo(
-    () => [
-      totalStakeQuery,
-      totalIssuanceQuery,
-      recyclingPercentageQuery,
-      treasuryEmissionFeeQuery,
-      incentivesRatioQuery,
-    ],
-    [
-      totalStakeQuery,
-      totalIssuanceQuery,
-      recyclingPercentageQuery,
-      treasuryEmissionFeeQuery,
-      incentivesRatioQuery,
-    ],
-  );
+  const queries = [
+    totalStakeQuery,
+    totalIssuanceQuery,
+    recyclingPercentageQuery,
+    treasuryEmissionFeeQuery,
+    incentivesRatioQuery,
+  ];
apps/torus-page/src/app/_components/torus-animation.tsx (1)

72-91: Dispose ShaderMaterial on unmount to free GPU memory.

Add a cleanup to call dispose() for safety, especially when using <primitive object={...} attach="material" />.

   const shaderMaterial = useMemo(() => {
     const uniforms = {
       time: { value: 0 },
       intensity: { value: 0.8 },
       speed: { value: 0.1 },
       lineThickness: { value: 0.01 },
       pixelSize: { value: 8 },
     };

     return new THREE.ShaderMaterial({
       uniforms,
       vertexShader,
       fragmentShader,
       transparent: true,
       depthWrite: false,
       depthTest: true,
       side: THREE.DoubleSide,
       blending: THREE.AdditiveBlending,
     });
   }, []);
+
+  // Ensure material gets disposed on unmount
+  useEffect(() => {
+    return () => {
+      shaderMaterial.dispose();
+    };
+  }, [shaderMaterial]);
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/force-graph/use-graph-interactions.ts (1)

12-17: Simplified handler is fine here.

Given the small body, dropping useCallback is acceptable. If passed deep, consider memoizing later for render churn.

apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx (1)

78-89: Avoid shadowing state variable originTxUrl.

Local tuple element originTxUrl shadows the state variable of the same name, which hurts readability and increases risk of mistakes. Rename the local binding.

-        const [txUrlError, originTxUrl] = trySync(() =>
+        const [txUrlError, txUrl] = trySync(() =>
           multiProvider.tryGetExplorerTxUrl(origin, { hash: originTxHash }),
         );
@@
-        } else if (originTxUrl) {
-          setOriginTxUrl(fixDoubleSlash(originTxUrl));
+        } else if (txUrl) {
+          setOriginTxUrl(fixDoubleSlash(txUrl));
         }
apps/torus-bridge/src/app/_components/transfer-details/index.tsx (1)

17-29: Auto-close the modal when there’s no latest transfer.

Currently the modal never auto-closes if transfers become empty or loading resumes. Add a symmetric close path.

   useLayoutEffect(() => {
     const shouldShowModal = !transferLoading && transfers.length > 0;
     const latestTransfer = shouldShowModal
       ? transfers[transfers.length - 1]
       : null;

     if (latestTransfer && latestTransfer !== selectedTransfer) {
       queueMicrotask(() => {
         setSelectedTransfer(latestTransfer);
         setIsModalOpen(true);
       });
+    } else if (!shouldShowModal && (isModalOpen || selectedTransfer)) {
+      queueMicrotask(() => {
+        setIsModalOpen(false);
+        setSelectedTransfer(null);
+      });
     }
-  }, [transfers, transferLoading, selectedTransfer]);
+  }, [transfers, transferLoading, selectedTransfer, isModalOpen]);

Also applies to: 29-29

apps/torus-bridge/src/app/_components/tokens/token-select-field.tsx (2)

27-31: Simplify isAutomaticSelection expression.

Equivalent and clearer as “length <= 1”.

-  const isAutomaticSelection =
-    tokensWithRoute.length !== 1 ? tokensWithRoute.length === 0 : true;
+  const isAutomaticSelection = tokensWithRoute.length <= 1;

27-31: Stabilize tokensWithRoute identity to reduce effect churn.

Memoize to avoid re-running the effect on every render when the route hasn’t changed.

Add import:

import { useMemo } from "react";

Memoize:

const tokensWithRoute = useMemo(
  () => warpCore.getTokensForRoute(origin, destination),
  [warpCore, origin, destination],
);
apps/torus-governance/src/app/_components/comments/view-comment.tsx (2)

151-166: Avoid re-sorting on every render; consider memoizing or precomputing keys.

Sorting and repeatedly constructing Date objects each render can add up with many comments. Using useMemo (keyed by comments reference and sortBy) keeps behavior while avoiding unnecessary work.

Apply:

-  const sortedComments = (() => {
+  const sortedComments = useMemo(() => {
     if (!comments) return [];
     return [...comments].sort((a, b) => {
       if (sortBy === "newest") {
         return (
           new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
         );
       } else if (sortBy === "oldest") {
         return (
           new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
         );
       } else {
         return (Number(b.likes) || 0) - (Number(a.likes) || 0);
       }
     });
-  })();
+  }, [comments, sortBy]);

And add the import:

-import { useCallback, useEffect, useLayoutEffect, useState } from "react";
+import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";

188-240: Nice consolidation of vote logic; add in-flight guard to prevent double submits.

Wire the mutations’ pending state to disable buttons and avoid duplicate requests; optional optimistic UI can further improve UX.

If you’re on React Query v5, isPending is available; otherwise use isLoading.

   const castVoteMutation = api.commentInteraction.reaction.useMutation();
   const deleteVoteMutation =
     api.commentInteraction.deleteReaction.useMutation();
 
+  const isVoting =
+    (castVoteMutation as any).isPending ?? castVoteMutation.isLoading ||
+    (deleteVoteMutation as any).isPending ?? deleteVoteMutation.isLoading || false;

Outside this block, enable the disabled props:

-                      // disabled={isVoting || !selectedAccount?.address}
+                      disabled={isVoting || !selectedAccount?.address}
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-signals-accordion.tsx (1)

23-27: Recompute cost is tiny here, but memoizing is a safe default if the list grows.

Filtering on each render is fine now; using useMemo guards against future growth or accidental re-renders.

-  const nodeSignals = (() => {
+  const nodeSignals = useMemo(() => {
     if (!allSignals || !selectedNode?.id) return [];
 
     return allSignals.filter((signal) => signal.agentKey === selectedNode.id);
-  })();
+  }, [allSignals, selectedNode?.id]);

Also add:

+import { useMemo } from "react";
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/permission-graph-command.tsx (1)

93-178: Build search groups once per dependency change to avoid heavy per-render work.

This block walks several large collections; recomputation on keystroke/rerender can be noticeable. Memoize by graphData and getFormattedAddress.

-  const searchGroups = (() => {
+  const searchGroups = useMemo(() => {
     if (!graphData) return [];
 
     return [
       {
         type: "agent",
         icon: Users,
         heading: "Agents",
         items: Array.from(
           new Map(
             graphData.agents.map((agent) => [
               agent.accountId,
               {
                 id: agent.accountId,
                 displayName: agent.name || smallAddress(agent.accountId),
                 subtitle: `${agent.role} • ${smallAddress(agent.accountId)}`,
                 searchText:
                   `${agent.accountId} ${agent.name} ${agent.role}`.toLowerCase(),
               },
             ]),
           ).values(),
         ),
       },
       {
         type: "stream",
         icon: Zap,
         heading: "Stream Permissions",
         items: Array.from(
           new Map(
             graphData.permissions.emission.map((permission) => [
               permission.id,
               {
                 id: `permission-${permission.id}`,
                 displayName: "Stream Permission",
                 subtitle: `From: ${getFormattedAddress(permission.delegatorAccountId, 4)} → To: ${
                   permission.distributionTargets?.length
                     ? `${permission.distributionTargets.length} recipients`
                     : getFormattedAddress(permission.recipientAccountId, 4)
                 }`,
                 searchText:
                   `${permission.id} ${permission.delegatorAccountId} stream emission`.toLowerCase(),
               },
             ]),
           ).values(),
         ),
       },
       {
         type: "namespace",
         icon: Package,
         heading: "Capability Permissions",
         items: Array.from(
           new Map(
             graphData.permissions.namespace.map((permission) => [
               permission.id,
               {
                 id: `permission-${permission.id}`,
                 displayName: "Capability Permission",
                 subtitle: `From: ${getFormattedAddress(permission.delegatorAccountId, 4)} → To: ${getFormattedAddress(permission.recipientAccountId, 4)}`,
                 searchText:
                   `${permission.id} ${permission.delegatorAccountId} ${permission.recipientAccountId} namespace capability`.toLowerCase(),
               },
             ]),
           ).values(),
         ),
       },
       {
         type: "signal",
         icon: Radio,
         heading: "Signals",
         items: Array.from(
           new Map(
             graphData.signals.map((signal) => [
               signal.id,
               {
                 id: `signal-${signal.id}`,
                 displayName: signal.title,
                 subtitle: `From: ${getFormattedAddress(signal.agentKey, 4)}`,
                 searchText:
                   `${signal.title} ${signal.description} ${signal.agentKey} signal`.toLowerCase(),
               },
             ]),
           ).values(),
         ),
       },
     ];
-  })();
+  }, [graphData, getFormattedAddress]);
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-permission.tsx (2)

46-90: Aggregate duplicate stream weights and avoid repeating totals per stream

  • When the same (targetAccountId, streamId) appears multiple times, the first weight wins and subsequent ones are ignored. Safer to accumulate.
  • The total value badge is rendered inside the per-stream loop, so the same total is repeated for each stream row. Compute once per target (or show per-stream amounts).
   for (const p of allPermissions) {
     if (p.permissions.permissionId !== permissionData.permissionId) continue;
     const target = p.emission_distribution_targets;
     if (!target?.targetAccountId) continue;
     const targetId = target.targetAccountId;
     const streamKey = target.streamId ?? "default";
     if (!grouped.has(targetId)) grouped.set(targetId, new Map());
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const streams = grouped.get(targetId)!;
-    if (!streams.has(streamKey)) {
-      streams.set(streamKey, {
-        streamId: target.streamId,
-        weight: target.weight,
-      });
-    }
+    const streams = grouped.get(targetId)!;
+    const existing = streams.get(streamKey);
+    if (existing) {
+      existing.weight += target.weight ?? 0;
+    } else {
+      streams.set(streamKey, {
+        streamId: target.streamId,
+        weight: target.weight ?? 0,
+      });
+    }
   }
 
-  return Array.from(grouped.entries()).map(([targetAccountId, streams]) => {
-    const values = Array.from(streams.values());
-    const hasSpecificStreams = values.some((s) => s.streamId);
-    // If there are specific streams, drop the default aggregate to avoid duplicated weights
-    if (hasSpecificStreams && streams.has("default")) {
-      streams.delete("default");
-    }
-    return {
-      targetAccountId,
-      streams: Array.from(streams.values()),
-    };
-  });
+  return Array.from(grouped.entries()).map(([targetAccountId, streams]) => {
+    // If there are specific streams, drop the default aggregate to avoid duplicated weights
+    if (Array.from(streams.values()).some((s) => s.streamId) && streams.has("default")) {
+      streams.delete("default");
+    }
+    const streamsArr = Array.from(streams.values());
+    const totalWeight = streamsArr.reduce((acc, s) => acc + s.weight, 0);
+    return { targetAccountId, streams: streamsArr, totalWeight };
+  });

If you prefer, I can also move the “total value” badge outside the per-stream map and use entry.totalWeight.


93-107: Stabilize accountIds to avoid unnecessary emissions fetches

allAccountIds is a fresh array every render. If the hook keys on reference equality, this can cause needless re-fetching. At minimum, return a sorted array for stable ordering.

-    return Array.from(accounts);
+    return Array.from(accounts).sort();

If the hook depends on reference equality, consider memoizing the array or keying the hook internally by a sorted-joined string.

apps/torus-portal/src/app/(pages)/(permission-graph)/_components/permission-graph-overview.tsx (1)

4-11: Use theme tokens instead of hard-coded colors

Swap gray/white classes for design tokens to respect light/dark themes and future theming.

-const Bar = () => <div className="h-4 w-px bg-gray-800" />;
+const Bar = () => <div className="h-4 w-px bg-border" />;

 const Stat = ({ label, value }: { label: string; value: number }) => (
   <div className="flex items-center gap-2">
-    <span className="text-xs font-medium text-gray-500">{label}</span>
-    <span className="text-sm font-semibold text-white">{value}</span>
+    <span className="text-xs font-medium text-muted-foreground">{label}</span>
+    <span className="text-sm font-semibold text-foreground">{value}</span>
   </div>
 );
apps/torus-portal/src/app/(pages)/(permission-graph)/page.tsx (1)

45-67: Replace 0ms setTimeout with queueMicrotask in effect

This PR’s goal is to remove 0ms timers; using a microtask here preserves ordering without a macrotask hop, avoids timer overhead, and aligns with your stated approach.

-    // Use setTimeout to avoid direct setState in effect
-    const timer = setTimeout(updateSelection, 0);
-    return () => clearTimeout(timer);
+    // Defer to microtask to avoid synchronous setState while staying in the same tick
+    let cancelled = false;
+    queueMicrotask(() => {
+      if (!cancelled) updateSelection();
+    });
+    return () => {
+      cancelled = true;
+    };
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx (3)

144-168: Sort immutably to avoid in-place mutation; consider memoizing if list is large.

.sort() mutates the array. Sorting copies keeps data flow clearer. If processedPermissions can be large, re-sorting every render may regress perf—useMemo keyed on processedPermissions would help.

-    const sortPermissions = (permissions: typeof processedPermissions) => {
-      return permissions.sort((a, b) => {
+    const sortPermissions = (permissions: typeof processedPermissions) => {
+      return [...permissions].sort((a, b) => {
         // Sort by type: Capabilities first (namespace_permissions), then Emissions
         const aIsCapability = !!a.details?.namespace_permissions;
         const bIsCapability = !!b.details?.namespace_permissions;
         if (aIsCapability && !bIsCapability) return -1;
         if (!aIsCapability && bIsCapability) return 1;
         // Within same type, sort alphabetically by permission ID
         const aId = a.details?.permissions.permissionId ?? "";
         const bId = b.details?.permissions.permissionId ?? "";
         return aId.localeCompare(bId);
       });
     };

308-316: Prefer stable keys.

Use the path value as the React key instead of the array index to avoid key collisions and preserve state on reorder.

-                                      {paths.map((path, index) => (
-                                        <div key={index}>
+                                      {paths.map((path) => (
+                                        <div key={path}>
                                           <ShortenedCapabilityPath
                                             path={path}
                                             showTooltip={true}
                                           />
                                         </div>
                                       ))}

461-487: Inline element IIFE is okay; a small readability tweak helps.

A simple renderer function avoids the IIFE pattern and reads cleaner with identical behavior.

-  const PermissionsContent = (() => (
+  const renderPermissionsContent = () => (
     <ScrollArea className="h-[calc(100vh-26rem)]">
       {processedPermissions.length > 0 ? (
         <Accordion type="single" collapsible className="w-full">
           {/* Delegated Permissions - only show if there are delegated permissions */}
           {groupedPermissions.delegated.length > 0 &&
             renderPermissionGroup(
               groupedPermissions.delegated,
               "Delegated",
               <ArrowUpRight className="text-muted-foreground h-4 w-4" />,
             )}
           {/* Received Permissions - only show if there are received permissions */}
           {groupedPermissions.received.length > 0 &&
             renderPermissionGroup(
               groupedPermissions.received,
               "Received",
               <ArrowDownLeft className="text-muted-foreground h-4 w-4" />,
             )}
         </Accordion>
       ) : (
         <div className="mt-8 text-center text-gray-500">
           No permissions found for this agent
         </div>
       )}
     </ScrollArea>
-  ))();
+  );
@@
-          {PermissionsContent}
+          {renderPermissionsContent()}

Also applies to: 500-500

apps/torus-wallet/src/hooks/useTransactions.tsx (2)

29-31: Optional: stabilize resetKey computation to avoid spurious resets.

JSON.stringify on a filters object can reorder keys and trigger unnecessary resets. Consider a stable stringify (e.g., fast-json-stable-stringify) or building resetKey from a well-defined subset of sorted fields.


101-104: Optional: refresh could reset totals/hasMore for consistency.

Consider also resetting totalTransactions and hasMore on refresh to prevent brief stale counts.

…ompiler option in Next.js config for multiple apps
… dev changes

- Merge conflict resolved in torus-bridge/next.config.mjs
- Combined React Compiler flag with latest dev configurations
- Fixed SEO metadata imports in wallet transfers page
- All React Compiler configurations preserved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
apps/torus-portal/next.config.mjs (1)

5-7: Gate the experimental React Compiler behind an env flag for safe rollout.

This lets you flip it off quickly if regressions appear in one app.

Apply within the changed block:

-  experimental: {
-    reactCompiler: true,
-  },
+  experimental: {
+    reactCompiler: enableReactCompiler,
+  },

And add near the top of the file (outside the hunk):

const enableReactCompiler = process.env.REACT_COMPILER === "true";
apps/torus-wallet/next.config.mjs (1)

9-11: Same: toggle React Compiler via env for quick rollback.

-  experimental: {
-    reactCompiler: true,
-  },
+  experimental: {
+    reactCompiler: enableReactCompiler,
+  },

Add once near the top:

const enableReactCompiler = process.env.REACT_COMPILER === "true";
apps/torus-bridge/next.config.mjs (1)

20-22: Guard experimental compiler with an env switch.

-  experimental: {
-    reactCompiler: true,
-  },
+  experimental: {
+    reactCompiler: enableReactCompiler,
+  },

Add near imports:

const enableReactCompiler = process.env.REACT_COMPILER === "true";
apps/torus-governance/next.config.mjs (1)

5-7: Env-gate the compiler; default can be on in CI/prod via env.

-  experimental: {
-    reactCompiler: true,
-  },
+  experimental: {
+    reactCompiler: enableReactCompiler,
+  },

Add at top:

const enableReactCompiler = process.env.REACT_COMPILER === "true";
apps/torus-allocator/next.config.mjs (1)

5-7: Env switch for experimental.reactCompiler.

-  experimental: {
-    reactCompiler: true,
-  },
+  experimental: {
+    reactCompiler: enableReactCompiler,
+  },

Add at top:

const enableReactCompiler = process.env.REACT_COMPILER === "true";
apps/torus-page/next.config.mjs (1)

5-7: Env-gate for safer staged rollout across apps.

-  experimental: {
-    reactCompiler: true,
-  },
+  experimental: {
+    reactCompiler: enableReactCompiler,
+  },

Add at top:

const enableReactCompiler = process.env.REACT_COMPILER === "true";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 1e64cc0 and ebe6e88.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/torus-allocator/next.config.mjs (1 hunks)
  • apps/torus-bridge/next.config.mjs (1 hunks)
  • apps/torus-governance/next.config.mjs (1 hunks)
  • apps/torus-page/next.config.mjs (1 hunks)
  • apps/torus-portal/next.config.mjs (1 hunks)
  • apps/torus-wallet/next.config.mjs (1 hunks)
  • package.json (1 hunks)
🔇 Additional comments (1)
package.json (1)

11-11: Avoid double-enabling React Compiler; verify necessity of Babel plugin.

package.json lists "babel-plugin-react-compiler": "19.1.0-rc.3" (matches npm dist-tag); react/react-dom are overridden to ^19.0.0 and Next is referenced as workspace "catalog". The repo search for Babel configs errored, so confirm the plugin is not also enabled via any Babel config or package.json "babel" field.

Run: rg -n --hidden --glob '!.git/' --glob '!node_modules/' 'babel-plugin-react-compiler' || true

If not referenced elsewhere, remove the devDependency to reduce supply-chain surface; if kept, pin to a stable version compatible with your React/Next stack.

- Downgraded several package versions to maintain compatibility and stability, including '@next/third-parties', '@react-three/drei', '@tanstack/react-query', and others.
- Adjusted versions for '@trpc/client', '@trpc/react-query', and '@trpc/server' to align with the latest stable releases.
- Updated '@types/node' and other dependencies to ensure consistency across the project.
- Ensured proper formatting by adding a newline at the end of the file to comply with coding standards.
- Updated several components in the torus-bridge and torus-governance applications to use setTimeout instead of queueMicrotask for state updates after client-side mounting and authentication checks.
- This change improves compatibility and ensures that state updates are handled correctly in the React lifecycle.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/torus-portal/src/hooks/use-blob-url.tsx (1)

12-15: Replace setTimeout(0) with queueMicrotask; clear URL on null and guard against post-cleanup setState.

  • setTimeout(0) contradicts the PR goal and risks calling setState after cleanup/unmount.
  • When blob becomes null, url remains a revoked URL; set it to null.

Apply this diff within the changed lines:

-    setTimeout(() => setUrl(objectUrl), 0);
-    return () => {
-      URL.revokeObjectURL(objectUrl);
-    };
+    let cancelled = false;
+    queueMicrotask(() => {
+      if (!cancelled) setUrl(objectUrl);
+    });
+    return () => {
+      cancelled = true;
+      URL.revokeObjectURL(objectUrl);
+    };

And update the early return outside the selected range to clear stale state:

if (!blob) {
  setUrl(null);
  return;
}

Verify no remaining zero-delay timers in the repo:

#!/bin/bash
# Find setTimeout(..., 0) usages
rg -nP --type=ts --type=tsx -C2 'setTimeout\s*\(\s*[^,]+,\s*0\s*\)'
🧹 Nitpick comments (2)
apps/torus-bridge/next.config.mjs (2)

70-87: Reconsider custom splitChunks; Next’s defaults are tuned and overrides can backfire.

Manual cache groups can increase duplication or block Next’s heuristics. If you keep this, gate behind an env to allow quick rollback.

Apply this diff to gate the override:

-      // Optimize bundle splitting
-      config.optimization.splitChunks = {
+      // Optimize bundle splitting (optional; gate for easy rollback)
+      if (process.env.ENABLE_CUSTOM_SPLIT_CHUNKS === "true") {
+        config.optimization.splitChunks = {
           chunks: "all",
           cacheGroups: {
             vendor: {
               test: /[\\/]node_modules[\\/]/,
               name: "vendors",
               chunks: "all",
               priority: 10,
             },
             wallet: {
               test: /[\\/]node_modules[\\/](@solana|@cosmos-kit|@starknet|@hyperlane)[\\/]/,
               name: "wallet-vendors",
               chunks: "all",
               priority: 20,
             },
           },
-      };
+        };
+      }

3-6: Remove stale jiti/env-loading comments or wire them up.

These comments reference createJiti but it isn’t used; they add noise.

Apply this diff:

-// Import env files to validate at build time. Use jiti so we can load .ts files in here.
-// WARNING: ONLY NEEDED IF NEXT_PUBLIC_* VARIABLES ARE USED IN THE APP DIRECTLY
-// createJiti(fileURLToPath(import.meta.url))("./src/env");
+// If you need build-time validation of NEXT_PUBLIC_* vars, wire up env loading here.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 ebe6e88 and 2ae485f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • apps/torus-bridge/next.config.mjs (3 hunks)
  • apps/torus-bridge/src/app/_components/buttons/connect-aware-submit-button.tsx (1 hunks)
  • apps/torus-bridge/src/context/evm-wallet-provider.tsx (1 hunks)
  • apps/torus-bridge/src/context/solana-wallet-provider.tsx (2 hunks)
  • apps/torus-governance/hooks/use-sign-in.tsx (2 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/page.tsx (1 hunks)
  • apps/torus-portal/src/hooks/use-blob-url.tsx (1 hunks)
  • package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • package.json
  • apps/torus-bridge/src/app/_components/buttons/connect-aware-submit-button.tsx
  • apps/torus-portal/src/app/(pages)/(permission-graph)/page.tsx
  • apps/torus-governance/hooks/use-sign-in.tsx
  • apps/torus-bridge/src/context/solana-wallet-provider.tsx
  • apps/torus-bridge/src/context/evm-wallet-provider.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-portal/src/hooks/use-blob-url.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-portal/src/hooks/use-blob-url.tsx
🧬 Code graph analysis (1)
apps/torus-bridge/next.config.mjs (5)
apps/torus-governance/next.config.mjs (1)
  • config (2-25)
apps/torus-page/next.config.mjs (1)
  • config (2-15)
apps/torus-wallet/next.config.mjs (1)
  • config (6-39)
apps/torus-allocator/next.config.mjs (1)
  • config (2-20)
apps/torus-portal/next.config.mjs (1)
  • config (2-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-cache-pr-activate / clean-stale
  • GitHub Check: lint
  • GitHub Check: typecheck
🔇 Additional comments (5)
apps/torus-bridge/next.config.mjs (5)

50-56: Node protocol replacement for client builds looks good.

Scoped to !isServer, regex is precise, and the replacement integrates cleanly with the fallbacks/ProvidePlugin once fixed.


12-12: Enable React Compiler carefully (experimental).

Good to keep aligned across apps; suggest gating with an env for quick disable if regressions appear.

Apply this minimal gate if you want the safety valve:

-    reactCompiler: true,
+    reactCompiler: process.env.REACT_COMPILER === "true",

Confirm: the repo adds babel-plugin-react-compiler and CI covers pages most affected by memoization changes.


57-61: Good: explicitly disabling unpolyfillable Node modules on the client.

Marking fs, net, tls, and child_process as false avoids bloating the bundle or accidental shimming.


44-44: No action needed (comment-only change).


26-33: YAML/Turbopack: confirm Turbopack rule compatibility and address missing polyfills

  • yaml-loader is present in package.json (FOUND).
  • apps/torus-bridge/next.config.mjs (lines 26–33; also 90–95) uses turbopack.rules but the app's package.json lists Next as "catalog:" (no concrete version) — run in dev (Turbopack) or pin a concrete Next version (e.g., ^14/^15) to confirm the rules shape is honored.
  • Missing Node polyfills in package.json: util, crypto-browserify, stream-browserify, events, process, buffer — add only if you hit build/runtime errors that require them.

…onfig

- Deleted commented-out code for importing environment files in the Next.js configuration of both the torus-bridge and torus-wallet applications.
- This cleanup enhances code readability and reduces clutter in the configuration files.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/torus-bridge/next.config.mjs (1)

38-38: Don’t ship production browser source maps by default—gate behind an env flag.

Exposes source and bloats payloads if left on. Make it opt‑in.

-  productionBrowserSourceMaps: true,
+  // Opt-in only to avoid leaking source + increasing payloads
+  productionBrowserSourceMaps: process.env.ENABLE_PROD_SOURCEMAPS === "true",
🧹 Nitpick comments (2)
apps/torus-bridge/next.config.mjs (2)

66-83: Custom splitChunks can fight Next defaults—gate or measure.

Safe to keep, but consider opt‑in to avoid regressions unless benchmarked.

-      config.optimization.splitChunks = {
+      if (process.env.ENABLE_CUSTOM_SPLIT_CHUNKS === "true") {
+        config.optimization.splitChunks = {
           chunks: "all",
           cacheGroups: {
             vendor: {
               test: /[\\/]node_modules[\\/]/,
               name: "vendors",
               chunks: "all",
               priority: 10,
             },
             wallet: {
               test: /[\\/]node_modules[\\/](@solana|@cosmos-kit|@starknet|@hyperlane)[\\/]/,
               name: "wallet-vendors",
               chunks: "all",
               priority: 20,
             },
           },
-      };
+        };
+      }

86-90: YAML loader present — de‑dupe turbopack vs webpack rule or document intent.
yaml-loader is declared in apps/torus-bridge/package.json (and appears in pnpm-lock.yaml). next.config.mjs registers it twice: turbopack.rules (".yaml"/".yml") and webpack.module.rules (test: /.ya?ml$/). If both are intentional (Turbopack vs Webpack), add an inline comment explaining why; otherwise remove the redundant rule.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 2ae485f and a77c2af.

📒 Files selected for processing (2)
  • apps/torus-bridge/next.config.mjs (3 hunks)
  • apps/torus-wallet/next.config.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/torus-wallet/next.config.mjs
🧰 Additional context used
🧬 Code graph analysis (1)
apps/torus-bridge/next.config.mjs (5)
apps/torus-wallet/next.config.mjs (1)
  • config (2-35)
apps/torus-allocator/next.config.mjs (1)
  • config (2-20)
apps/torus-governance/next.config.mjs (1)
  • config (2-25)
apps/torus-portal/next.config.mjs (1)
  • config (2-20)
apps/torus-page/next.config.mjs (1)
  • config (2-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / docker-build
  • GitHub Check: torus-cache-pr-activate / docker-build
  • GitHub Check: torus-allocator-pr-activate / docker-build
  • GitHub Check: torus-bridge-pr-activate / docker-build
  • GitHub Check: torus-wallet-pr-activate / docker-build
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / docker-build
  • GitHub Check: torus-page-pr-activate / docker-build
🔇 Additional comments (3)
apps/torus-bridge/next.config.mjs (3)

1-1: Webpack fallbacks resolve to wrong paths — verification blocked (ripgrep skipped files)

The verification script returned "No files were searched", so I couldn't confirm whether crypto-browserify, stream-browserify, events, process, buffer, and util exist in the workspace (apps/torus-bridge/next.config.mjs change depends on them).

  • Re-run verification from repo root:
    rg -n --no-heading -S 'crypto-browserify|stream-browserify|events|process|buffer|util' package.json package-lock.json yarn.lock pnpm-lock.yaml || true
  • If any are missing, install them and apply the suggested next.config.mjs changes: use createRequire(import.meta.url), add webpack.ProvidePlugin({ process: 'process/browser', Buffer: ['buffer','Buffer'] }), and set resolve.fallback to require.resolve(...) including buffer.

8-20: Gate experimental React compiler behind an env and verify Next.js compatibility.

experimental.reactCompiler is enabled in apps/*/next.config.mjs; root package.json includes babel-plugin-react-compiler@19.1.0-rc.3 and pnpm overrides pin react/react-dom to ^19.0.0, but Next.js versions are listed as "catalog:" (not resolved here) — confirm the actual Next.js release supports this experimental flag before enabling globally and roll out behind a feature gate.

Files: apps/*/next.config.mjs (e.g. apps/torus-bridge/next.config.mjs).

-    reactCompiler: true,
+    reactCompiler: process.env.ENABLE_REACT_COMPILER === "true",

22-28: Verify turbopack.rules is actually read by your Next version.

Next’s turbopack supports a rules map and lists yaml-loader as a supported loader, but the config key was renamed from experimental.turbo/turbo → turbopack across releases — confirm the workspace Next version and that you run the dev server with Turbopack; apps/torus-bridge/next.config.mjs already defines turbopack.rules and also adds a webpack YAML-loader fallback (keep the webpack rule for Webpack builds).

…ider management

- Updated the ProviderRenderer component in the torus-bridge application to handle providers with props, allowing for more flexible component rendering.
- Introduced a createProviderWithProps utility function to streamline the creation of provider components with associated props.
- Refactored the AppContextProvider to utilize the new provider structure, improving readability and maintainability of the code.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (9)
apps/torus-bridge/src/context/evm-wallet-provider.tsx (4)

129-129: Re-init trigger may miss upstream chain-set changes.

If multiProvider’s identity is stable while its known chains change internally, this memo won’t re-run. Consider keying off a stable “version” or the known chain names for safer invalidation.

Apply:

-  }, [multiProvider]);
+  }, [multiProvider, /* safer: */ multiProvider.getKnownChainNames?.()?.join("|")]);

If getKnownChainNames isn’t cheap, expose a cheap version/timestamp instead.


155-155: Depend directly on token list to avoid stale initialChain.

Depending on warpCore (object) risks missing updates if tokens mutate without reference change.

Apply:

-  }, [multiProvider, warpCore]);
+  }, [multiProvider, warpCore.tokens]);

If tokens can mutate in place, also include a light key:

+  }, [multiProvider, warpCore.tokens, warpCore.tokens.length]);

82-90: Runtime guards and theme memo (optional).

  • createConfig.client assumes at least one RPC URL; add a defensive check to avoid undefined access.
  • midnightTheme object is recreated each render; memoize to reduce churn (minor).
     client({ chain }) {
-      const transport = http(chain.rpcUrls.default.http[0]);
+      const url = chain.rpcUrls.default.http[0];
+      if (!url) throw new Error(`No HTTP RPC URL for chain ${chain.id}`);
+      const transport = http(url);
       return createClient({ chain, transport });
     },
-  return (
+  const theme = useMemo(
+    () =>
+      midnightTheme({
+        accentColor: "#A7AFBE",
+        borderRadius: "small",
+        fontStack: "system",
+      }),
+    [],
+  );
+  return (
     <WagmiProvider config={wagmiConfig}>
       <RainbowKitProvider
-        theme={midnightTheme({
-          accentColor: "#A7AFBE",
-          borderRadius: "small",
-          fontStack: "system",
-        })}
+        theme={theme}
         initialChain={initialChain}
       >

Also applies to: 164-170


101-104: Add concise JSDoc for exported components.

Per docs/DOCUMENTATION_STYLE.md, add brief JSDoc for initWagmi and EvmWalletProvider (props, behavior, errors).

Also applies to: 161-174

apps/torus-bridge/src/app/_components/provider-renderer.tsx (4)

10-10: Fix lint: avoid any in ProviderArray.

Use unknown to satisfy the lint rule and preserve intent (heterogeneous providers).

-type ProviderArray = ProviderItem<any>[];
+type ProviderArray = ProviderItem<unknown>[];

12-21: Null guard in type guard to prevent runtime TypeError.

"in" on null throws. Add provider !== null.

-  return (
-    typeof provider === "object" &&
+  return (
+    typeof provider === "object" &&
+    provider !== null &&
     "component" in provider &&
     "props" in provider
   );

43-51: Fix lint: avoid any in createProviderWithProps generic.

Swap any for unknown and keep props inference via React.ComponentProps.

-export function createProviderWithProps<T extends ComponentType<any>>(
+export function createProviderWithProps<T extends ComponentType<unknown>>(
   component: T,
   props: Omit<React.ComponentProps<T>, "children">,
 ): {
   component: T;
   props: Omit<React.ComponentProps<T>, "children">;
 } {
   return { component, props };
 }

Please re-run lint after this change.


1-1: Add minimal JSDoc on exported APIs.

Add short descriptions and param docs for ProviderRenderer and createProviderWithProps per repo style.

Also applies to: 28-31, 43-51

apps/torus-bridge/src/context/app-context-provider.tsx (1)

26-26: Return null for SSR guard.

Returning an empty div injects an extra node; prefer null to avoid layout/hydration quirks.

-  if (useIsSsr()) return <div></div>;
+  if (useIsSsr()) return null;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 a77c2af and 4d65e27.

📒 Files selected for processing (3)
  • apps/torus-bridge/src/app/_components/provider-renderer.tsx (1 hunks)
  • apps/torus-bridge/src/context/app-context-provider.tsx (2 hunks)
  • apps/torus-bridge/src/context/evm-wallet-provider.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/context/app-context-provider.tsx
  • apps/torus-bridge/src/app/_components/provider-renderer.tsx
  • apps/torus-bridge/src/context/evm-wallet-provider.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/context/app-context-provider.tsx
  • apps/torus-bridge/src/app/_components/provider-renderer.tsx
  • apps/torus-bridge/src/context/evm-wallet-provider.tsx
🧬 Code graph analysis (1)
apps/torus-bridge/src/context/app-context-provider.tsx (3)
apps/torus-bridge/src/app/_components/provider-renderer.tsx (2)
  • ProviderRenderer (28-41)
  • createProviderWithProps (43-51)
packages/torus-provider/src/torus-provider.tsx (1)
  • TorusProvider (58-264)
packages/query-provider/src/query-provider.tsx (1)
  • ReactQueryProvider (24-36)
🪛 GitHub Check: lint
apps/torus-bridge/src/app/_components/provider-renderer.tsx

[failure] 43-43:
Unexpected any. Specify a different type


[failure] 10-10:
Unexpected any. Specify a different type

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: torus-bridge-pr-activate / clean-stale
  • GitHub Check: torus-portal-pr-activate / clean-stale
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-page-pr-activate / clean-stale
  • GitHub Check: torus-governance-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / docker-build
  • GitHub Check: torus-cache-pr-activate / clean-stale
🔇 Additional comments (6)
apps/torus-bridge/src/context/evm-wallet-provider.tsx (2)

25-25: Import cleanup looks good.

Dropping useEffect/useState and relying on memoization aligns with the new SSR guard at the app level.


82-90: Wagmi createConfig client({ chain }) — compatible with @wagmi/core v2
pnpm-lock.yaml shows @wagmi/core resolved to 2.20.2 (2.16.3 present transitively); @wagmi/core v2 supports createConfig({ client({ chain }) { return createClient(...) } }) so the snippet is compatible. (wagmi.sh)

apps/torus-bridge/src/app/_components/provider-renderer.tsx (1)

32-41: Provider folding logic is clean and readable.

The reduceRight composition and props-aware branch look good.

apps/torus-bridge/src/context/app-context-provider.tsx (3)

9-12: New provider composition import looks good.

Importing createProviderWithProps and ProviderRenderer matches the new composition model.


29-41: Provider order LGTM; minor env safety check.

Ordering places Torus and Query providers before consumers, which is correct. Ensure env() is safe on the client for NEXT_PUBLIC_* vars.

If env() can throw on missing keys, confirm both NEXT_PUBLIC_TORUS_RPC_URL and NEXT_PUBLIC_TORUS_CACHE_URL are defined in all deploy envs.


43-47: Child layout OK.

Header/Container/Toaster/Footer placement looks consistent with prior structure.

…Renderer

- Added eslint-disable comments to suppress warnings for the use of 'any' in the ProviderRenderer component of the torus-bridge application.
- This change improves code clarity while maintaining flexibility in provider management.
…ment

- Updated the TransferDetails component in the torus-bridge application to utilize useRef for tracking previous transfer lengths, improving the logic for displaying transfer details.
- Enhanced the useLayoutEffect hook to prevent unnecessary updates and ensure the modal opens only when new transfers are detected.
- Modified the TransfersDetailsDialog to handle open state changes more effectively, improving user experience during transfer interactions.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx (1)

71-121: Guard async effect to prevent stale updates and setState on unmounted.

Add a cancel flag and cleanup. This avoids older resolutions clobbering newer state and warnings after unmount. (Same as a prior review on this block.)

   useEffect(() => {
+    let cancelled = false;
     const getMessageUrls = async () => {
       setFromUrl("");
       setToUrl("");
       setOriginTxUrl("");

       if (originTxHash) {
-        const [txUrlError, originTxUrl] = trySync(() =>
+        const [txUrlError, originTxUrl] = trySync(() =>
           multiProvider.tryGetExplorerTxUrl(origin, { hash: originTxHash }),
         );

         if (txUrlError !== undefined) {
           logger.error(
             `Error getting transaction URL for hash ${originTxHash}:`,
             txUrlError,
           );
-        } else if (originTxUrl) {
-          setOriginTxUrl(fixDoubleSlash(originTxUrl));
+        } else if (originTxUrl && !cancelled) {
+          setOriginTxUrl(fixDoubleSlash(originTxUrl));
         }
       }

       const [fromUrlPromise, toUrlPromise] = [
         multiProvider.tryGetExplorerAddressUrl(origin, sender),
         multiProvider.tryGetExplorerAddressUrl(destination, recipient),
       ];

       const [fromUrlError, fromUrl] = await tryAsync(fromUrlPromise);
       if (fromUrlError !== undefined) {
         logger.error(
           `Error getting explorer URL for sender ${sender}:`,
           fromUrlError,
         );
-      } else if (fromUrl) {
-        setFromUrl(fixDoubleSlash(fromUrl));
+      } else if (fromUrl && !cancelled) {
+        setFromUrl(fixDoubleSlash(fromUrl));
       }

       const [toUrlError, toUrl] = await tryAsync(toUrlPromise);
       if (toUrlError !== undefined) {
         logger.error(
           `Error getting explorer URL for recipient ${recipient}:`,
           toUrlError,
         );
-      } else if (toUrl) {
-        setToUrl(fixDoubleSlash(toUrl));
+      } else if (toUrl && !cancelled) {
+        setToUrl(fixDoubleSlash(toUrl));
       }
     };

     getMessageUrls().catch((err) => {
       logger.error("Error getting message URLs for details modal", err);
     });
+    return () => {
+      cancelled = true;
+    };
   }, [sender, recipient, originTxHash, multiProvider, origin, destination]);
🧹 Nitpick comments (2)
apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx (1)

78-89: Avoid shadowing state variable name.

Local originTxUrl shadows the state setter target, which is easy to misread. Rename the local to txUrl.

-        const [txUrlError, originTxUrl] = trySync(() =>
+        const [txUrlError, txUrl] = trySync(() =>
           multiProvider.tryGetExplorerTxUrl(origin, { hash: originTxHash }),
         );
@@
-        } else if (originTxUrl) {
-          setOriginTxUrl(fixDoubleSlash(originTxUrl));
+        } else if (txUrl) {
+          setOriginTxUrl(fixDoubleSlash(txUrl));
         }
apps/torus-bridge/src/app/_components/transfer-details/index.tsx (1)

32-35: Guard microtask setState against unmount.

Queueing a microtask can still fire after unmount. Add an isMounted ref and short‑circuit inside the microtask.

-      queueMicrotask(() => {
-        setSelectedTransfer(latestTransfer);
-        setIsModalOpen(true);
-      });
+      queueMicrotask(() => {
+        if (!isMounted.current) return;
+        setSelectedTransfer(latestTransfer);
+        setIsModalOpen(true);
+      });

Add lifecycle guard (outside the effect):

-import { useLayoutEffect, useRef, useState } from "react";
+import { useEffect, useLayoutEffect, useRef, useState } from "react";
@@
-  const prevTransfersLength = useRef(0);
+  const prevTransfersLength = useRef(0);
+  const isMounted = useRef(true);
+  useEffect(() => {
+    return () => {
+      isMounted.current = false;
+    };
+  }, []);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 b782a19 and c0cff6d.

📒 Files selected for processing (2)
  • apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx (2 hunks)
  • apps/torus-bridge/src/app/_components/transfer-details/index.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-bridge/src/app/_components/transfer-details/index.tsx
  • apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-bridge/src/app/_components/transfer-details/index.tsx
  • apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx
🧬 Code graph analysis (2)
apps/torus-bridge/src/app/_components/transfer-details/index.tsx (1)
apps/torus-bridge/src/utils/store.ts (1)
  • useStore (58-175)
apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx (2)
packages/torus-utils/src/try-catch.ts (2)
  • trySync (40-42)
  • tryAsync (97-101)
apps/torus-bridge/src/utils/logger.ts (1)
  • logger (1-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: torus-bridge-pr-activate / docker-build
  • GitHub Check: torus-cache-pr-activate / docker-build
  • GitHub Check: torus-wallet-pr-activate / docker-build
  • GitHub Check: torus-wallet-pr-activate / clean-stale
  • GitHub Check: torus-allocator-pr-activate / docker-build
  • GitHub Check: torus-portal-pr-activate / docker-build
  • GitHub Check: torus-governance-pr-activate / docker-build
  • GitHub Check: torus-page-pr-activate / docker-build
🔇 Additional comments (2)
apps/torus-bridge/src/app/_components/transfer-details/_components/transfer-details-dialog.tsx (1)

150-150: LGTM on controlled dialog close behavior.

Using onOpenChange={(open) => !open && onClose()} correctly delegates close events to the parent.

apps/torus-bridge/src/app/_components/transfer-details/index.tsx (1)

21-25: Initialization flow reads clean.

Skipping the first render while initializing the baseline is correct.

- Added a new layout component for the DAO dashboard in the torus-governance application, enhancing the structure for displaying dashboard content.
- Introduced SEO metadata generation for the DAO dashboard, improving search engine visibility and user engagement.
- Updated the next-env.d.ts file to include route type definitions for better TypeScript support.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/torus-governance/src/app/(pages)/dao-dashboard/page.tsx (1)

10-12: Optionally lazy‑load tab panels to reduce initial JS.

Defer loading of non‑active panels via next/dynamic. Also drop static key props on TabsContent (not part of a list).

+import dynamic from "next/dynamic";
-import AgentHealthTab from "./_components/agent-health-tab/agent-health-tab";
-import DaoApplicationsTab from "./_components/dao-applications-tab";
-import DashboardTab from "./_components/dashboard-tab/dashboard";
+const DashboardTab = dynamic(() => import("./_components/dashboard-tab/dashboard"));
+const AgentHealthTab = dynamic(() => import("./_components/agent-health-tab/agent-health-tab"));
+const DaoApplicationsTab = dynamic(() => import("./_components/dao-applications-tab"));

-        <TabsContent value="dashboard" key="dashboard-tab">
+        <TabsContent value="dashboard">
           <DashboardTab />
         </TabsContent>
-        <TabsContent value="agent-health" key="agent-health-tab">
+        <TabsContent value="agent-health">
           <AgentHealthTab />
         </TabsContent>
-        <TabsContent value="dao-applications" key="dao-applications-tab">
+        <TabsContent value="dao-applications">
           <DaoApplicationsTab />
         </TabsContent>

Also applies to: 36-44

apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx (1)

1-2: Add explicit return type for generateMetadata.

Minor type clarity improvement aligned with our TS docs.

+import type { Metadata } from "next";
 import { createSeoMetadata } from "@torus-ts/ui/components/seo";
 import { env } from "~/env";

-export function generateMetadata() {
+export function generateMetadata(): Metadata {

Also applies to: 4-5

apps/torus-governance/next-env.d.ts (1)

3-3: Prefer glob include to future‑proof against file name changes.

Hardcoding routes.d.ts can break when Next changes generated file names. Use the commonly recommended glob instead.

Apply this diff:

-/// <reference path="./.next/types/routes.d.ts" />
+/// <reference path="./.next/types/**/*.ts" />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 c0cff6d and 2a31d37.

📒 Files selected for processing (3)
  • apps/torus-governance/next-env.d.ts (1 hunks)
  • apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx (1 hunks)
  • apps/torus-governance/src/app/(pages)/dao-dashboard/page.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx
  • apps/torus-governance/next-env.d.ts
  • apps/torus-governance/src/app/(pages)/dao-dashboard/page.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx
  • apps/torus-governance/src/app/(pages)/dao-dashboard/page.tsx
🧠 Learnings (2)
📚 Learning: 2025-07-27T01:55:40.478Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/torus-sdk-ts/src/chain/CLAUDE.md:0-0
Timestamp: 2025-07-27T01:55:40.478Z
Learning: Applies to packages/torus-sdk-ts/src/chain/**/*.{ts,tsx} : Document all exports with JSDoc comments.

Applied to files:

  • apps/torus-governance/next-env.d.ts
📚 Learning: 2025-07-19T17:44:15.833Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/dsl/CLAUDE.md:0-0
Timestamp: 2025-07-19T17:44:15.833Z
Learning: Applies to packages/dsl/src/types.ts : All core type definitions for the DSL must be defined in a single source of truth file: /packages/dsl/src/types.ts

Applied to files:

  • apps/torus-governance/next-env.d.ts
🧬 Code graph analysis (1)
apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx (1)
packages/ui/src/components/seo.tsx (1)
  • createSeoMetadata (67-156)
🔇 Additional comments (4)
apps/torus-governance/src/app/(pages)/dao-dashboard/page.tsx (1)

1-1: Client page OK — metadata owned by layout. Verified: apps/torus-governance/src/app/(pages)/dao-dashboard/page.tsx has no generateMetadata export; apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx exports generateMetadata (line 4).

apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx (1)

4-20: Confirm BASE_URL is set and absolute

generateMetadata (apps/torus-governance/src/app/(pages)/dao-dashboard/layout.tsx) uses env("BASE_URL") but a repo-wide search returned no matches for BASE_URL — add BASE_URL to all envs (absolute URL) or supply a safe absolute fallback so OG/canonical links are correct.

apps/torus-governance/next-env.d.ts (2)

3-3: LGTM: Adds typed routes to TS context.

This aligns with Next’s typed‑routes pattern and has no runtime impact.


3-3: Ensure CI generates Next .next/types before running tsc — verify just typecheck

apps/torus-governance/next-env.d.ts references "./.next/types/routes.d.ts" and apps/torus-governance/package.json defines "typecheck": "tsc --noEmit". CI runs just typecheck (.github/workflows/ci.yml → Typecheck step) but no Justfile was found in the repo — confirm what just typecheck actually executes. If it does not generate Next's .next/types first, either:

  • run next build (or Next typegen) for affected apps in CI before running tsc, or
  • remove/guard the triple-slash reference (or commit a minimal stub .next/types/routes.d.ts) so tsc --noEmit won't fail.

Files to check: apps/torus-governance/next-env.d.ts, apps/torus-governance/package.json, .github/workflows/ci.yml.

- Refactored multiple components across the torus-bridge and torus-governance applications to utilize useMemo for memoizing values, enhancing performance by preventing unnecessary recalculations.
- Updated the token list modal, transfer token form, agent applications hook, and various other components to improve rendering efficiency and state management.
- Adjusted ESLint configuration to disable the 'preserve-manual-memoization' rule, allowing for clearer memoization practices in the codebase.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx (1)

196-201: Accordion item “value”/key collisions possible across different permissions of the same source/target

Using ${sourceId}-${targetId} for both key and value will collide if multiple permissions exist between the same pair (e.g., capability and emission). Use the permissionId to guarantee uniqueness.

-                    <AccordionItem
-                      key={`${sourceId}-${targetId}`}
-                      value={`${sourceId}-${targetId}`}
+                    <AccordionItem
+                      key={`cap-${details?.permissions.permissionId ?? `${sourceId}-${targetId}`}`}
+                      value={`cap-${details?.permissions.permissionId ?? `${sourceId}-${targetId}`}`}
                       className="bg-accent mb-2 border"
                     >
-                    <AccordionItem
-                      key={`${sourceId}-${targetId}`}
-                      value={`${sourceId}-${targetId}`}
+                    <AccordionItem
+                      key={`emi-${details?.permissions.permissionId ?? `${sourceId}-${targetId}`}`}
+                      value={`emi-${details?.permissions.permissionId ?? `${sourceId}-${targetId}`}`}
                       className="bg-accent mb-2 border"
                     >

Also applies to: 338-343

🧹 Nitpick comments (6)
apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx (5)

462-488: Replace IIFE element with a memoized element and lowercase name to avoid component confusion

The IIFE pre-renders every render and PermissionsContent looks like a component but is an element. Memoize the element and rename for clarity.

-  const PermissionsContent = (() => (
+  const permissionsContentEl = useMemo(() => (
     <ScrollArea className="h-[calc(100vh-26rem)]">
       {processedPermissions.length > 0 ? (
         <Accordion type="single" collapsible className="w-full">
           {/* Delegated Permissions - only show if there are delegated permissions */}
           {groupedPermissions.delegated.length > 0 &&
             renderPermissionGroup(
               groupedPermissions.delegated,
               "Delegated",
               <ArrowUpRight className="text-muted-foreground h-4 w-4" />,
             )}
 
           {/* Received Permissions - only show if there are received permissions */}
           {groupedPermissions.received.length > 0 &&
             renderPermissionGroup(
               groupedPermissions.received,
               "Received",
               <ArrowDownLeft className="text-muted-foreground h-4 w-4" />,
             )}
         </Accordion>
       ) : (
         <div className="mt-8 text-center text-gray-500">
           No permissions found for this agent
         </div>
       )}
     </ScrollArea>
-  ))();
+  ), [groupedPermissions, processedPermissions.length]);
-          {PermissionsContent}
+          {permissionsContentEl}

Also applies to: 501-501


82-142: Memoize processedPermissions; otherwise groupedPermissions useMemo always recomputes

processedPermissions is recreated each render via map, so groupedPermissions’s dependency changes every time. Memoize the mapping to unlock the intended optimization.

-  const processedPermissions = nodePermissions.map((permission) => {
+  const processedPermissions = useMemo(() => nodePermissions.map((permission) => {
     // Extract permission ID from node ID if it's a permission node
     const getPermissionId = (nodeId: string | number | object | undefined) => {
       if (!nodeId) return null;
       const id =
         typeof nodeId === "object" && "id" in nodeId ? nodeId.id : nodeId;
       if (typeof id !== "string") return null;
       return id.startsWith("permission-")
         ? id.replace("permission-", "")
         : null;
     };
 
     const sourcePermissionId = getPermissionId(permission.source);
     const targetPermissionId = getPermissionId(permission.target);
 
     // Try to find by permission ID first (most reliable)
     const permissionId = sourcePermissionId ?? targetPermissionId;
 
     const details = allPermissions?.find((p) => {
       if (permissionId) {
         return p.permissions.permissionId === permissionId;
       }
       // Fallback to account ID matching (for non-permission nodes)
       const sourceId =
         typeof permission.source === "object" && "id" in permission.source
           ? permission.source.id
           : permission.source;
       const targetId =
         typeof permission.target === "object" && "id" in permission.target
           ? permission.target.id
           : permission.target;
       return (
         p.permissions.grantorAccountId === sourceId &&
         p.permissions.granteeAccountId === targetId
       );
     });
     const isOutgoing = permission.type === "outgoing";
     const connectedNode = graphData?.nodes.find(
       (n) => n.id === (isOutgoing ? permission.target : permission.source),
     );
     const connectedAddress =
       connectedNode?.fullAddress ?? connectedNode?.id ?? "";
 
     const sourceId =
       typeof permission.source === "object"
         ? permission.source.id
         : permission.source;
     const targetId =
       typeof permission.target === "object"
         ? permission.target.id
         : permission.target;
 
     return {
       permission,
       details,
       isOutgoing,
       connectedAddress,
       sourceId,
       targetId,
     };
-  });
+  }), [nodePermissions, allPermissions, graphData?.nodes]);

309-317: Prefer stable, semantic keys for paths

Use path as the key instead of the array index to avoid unnecessary re-renders when order changes.

-                                      {paths.map((path, index) => (
-                                        <div key={index}>
+                                      {paths.map((path) => (
+                                        <div key={path}>
                                           <ShortenedCapabilityPath
                                             path={path}
                                             showTooltip={true}
                                           />
                                         </div>
                                       ))}

66-68: Remove eslint-disable by typing the map param

Narrow target to a safe shape to drop no-unsafe-member-access.

-        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-        .map((target) => target?.targetAccountId as string | undefined)
+        .map(
+          (target: { targetAccountId?: string } | null | undefined) =>
+            target?.targetAccountId
+        )

31-36: Add minimal JSDoc per repo conventions

Document the component and props to align with docs/DOCUMENTATION_STYLE.md.

+/**
+ * Node details pane for permissions/signals in the permission graph sheet.
+ * Renders delegated/received permission groups and signals for the selected node.
+ */
 export function NodeDetailsCard({

Also applies to: 38-43

tooling/eslint/react.js (1)

15-15: Set react-hooks/preserve-manual-memoization to "warn" and enable the React Compiler lint rule.

tooling/eslint/package.json shows the plugin is on the RC channel (so the rule exists); use "warn" to preserve signal without failing CI and enable "react-hooks/react-compiler" when adopting the React Compiler. Ensure eslint-plugin-react-hooks >= 6.0.0-rc.1.

File: tooling/eslint/react.js (around line 15)

-      "react-hooks/preserve-manual-memoization": "off",
+      "react-hooks/preserve-manual-memoization": "warn",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 2a31d37 and 0b45fb7.

📒 Files selected for processing (17)
  • apps/torus-bridge/src/app/_components/tokens/token-list-modal.tsx (0 hunks)
  • apps/torus-bridge/src/app/_components/transfer-token/_components/transfer-token-form.tsx (0 hunks)
  • apps/torus-governance/hooks/use-agent-applications.tsx (1 hunks)
  • apps/torus-governance/hooks/use-agent-health.ts (0 hunks)
  • apps/torus-governance/src/app/_components/comments/view-comment.tsx (2 hunks)
  • apps/torus-governance/src/app/_components/filter-content.tsx (0 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/force-graph/use-graph-data.ts (0 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/force-graph/use-graph-interactions.ts (1 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx (3 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-permission.tsx (0 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-signals-accordion.tsx (0 hunks)
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/permission-graph-command.tsx (0 hunks)
  • apps/torus-portal/src/app/(pages)/capabilities/register-capability/_components/create-capability-prefix-field.tsx (0 hunks)
  • apps/torus-portal/src/app/_components/address-field.tsx (0 hunks)
  • apps/torus-portal/src/hooks/use-weekly-usd.ts (1 hunks)
  • apps/torus-wallet/next-env.d.ts (1 hunks)
  • tooling/eslint/react.js (1 hunks)
💤 Files with no reviewable changes (10)
  • apps/torus-portal/src/app/_components/address-field.tsx
  • apps/torus-portal/src/app/(pages)/capabilities/register-capability/_components/create-capability-prefix-field.tsx
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/permission-graph-command.tsx
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-signals-accordion.tsx
  • apps/torus-governance/src/app/_components/filter-content.tsx
  • apps/torus-bridge/src/app/_components/tokens/token-list-modal.tsx
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-permission.tsx
  • apps/torus-governance/hooks/use-agent-health.ts
  • apps/torus-bridge/src/app/_components/transfer-token/_components/transfer-token-form.tsx
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/force-graph/use-graph-data.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/torus-governance/src/app/_components/comments/view-comment.tsx
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/force-graph/use-graph-interactions.ts
  • apps/torus-governance/hooks/use-agent-applications.tsx
  • apps/torus-portal/src/hooks/use-weekly-usd.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Follow docs/DOCUMENTATION_STYLE.md JSDoc conventions for all code documentation
Avoid top-level stateful singletons; use lazy initialization instead
For rustie enums, represent values as {VariantName: contents} and use rustie utilities (match, if_let) instead of switch/casts
Handle Result<T,E> from @torus-network/torus-utils/result via the [error, data] tuple pattern; do not use isOk/.success or try/catch for flow

Files:

  • apps/torus-wallet/next-env.d.ts
  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx
**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Access Drizzle entities via camelCase properties (e.g., permission.grantorKey), not snake_case

Files:

  • apps/torus-portal/src/app/(pages)/(permission-graph)/_components/graph-sheet/graph-sheet-details/graph-sheet-details-card.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-19T17:44:15.833Z
Learnt from: CR
PR: renlabs-dev/torus-ts#0
File: packages/dsl/CLAUDE.md:0-0
Timestamp: 2025-07-19T17:44:15.833Z
Learning: Applies to packages/dsl/src/types.ts : All core type definitions for the DSL must be defined in a single source of truth file: /packages/dsl/src/types.ts

Applied to files:

  • apps/torus-wallet/next-env.d.ts
🔇 Additional comments (1)
apps/torus-wallet/next-env.d.ts (1)

3-3: LGTM — confirm typed routes are generated in CI

apps//next-env.d.ts (apps/torus-wallet, torus-portal, torus-governance, torus-bridge, torus-page) reference .next/types/routes.d.ts; no typedRoutes found in any apps' next.config. and package.json shows "next": "catalog:" (Next managed centrally). Ensure CI runs a Next step that generates ./.next/types/routes.d.ts (or enable typedRoutes in config) before running tsc --noEmit on clean checkouts.

…portal applications

- Adjusted import statements for better readability in the use-agent-applications hook and view-comment component.
- Enhanced the useWeeklyUsdCalculation hook by formatting the dependency array for improved clarity.
- These changes contribute to a cleaner codebase and maintainable structure across the applications.
rules: {
...reactPlugin.configs["jsx-runtime"].rules,
...hooksPlugin.configs.recommended.rules,
"react-hooks/preserve-manual-memoization": "off",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I left it off for now because it doesn't seem to be that simple to solve this problem, by leaving this rule activated and removing both useCallback and useMemo (bugs occur due to lack of memorization, react-compiler support doesn't seem to be working 100% correctly), I did some research and tried to solve it via LLM but was unsuccessful so I think we can revisit this in the future when it is no longer experimental.

…mpatibility issues

- Temporarily turned off the 'react-hooks/preserve-manual-memoization' rule in the ESLint configuration for React.
- This change addresses compatibility concerns with the experimental react-compiler in the Next.js ecosystem.
- Future updates will revisit this deactivation once the compatibility issues are resolved.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants