Skip to content

Multi Profile support#2010

Merged
prxt6529 merged 35 commits intomainfrom
multi-profile-support
Mar 3, 2026
Merged

Multi Profile support#2010
prxt6529 merged 35 commits intomainfrom
multi-profile-support

Conversation

@prxt6529
Copy link
Copy Markdown
Collaborator

@prxt6529 prxt6529 commented Feb 27, 2026

Summary by CodeRabbit

  • New Features

    • Multi-wallet support: connect/manage up to 3 profiles, add accounts, quick-switch between profiles, and "Sign Out All Profiles".
    • Connected-profile UI updates: avatar overlays/indicators across header/sidebar, connected-accounts list, add/switch flows, and updated Accept Connection flow with stateful button and spinner.
    • Connection status indicator with clearer labels and styling for disconnected/authorized/connected states.
  • Bug Fixes

    • Improved error handling and user-facing toasts for storage, persistence, signing, and max-profile limits.

Signed-off-by: prxt6529 <prxt@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds multi‑wallet support and UI: persistent connectedAccounts with active-account lifecycle, account switching/adding, storage migration and events; SeizeConnectContext API expanded; Auth gains immediate-validation and abort guards; header/sidebar show connected-account UI and switching; tests, constants, styles, and lint/tooling adjusted.

Changes

Cohort / File(s) Summary
Auth utils & storage
services/auth/auth.utils.ts, __tests__/services/auth.utils.test.ts
Implements multi-account storage, active-account read/write/migration, WALLET_ACCOUNTS_UPDATED_EVENT, helpers (getConnectedWalletAccounts, canStoreAnotherWalletAccount, getRefreshToken, setActiveWalletAccount), and changes setAuthJwt to return boolean; tests expanded for multi-account behaviors.
Connect context & core auth UI
components/auth/SeizeConnectContext.tsx, components/auth/Auth.tsx, components/auth/Auth.module.scss, components/auth/connection-state-indicator.ts
Expands SeizeConnectContext type/surface (connectedAccounts, seizeSwitchConnectedAccount, seizeAddConnectedAccount, canAddConnectedAccount, seizeDisconnectAndLogoutAll and signature change), adds storedConnectedAccounts and add-flow handling; Auth adds immediate validation, abort/operationId guards, modal restructure and new styles; adds connection-state utility.
Header / Connected accounts UI
components/header/AppUserConnect.tsx, components/header/AppSidebarConnectedAccounts.tsx, components/header/user/connected/HeaderUserConnectedAccounts.tsx, components/header/user/proxy/HeaderUserProxyDropdown.tsx
Adds components and UI for listing, switching and adding connected accounts; introduces "Sign Out All Profiles"; integrates connectedAccounts into connect/disconnect flows and proxy dropdown; adds runMenuAction/error toasts and pending states.
Avatar / Sidebar / Header integration
components/header/AppHeader.tsx, components/header/AppSidebarHeader.tsx, components/header/AppSidebarUserInfo.tsx, components/layout/sidebar/WebSidebarUser.tsx
Reworks avatar rendering to use IPFS resolution/fallback, connection indicators/overlays, wallet label display, and double-activate behavior to cycle accounts; WebSidebarUser prop updated to accept profile.
Notifications & accept flow
components/notifications/NotificationsContext.tsx, app/accept-connection-sharing/page.client.tsx
Notification actions now resolve notification profile→address and may switch active address; accept-connection flow checks canStoreAnotherWalletAccount, persists JWT via guarded setAuthJwt, and shows toasts on limits/failures.
New constants & styles
constants/constants.ts, components/auth/Auth.module.scss
Adds MAX_CONNECTED_PROFILES and DEFAULT_CONNECTED_PROFILE_FALLBACK_PFP; refactors Auth modal CSS classes and responsive styling.
Tests & mocks
__tests__/components/auth/*.test.tsx, __tests__/components/header/*.test.tsx, __tests__/components/auth/SeizeConnectContext.test.tsx, __tests__/components/auth/Auth.test.tsx
Widespread test updates: expanded mock surface, React-Bootstrap modal mocks, async waitFor usage, updated expectations/data-testid strings, and multi-account flow coverage.
Tooling & housekeeping
package.json, eslint.config.mjs, eslint.config.tight.mjs, assorted hooks/contexts/layout files`
Adds diff-aware lint/quality scripts; converts ESLint config to factory API; minor import/order/format adjustments across small files.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Header as AppHeader
    participant Context as SeizeConnectContext
    participant Storage as AuthUtils
    participant Auth as AuthComponent

    User->>Header: double-activate avatar
    Header->>Context: request next connected address / switch
    Context->>Storage: setActiveAddressInStorage(address)
    Storage-->>Context: emit WALLET_ACCOUNTS_UPDATED_EVENT
    Context->>Auth: notify address change
    Auth->>Auth: run immediate validation (abort previous ops)
    Auth-->>Header: updated UI state
    Header-->>User: show switched profile
Loading
sequenceDiagram
    participant User
    participant Page as AcceptConnectionPage
    participant Storage as AuthUtils
    participant Context as SeizeConnectContext
    participant Auth as AuthComponent

    User->>Page: Click "Accept Connection"
    Page->>Storage: canStoreAnotherWalletAccount(address)?
    alt can store
        Page->>Context: seizeConnect/start flow
        Context->>Auth: initiate sign-in flow
        Auth->>Storage: setAuthJwt(address,jwt,refreshToken,role)
        Storage-->>Context: persist accounts + emit WALLET_ACCOUNTS_UPDATED_EVENT
        Context-->>Page: success → navigate
    else cannot store
        Page-->>User: show "Maximum connected profiles reached" toast
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I nibble at keys and tokens bright,
Wallets multiply in soft moonlight,
Hop, switch, and sign — profiles take flight,
Connected friends all snug and tight,
A rabbit cheers: connectors unite!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Multi Profile support' directly describes the main feature being added throughout the changeset: multi-profile/multi-wallet account management across authentication, context, and UI components.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch multi-profile-support

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.

Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
@prxt6529
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (3)
app/accept-connection-sharing/page.client.tsx (1)

51-73: ⚠️ Potential issue | 🟠 Major

Handle invalid redeem responses and setAuthJwt failure before redirecting.

Right now, an invalid-but-non-throwing redeem response can leave the button stuck in processing, and a failed setAuthJwt(...) still proceeds to seizeAcceptConnection + navigation.

💡 Proposed fix
   const acceptConnection = async () => {
     try {
@@
       if (
         redeemResponse.address &&
         redeemResponse.token &&
         areEqualAddresses(redeemResponse.address, address)
       ) {
@@
-        setAuthJwt(
+        const persisted = setAuthJwt(
           redeemResponse.address,
           redeemResponse.token,
           token,
           role ?? undefined
         );
+        if (!persisted) {
+          setToast({
+            message: "Maximum connected profiles reached",
+            type: "error",
+          });
+          return;
+        }
         seizeAcceptConnection(redeemResponse.address);
         router.push("/");
+        return;
       }
+      setToast({ message: "Invalid connection response", type: "error" });
     } catch (error) {
       console.error(error);
       setToast({ message: "Failed to accept connection", type: "error" });
-      setAcceptingConnection(false);
+    } finally {
+      setAcceptingConnection(false);
     }
   };

Also applies to: 120-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/accept-connection-sharing/page.client.tsx` around lines 51 - 73, Ensure
redeemResponse is fully valid and handle failures from setAuthJwt before calling
seizeAcceptConnection or router.push: validate redeemResponse.address/token and
areEqualAddresses(redeemResponse.address, address), and if invalid call
setToast(...) and setAcceptingConnection(false) then return; call
setAuthJwt(...) inside a try/catch or check its boolean result and if it fails
set a toast, setAcceptingConnection(false) and return instead of proceeding;
only call seizeAcceptConnection(redeemResponse.address) and router.push("/")
after setAuthJwt succeeded. Apply the same guard and failure handling to the
same logic at the other occurrence (lines 120-142).
components/header/user/proxy/HeaderUserProxyDropdown.tsx (1)

195-240: ⚠️ Potential issue | 🟠 Major

Avoid fire-and-forget async disconnect/logout calls.

Both handlers call async context methods without awaiting/catching errors, so failures can become unhandled and invisible to users.

Suggested fix
                     {isConnected ? (
                       <button
                         onClick={() => {
-                          seizeDisconnect();
-                          onClose();
+                          void (async () => {
+                            try {
+                              await seizeDisconnect();
+                            } catch (error) {
+                              console.error("Failed to disconnect wallet", error);
+                              setToast({
+                                message:
+                                  "Failed to disconnect wallet. Please try again.",
+                                type: "error",
+                              });
+                            } finally {
+                              onClose();
+                            }
+                          })();
                         }}
@@
                   <div className="tw-h-full tw-px-2 tw-pt-2">
                     <button
-                      onClick={() => seizeDisconnectAndLogout()}
+                      onClick={() => {
+                        void (async () => {
+                          try {
+                            await seizeDisconnectAndLogout();
+                            onClose();
+                          } catch (error) {
+                            console.error("Failed to disconnect and logout", error);
+                            setToast({
+                              message:
+                                "Failed to logout. Please try again.",
+                              type: "error",
+                            });
+                          }
+                        })();
+                      }}
                       type="button"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/header/user/proxy/HeaderUserProxyDropdown.tsx` around lines 195 -
240, The handlers call async functions (seizeDisconnect, seizeConnect,
seizeDisconnectAndLogout) without awaiting or handling errors; convert the
onClick handlers to async functions that await the corresponding call, wrap each
await in try/catch to handle/log errors (and surface UI feedback if available),
and ensure onClose is only invoked after a successful await (or still invoked in
finally if intended). Also consider disabling the button during the pending
state (use a local isPending state per button) to prevent double-clicks; update
references to seizeDisconnect, seizeConnect, and seizeDisconnectAndLogout
accordingly.
components/auth/SeizeConnectContext.tsx (1)

742-852: ⚠️ Potential issue | 🟠 Major

Don’t gate disconnect on isActiveWalletConnected in logout paths.

When the live wallet is connected but differs from the active auth profile, this guard is false and logout/sign-out-all can skip disconnect(), leaving a wallet session active.

Suggested fix
   const seizeDisconnect = useCallback(async (): Promise<void> => {
-    if (!isActiveWalletConnected) {
+    if (!account.isConnected) {
       return;
     }
@@
-  }, [disconnect, isActiveWalletConnected]);
+  }, [account.isConnected, disconnect]);

   const seizeDisconnectAndLogout = useCallback(async (): Promise<void> => {
-    if (isActiveWalletConnected) {
+    if (account.isConnected) {
@@
   }, [
+    account.isConnected,
     disconnect,
-    isActiveWalletConnected,
     refreshStoredConnectedAccounts,
     setConnected,
     setDisconnected,
   ]);

   const seizeDisconnectAndLogoutAll = useCallback(async (): Promise<void> => {
-    if (isActiveWalletConnected) {
+    if (account.isConnected) {
@@
   }, [
+    account.isConnected,
     disconnect,
-    isActiveWalletConnected,
     refreshStoredConnectedAccounts,
     setDisconnected,
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/SeizeConnectContext.tsx` around lines 742 - 852, The logout
handlers seizeDisconnectAndLogout and seizeDisconnectAndLogoutAll currently skip
calling disconnect() when isActiveWalletConnected is false, which can leave a
live wallet session active; remove the guard and always attempt await
disconnect() inside the existing try/catch so disconnect() runs on logout
regardless of isActiveWalletConnected, keeping the existing error wrapping
(createWalletError/WalletDisconnectionError) and AuthenticationError behavior
and leaving the rest of the auth-cleanup logic (removeAuthJwt,
refreshStoredConnectedAccounts, setConnected/setDisconnected) unchanged.
🧹 Nitpick comments (5)
components/header/AppSidebarHeader.tsx (1)

19-26: Consider adding a hover state to the close button.

The button has active:tw-text-white but no hover: state, which may reduce visual feedback for users before they click. Adding a hover state would improve interaction discoverability.

💡 Suggested enhancement
           className="tw-border-none tw-bg-transparent tw-p-0 tw-text-iron-400 tw-transition-colors tw-duration-200 focus-visible:tw-ring-2 focus-visible:tw-ring-primary-400 focus-visible:tw-ring-offset-2 focus-visible:tw-ring-offset-iron-900 active:tw-text-white"
+          className="tw-border-none tw-bg-transparent tw-p-0 tw-text-iron-400 tw-transition-colors tw-duration-200 hover:tw-text-iron-200 focus-visible:tw-ring-2 focus-visible:tw-ring-primary-400 focus-visible:tw-ring-offset-2 focus-visible:tw-ring-offset-iron-900 active:tw-text-white"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/header/AppSidebarHeader.tsx` around lines 19 - 26, The close
button in AppSidebarHeader (the button element with onClose and the XMarkIcon)
lacks a hover state; update its className to include a hover utility (for
example add hover:tw-text-white or another appropriate hover:tw-text-... and/or
a subtle hover:bg utility) so the XMarkIcon visually changes on hover and
provides better interaction feedback.
__tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx (1)

44-48: Include setToast in the mocked AuthContext value for contract completeness.

The component reads setToast; omitting it makes the mock fragile for any error-path test expansion.

Suggested fix
   const authValue = {
     activeProfileProxy: null,
     setActiveProfileProxy: jest.fn(),
     receivedProfileProxies: [{ id: "proxy-1" }],
+    setToast: jest.fn(),
   } as any;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx`
around lines 44 - 48, The mocked AuthContext value `authValue` used in
`HeaderUserProxyDropdown` tests is missing `setToast`, making the mock
incomplete and brittle; add a `setToast: jest.fn()` property to the `authValue`
object (alongside `activeProfileProxy`, `setActiveProfileProxy`, and
`receivedProfileProxies`) so the component can call `setToast` safely in tests,
keeping the `as any` typing or appropriate mock type intact.
components/header/AppUserConnect.tsx (2)

98-108: Consider making aria-label dynamic to match the visible label.

The visible button label changes between "Disconnect & Logout" and "Logout" based on isConnected, but the aria-label remains static. Screen reader users might be confused by the mismatch.

♿ Proposed fix for accessibility
       <button
         onClick={() => {
           seizeDisconnectAndLogout();
           onNavigate();
         }}
         className="tw-flex tw-w-full tw-items-center tw-space-x-4 tw-rounded-lg tw-border-none tw-bg-transparent tw-px-4 tw-py-3.5 tw-text-base tw-font-semibold tw-text-iron-300 tw-transition-colors tw-duration-200 active:tw-bg-iron-700 active:tw-text-iron-200"
-        aria-label="Disconnect & Logout"
+        aria-label={isConnected ? "Disconnect & Logout" : "Logout"}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/header/AppUserConnect.tsx` around lines 98 - 108, The aria-label
should reflect the visible text that depends on isConnected; update the button's
aria-label to use the same conditional expression as the span (e.g., isConnected
? "Disconnect & Logout" : "Logout") so screen readers receive the matching
label, keeping the existing onClick behavior (seizeDisconnectAndLogout and
onNavigate) and preserving the button's other attributes and the
ArrowRightEndOnRectangleIcon.

48-59: Async IIFE pattern is appropriate here but consider extracting for reuse.

The async IIFE pattern for handling disconnection is correct, but the same pattern is repeated at lines 111-120 for seizeDisconnectAndLogoutAll. Consider extracting this into a helper function for consistency.

♻️ Optional refactor to reduce duplication
+  const handleAsyncAction = async (
+    action: () => Promise<void>,
+    errorMessage: string
+  ) => {
+    try {
+      await action();
+    } catch (error) {
+      console.error(errorMessage, error);
+    } finally {
+      onNavigate();
+    }
+  };
+
   const walletConnectionButton = isConnected ? (
     <button
-      onClick={() => {
-        void (async () => {
-          try {
-            await seizeDisconnect();
-          } catch (error) {
-            console.error("Failed to disconnect wallet", error);
-          } finally {
-            onNavigate();
-          }
-        })();
-      }}
+      onClick={() => handleAsyncAction(seizeDisconnect, "Failed to disconnect wallet")}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/header/AppUserConnect.tsx` around lines 48 - 59, The disconnect
async IIFE used inside walletConnectionButton (calling seizeDisconnect then
onNavigate) is duplicated with the pattern used for seizeDisconnectAndLogoutAll;
extract a reusable helper (e.g., runAsyncAndNavigate or handleAsyncDisconnect)
that accepts an async callback like seizeDisconnect or
seizeDisconnectAndLogoutAll and handles try/catch/finally logging and calling
onNavigate, then replace both inline IIFEs in the component with calls to that
helper to remove duplication and centralize error handling.
components/header/AppHeader.tsx (1)

65-77: Consider extracting the constant outside the component.

PROFILE_DOUBLE_ACTIVATE_DELAY_MS is defined inside the component body, which means it's redeclared on every render. While this doesn't cause bugs, it's cleaner to define constants outside the component.

♻️ Suggested refactor
+const PROFILE_DOUBLE_ACTIVATE_DELAY_MS = 280;
+
 export default function AppHeader() {
-  const PROFILE_DOUBLE_ACTIVATE_DELAY_MS = 280;
   const [menuOpen, setMenuOpen] = useState(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/header/AppHeader.tsx` around lines 65 - 77,
PROFILE_DOUBLE_ACTIVATE_DELAY_MS is declared inside the AppHeader component and
should be extracted to a top-level constant to avoid redeclaration on each
render; move PROFILE_DOUBLE_ACTIVATE_DELAY_MS = 280 out of the component body
(above the AppHeader function) keeping it as a plain number constant, then
remove the in-component declaration and leave all usages (e.g., where
profileClickTimeoutRef, menuOpen, setMenuOpen, or other logic reference
PROFILE_DOUBLE_ACTIVATE_DELAY_MS) unchanged so the component reads the top-level
constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@__tests__/components/auth/Auth.test.tsx`:
- Around line 133-141: Remove the redeclaration via require and instead cast the
already imported commonApiPost to a Jest mock: drop the const { commonApiPost }
= require(...) line and assign mockCommonApiPost by casting the existing
imported commonApiPost (same pattern used for mockCommonApiFetch), e.g. const
mockCommonApiPost = commonApiPost as jest.MockedFunction<typeof commonApiPost>;
keep the existing mockUseIdentity assignment as-is; update any tests that
referenced the old require-based mock name to use mockCommonApiPost.

In `@__tests__/components/auth/connection-state-indicator.test.ts`:
- Around line 24-39: The test's expected CSS opacity tokens for
getConnectionProfileIndicator are mismatched with the implementation; update the
assertions in the test for the "authorized only (wallet not connected)" and
"authorized+connected" cases to match the exact class strings returned by
getConnectionProfileIndicator (adjust expectations for result.avatarClassName,
result.overlayClassName, result.buttonClassName and keep
result.state/result.title checks aligned). Locate the assertions referencing
getConnectionProfileIndicator in
__tests__/components/auth/connection-state-indicator.test.ts and change the
opacity fractions (e.g., "/40", "/30", "/60", "/35", "/25", "/55") to the tokens
returned by the function so the test compares the exact implemented class
values.

In `@components/auth/Auth.tsx`:
- Around line 584-606: The current sign-in flow treats requestSignIn success as
enough but doesn't fail when wallet/account persistence (setAuthJwt/store
account) fails, leading to inconsistent auth state; update requestSignIn to
detect and propagate persistence failures (e.g., when setAuthJwt or
canStoreAnotherWalletAccount fails) by returning success: false (or an explicit
persisted flag) and throw or return an error detail, then update this branch in
Auth.tsx to treat that returned failure as an authentication failure (hide
modal, call seizeDisconnect(), invalidateAll only on full success) so that
inability to persist an account prevents marking the user as signed in; look for
the requestSignIn function and setAuthJwt/canStoreAnotherWalletAccount usages to
implement this change.

In `@components/header/user/proxy/HeaderUserProxyDropdown.tsx`:
- Around line 77-84: The click handler onSelectConnectedAccount must catch
errors from seizeSwitchConnectedAccount to avoid breaking the dropdown flow;
update onSelectConnectedAccount to call seizeSwitchConnectedAccount inside a
try/catch (and await it if it returns a Promise) and ensure onClose() is invoked
in a finally block so the dropdown always closes; optionally log or surface the
caught error in the catch block for debugging.

In `@components/layout/sidebar/WebSidebarUser.tsx`:
- Around line 144-183: switchToNextConnectedAccount currently calls
seizeSwitchConnectedAccount synchronously and doesn't catch thrown errors; wrap
the call to seizeSwitchConnectedAccount(nextAccount.address) in a try-catch
inside switchToNextConnectedAccount, return false if an exception is caught (and
optionally log the error), so the component won't crash when
seizeSwitchConnectedAccount throws; keep the existing return true on success and
ensure connectedAccounts and activeIndex logic stays unchanged.

In `@package.json`:
- Around line 32-34: The xargs calls in the npm scripts are missing the
no-run-if-empty flag, causing eslint/prettier to run against the whole repo when
the file list is empty; update each affected script (lint:changed:fix,
lint:changed, lint:changed:tight and also lint:uncommitted:tight, lint:diff,
format:uncommitted) to add the -r option to the xargs invocation (i.e., change
xargs -0 ... to xargs -0 -r ...) so the command is skipped when there are no
filenames, preserving the intended changed-file behavior.

---

Outside diff comments:
In `@app/accept-connection-sharing/page.client.tsx`:
- Around line 51-73: Ensure redeemResponse is fully valid and handle failures
from setAuthJwt before calling seizeAcceptConnection or router.push: validate
redeemResponse.address/token and areEqualAddresses(redeemResponse.address,
address), and if invalid call setToast(...) and setAcceptingConnection(false)
then return; call setAuthJwt(...) inside a try/catch or check its boolean result
and if it fails set a toast, setAcceptingConnection(false) and return instead of
proceeding; only call seizeAcceptConnection(redeemResponse.address) and
router.push("/") after setAuthJwt succeeded. Apply the same guard and failure
handling to the same logic at the other occurrence (lines 120-142).

In `@components/auth/SeizeConnectContext.tsx`:
- Around line 742-852: The logout handlers seizeDisconnectAndLogout and
seizeDisconnectAndLogoutAll currently skip calling disconnect() when
isActiveWalletConnected is false, which can leave a live wallet session active;
remove the guard and always attempt await disconnect() inside the existing
try/catch so disconnect() runs on logout regardless of isActiveWalletConnected,
keeping the existing error wrapping (createWalletError/WalletDisconnectionError)
and AuthenticationError behavior and leaving the rest of the auth-cleanup logic
(removeAuthJwt, refreshStoredConnectedAccounts, setConnected/setDisconnected)
unchanged.

In `@components/header/user/proxy/HeaderUserProxyDropdown.tsx`:
- Around line 195-240: The handlers call async functions (seizeDisconnect,
seizeConnect, seizeDisconnectAndLogout) without awaiting or handling errors;
convert the onClick handlers to async functions that await the corresponding
call, wrap each await in try/catch to handle/log errors (and surface UI feedback
if available), and ensure onClose is only invoked after a successful await (or
still invoked in finally if intended). Also consider disabling the button during
the pending state (use a local isPending state per button) to prevent
double-clicks; update references to seizeDisconnect, seizeConnect, and
seizeDisconnectAndLogout accordingly.

---

Nitpick comments:
In `@__tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx`:
- Around line 44-48: The mocked AuthContext value `authValue` used in
`HeaderUserProxyDropdown` tests is missing `setToast`, making the mock
incomplete and brittle; add a `setToast: jest.fn()` property to the `authValue`
object (alongside `activeProfileProxy`, `setActiveProfileProxy`, and
`receivedProfileProxies`) so the component can call `setToast` safely in tests,
keeping the `as any` typing or appropriate mock type intact.

In `@components/header/AppHeader.tsx`:
- Around line 65-77: PROFILE_DOUBLE_ACTIVATE_DELAY_MS is declared inside the
AppHeader component and should be extracted to a top-level constant to avoid
redeclaration on each render; move PROFILE_DOUBLE_ACTIVATE_DELAY_MS = 280 out of
the component body (above the AppHeader function) keeping it as a plain number
constant, then remove the in-component declaration and leave all usages (e.g.,
where profileClickTimeoutRef, menuOpen, setMenuOpen, or other logic reference
PROFILE_DOUBLE_ACTIVATE_DELAY_MS) unchanged so the component reads the top-level
constant.

In `@components/header/AppSidebarHeader.tsx`:
- Around line 19-26: The close button in AppSidebarHeader (the button element
with onClose and the XMarkIcon) lacks a hover state; update its className to
include a hover utility (for example add hover:tw-text-white or another
appropriate hover:tw-text-... and/or a subtle hover:bg utility) so the XMarkIcon
visually changes on hover and provides better interaction feedback.

In `@components/header/AppUserConnect.tsx`:
- Around line 98-108: The aria-label should reflect the visible text that
depends on isConnected; update the button's aria-label to use the same
conditional expression as the span (e.g., isConnected ? "Disconnect & Logout" :
"Logout") so screen readers receive the matching label, keeping the existing
onClick behavior (seizeDisconnectAndLogout and onNavigate) and preserving the
button's other attributes and the ArrowRightEndOnRectangleIcon.
- Around line 48-59: The disconnect async IIFE used inside
walletConnectionButton (calling seizeDisconnect then onNavigate) is duplicated
with the pattern used for seizeDisconnectAndLogoutAll; extract a reusable helper
(e.g., runAsyncAndNavigate or handleAsyncDisconnect) that accepts an async
callback like seizeDisconnect or seizeDisconnectAndLogoutAll and handles
try/catch/finally logging and calling onNavigate, then replace both inline IIFEs
in the component with calls to that helper to remove duplication and centralize
error handling.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77d536c and 7601576.

⛔ Files ignored due to path filters (1)
  • public/intern-no-bg.png is excluded by !**/*.png
📒 Files selected for processing (35)
  • __tests__/components/auth/Auth.test.tsx
  • __tests__/components/auth/SeizeConnectContext.test.tsx
  • __tests__/components/auth/connection-state-indicator.test.ts
  • __tests__/components/header/AppSidebarUserInfo.test.tsx
  • __tests__/components/header/AppUserConnect.test.tsx
  • __tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx
  • __tests__/services/auth.utils.test.ts
  • app/accept-connection-sharing/page.client.tsx
  • components/auth/Auth.module.scss
  • components/auth/Auth.tsx
  • components/auth/SeizeConnectContext.tsx
  • components/auth/connection-state-indicator.ts
  • components/brain/my-stream/layout/LayoutContext.tsx
  • components/header/AppHeader.tsx
  • components/header/AppSidebarConnectedAccounts.tsx
  • components/header/AppSidebarHeader.tsx
  • components/header/AppSidebarUserInfo.tsx
  • components/header/AppUserConnect.tsx
  • components/header/user/connected/HeaderUserConnectedAccounts.tsx
  • components/header/user/proxy/HeaderUserProxyDropdown.tsx
  • components/layout/SmallScreenLayout.tsx
  • components/layout/WebLayout.tsx
  • components/layout/sidebar/WebSidebarUser.tsx
  • components/notifications/NotificationsContext.tsx
  • components/providers/LayoutWrapper.tsx
  • constants/constants.ts
  • contexts/HeaderContext.tsx
  • eslint.config.mjs
  • eslint.config.tight.mjs
  • hooks/isMobileDevice.ts
  • hooks/isMobileScreen.ts
  • hooks/useCapacitor.ts
  • hooks/useControlledTooltip.ts
  • package.json
  • services/auth/auth.utils.ts

Comment thread __tests__/components/auth/Auth.test.tsx
Comment thread __tests__/components/auth/connection-state-indicator.test.ts Outdated
Comment thread components/auth/Auth.tsx
Comment thread components/header/user/proxy/HeaderUserProxyDropdown.tsx
Comment thread components/layout/sidebar/WebSidebarUser.tsx
Comment thread package.json Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

✅ Actions performed

Review triggered.

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

Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
@prxt6529
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

✅ Actions performed

Review triggered.

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

Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
components/auth/Auth.tsx (1)

283-330: ⚠️ Potential issue | 🟠 Major

Current stale-address checks are ineffective in this effect.

On Line 292 and Line 322, currentAddress !== address compares two values from the same closure, so it won’t detect later address changes. This weakens the stale-response guard in concurrent auth transitions.

Suggested fix
+  const latestAddressRef = useRef<string | null>(null);
+  useEffect(() => {
+    latestAddressRef.current = address ?? null;
+  }, [address]);
@@
-      if (currentAddress !== address) {
+      if (latestAddressRef.current !== currentAddress) {
         return;
       }
@@
-        if (result.wasCancelled || currentAddress !== address) {
+        if (result.wasCancelled || latestAddressRef.current !== currentAddress) {
           return;
         }
@@
-        if (!abortController.signal.aborted && currentAddress === address) {
+        if (
+          !abortController.signal.aborted &&
+          latestAddressRef.current === currentAddress
+        ) {
           abortControllerRef.current = null;
           setAuthLoadingState("idle");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/Auth.tsx` around lines 283 - 330, The stale-address checks
comparing currentAddress to address inside validateImmediately are invalid
because both values are from the same closure; replace them with a runtime check
against a mutable ref or current operation id to detect later changes. Add a ref
like latestAddressRef (updated in a useEffect whenever address changes) or an
operationIdRef (set to operationId at start) and then: (1) before/after awaiting
validateAuthImmediate, compare currentAddress !== latestAddressRef.current (or
operationIdRef.current !== operationId) to detect stale operations, and (2) when
cleaning up, verify abortControllerRef.current === abortController (not
abortController.signal.aborted) before clearing abortControllerRef and setting
setAuthLoadingState("idle"); update usages of currentAddress !== address in
validateImmediately to use the chosen ref comparison and ensure
abortControllerRef is only cleared by the active operation.
♻️ Duplicate comments (1)
package.json (1)

41-41: ⚠️ Potential issue | 🟠 Major

Add -r to format:changed to prevent unintended broad formatting.

Line [41] still uses xargs -0 prettier ... without -r. If the upstream file list is empty, this can run prettier without file args and break the “changed-files only” contract (also affecting quality:changed and audit:changed flows).

Proposed fix
-    "format:changed": "bash -lc '{ git diff --name-only -z --diff-filter=ACMR main...HEAD -- \"*.js\" \"*.jsx\" \"*.ts\" \"*.tsx\" \"*.json\" \"*.css\" \"*.scss\" \":(exclude)generated/**\"; git diff --name-only -z --diff-filter=ACMR HEAD -- \"*.js\" \"*.jsx\" \"*.ts\" \"*.tsx\" \"*.json\" \"*.css\" \"*.scss\" \":(exclude)generated/**\"; git ls-files --others --exclude-standard -z -- \"*.js\" \"*.jsx\" \"*.ts\" \"*.tsx\" \"*.json\" \"*.css\" \"*.scss\" \":(exclude)generated/**\"; } | xargs -0 prettier --write --ignore-unknown'",
+    "format:changed": "bash -lc '{ git diff --name-only -z --diff-filter=ACMR main...HEAD -- \"*.js\" \"*.jsx\" \"*.ts\" \"*.tsx\" \"*.json\" \"*.css\" \"*.scss\" \":(exclude)generated/**\"; git diff --name-only -z --diff-filter=ACMR HEAD -- \"*.js\" \"*.jsx\" \"*.ts\" \"*.tsx\" \"*.json\" \"*.css\" \"*.scss\" \":(exclude)generated/**\"; git ls-files --others --exclude-standard -z -- \"*.js\" \"*.jsx\" \"*.ts\" \"*.tsx\" \"*.json\" \"*.css\" \"*.scss\" \":(exclude)generated/**\"; } | xargs -0 -r prettier --write --ignore-unknown'",
#!/bin/bash
# Verify whether format:changed includes the no-run-if-empty guard.
rg -n '"format:changed"' package.json

python - <<'PY'
import json
with open("package.json", "r", encoding="utf-8") as f:
    pkg = json.load(f)
cmd = pkg["scripts"]["format:changed"]
print("format:changed:", cmd)
print("contains '| xargs -0 -r prettier':", "| xargs -0 -r prettier" in cmd)
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 41, The "format:changed" npm script currently pipes a
possibly-empty NUL-separated file list into "xargs -0 prettier" which will run
prettier with no args if the list is empty; update the "format:changed" script
string to use "xargs -0 -r prettier" (add the -r / --no-run-if-empty flag) so
prettier is skipped when there are no files, and also scan the other related
scripts "quality:changed" and "audit:changed" and add the same "-r" to any
similar "xargs -0" invocations to keep changed-only behavior consistent.
🧹 Nitpick comments (5)
components/header/user/proxy/HeaderUserProxyDropdown.tsx (1)

138-150: Consider handling potential errors from seizeAddConnectedAccount.

The onAddAccount callback directly calls seizeAddConnectedAccount() without error handling. While seizeAddConnectedAccount in the context handles its own errors internally, consistency with other actions wrapped in runMenuAction would improve robustness.

♻️ Suggested improvement
                       <HeaderUserConnectedAccounts
                         accounts={availableConnectedAccounts}
                         onSelectAccount={onSelectConnectedAccount}
                         canAddAccount={canAddConnectedAccount}
-                        onAddAccount={() => {
-                          seizeAddConnectedAccount();
-                          onClose();
-                        }}
+                        onAddAccount={() => {
+                          void runMenuAction({
+                            action: seizeAddConnectedAccount,
+                            pendingKey: "add-account",
+                            errorMessage: "Failed to add account. Please try again.",
+                          });
+                        }}
                       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/header/user/proxy/HeaderUserProxyDropdown.tsx` around lines 138 -
150, The onAddAccount handler in HeaderUserProxyDropdown calls
seizeAddConnectedAccount() directly without running it through the existing
runMenuAction wrapper; change the onAddAccount callback given to
HeaderUserConnectedAccounts to invoke seizeAddConnectedAccount via runMenuAction
(e.g., runMenuAction(() => seizeAddConnectedAccount())) so its
errors/side-effects are handled consistently, and then call onClose after the
wrapped action completes (or in a finally-equivalent post-action step) to
preserve current behavior; update the onAddAccount arrow handler in
HeaderUserProxyDropdown accordingly.
components/auth/SeizeConnectContext.tsx (3)

952-958: Missing dependency in seizeAddConnectedAccount useMemo array.

The callback references storedConnectedAccounts.length in the dependency array, but the actual check uses canStoreAnotherWalletAccount() which may have its own internal state. This is fine if canStoreAnotherWalletAccount reads from the same source, but the dependency on .length is redundant since it's not directly used in the callback body.

♻️ Suggested cleanup
   }, [
     account.address,
     account.isConnected,
     disconnect,
     seizeConnect,
-    storedConnectedAccounts.length,
   ]);

Alternatively, if the intent is to re-create the callback when accounts change, use the full array or rely on refreshStoredConnectedAccounts being stable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/SeizeConnectContext.tsx` around lines 952 - 958, The useMemo
for seizeAddConnectedAccount incorrectly depends on
storedConnectedAccounts.length (a redundant dependency) while the callback
actually uses canStoreAnotherWalletAccount and possibly the full
storedConnectedAccounts; update the dependency array to include
canStoreAnotherWalletAccount and/or the entire storedConnectedAccounts (or
ensure refreshStoredConnectedAccounts is stable) so the memo recalculates
correctly when account storage state changes; locate the
seizeAddConnectedAccount definition and replace storedConnectedAccounts.length
with either canStoreAnotherWalletAccount or storedConnectedAccounts (or add
refreshStoredConnectedAccounts) depending on which value the callback truly
relies on.

200-201: Consider using a typed constant or configuration for the grace period.

The ADD_FLOW_CANCEL_GRACE_MS = 5000 is a reasonable value but could benefit from a comment explaining why 5 seconds was chosen, especially since it affects UX during wallet transitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/SeizeConnectContext.tsx` around lines 200 - 201, Replace the
hardcoded ADD_FLOW_CANCEL_GRACE_MS = 5000 with a clearly typed exported constant
or configuration value (e.g., export const ADD_FLOW_CANCEL_GRACE_MS: number =
5000 or pull from a config object) and add a short comment explaining why 5000ms
was chosen and how it impacts wallet transition UX; update any references (e.g.,
where ADD_FLOW_CANCEL_GRACE_MS is used in SeizeConnectContext logic) to use the
new typed/centralized value so the timeout can be easily adjusted or overridden
in the future.

984-1030: Potential stale closure: connectedAddress and impersonatedAddress in dependency array may not be needed.

The contextValue memo includes connectedAddress and impersonatedAddress in dependencies, but these are already captured via activeAddress. Additionally, walletInfo?.type is in the dependency array but not used in the memo body, which could cause unnecessary re-renders.

♻️ Suggested dependency cleanup
     [
       activeAddress,
       isActiveWalletConnected,
       connectedAccounts,
-      connectedAddress,
-      impersonatedAddress,
       walletInfo?.name,
       walletInfo?.icon,
-      walletInfo?.type,
       seizeConnect,
       seizeDisconnect,
       seizeDisconnectAndLogout,
       seizeDisconnectAndLogoutAll,
       seizeAcceptConnection,
       seizeSwitchConnectedAccount,
       seizeAddConnectedAccount,
       state.open,
-      account.isConnected,
       walletState,
       hasInitializationError,
       initializationError,
       canAddConnectedAccount,
     ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/SeizeConnectContext.tsx` around lines 984 - 1030, The memo
for contextValue includes unnecessary dependencies (connectedAddress,
impersonatedAddress, and walletInfo?.type) that aren't referenced in the
returned object and can cause extra re-renders; update the useMemo dependency
array to remove connectedAddress, impersonatedAddress, and walletInfo?.type
while keeping activeAddress, walletInfo?.name, walletInfo?.icon, all seize*
handler references (seizeConnect, seizeDisconnect, seizeDisconnectAndLogout,
seizeDisconnectAndLogoutAll, seizeAcceptConnection, seizeSwitchConnectedAccount,
seizeAddConnectedAccount), state.open, account.isConnected, walletState,
hasInitializationError, initializationError, connectedAccounts, and
canAddConnectedAccount so the memo dependencies exactly match the symbols used
in the SeizeConnectContextType value.
__tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx (1)

38-42: Consider adding a test for seizeSwitchConnectedAccount error handling.

Given the past review emphasis on error handling for account switching, adding a test that verifies the error toast appears when seizeSwitchConnectedAccount throws would strengthen coverage.

🧪 Suggested test case
it("shows error toast when switching account fails", async () => {
  const seizeSwitchConnectedAccount = jest.fn().mockImplementation(() => {
    throw new Error("Switch failed");
  });
  const { setToast, onClose } = renderDropdown({
    profile: profileBase,
    address: "0xabc",
    isConnected: true,
    connectedAccounts: [
      { address: "0xabc", role: null, isActive: true, isConnected: true },
      { address: "0xdef", role: null, isActive: false, isConnected: false },
    ],
    seizeSwitchConnectedAccount,
  });

  // Trigger account switch via the mock - would need to expose this in the mock
  // This is a placeholder showing the pattern
  await waitFor(() => {
    expect(setToast).toHaveBeenCalledWith(
      expect.objectContaining({ type: "error" })
    );
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx`
around lines 38 - 42, Add a test in HeaderUserProxyDropdown.test.tsx that
verifies error handling when seizeSwitchConnectedAccount throws: mock
seizeSwitchConnectedAccount to throw (jest.fn().mockImplementation(() => { throw
new Error("Switch failed") })) and pass it into renderDropdown along with
profileBase, isConnected true and two connectedAccounts (one active, one
inactive); trigger the account-switch UI action the same way other tests trigger
switching (or call the exposed handler used by the component), then await a
waitFor that asserts setToast was called with an object containing type: "error"
(use the existing setToast/onClose helpers returned by renderDropdown to observe
the toast).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@__tests__/components/auth/Auth.test.tsx`:
- Around line 63-72: The Modal mock currently always renders children causing
visibility tests to be invalid; modify the jest.mock for "react-bootstrap" so
the mocked Modal function accepts props (including show) and returns children
only when props.show is true (otherwise return null), and preserve the attached
static subcomponents Modal.Header, Modal.Title, Modal.Body, Modal.Footer; update
the mocked Modal signature (the Object.assign target) to check props.show before
rendering so tests that assert modal visibility behave correctly.

In `@components/auth/Auth.tsx`:
- Around line 205-206: The current render reads authRole = getRole(getAuthJwt())
which can throw on malformed/stale JWT and crash the provider tree; wrap the
derivation in a safe guard (e.g., try/catch or a safeDecode helper) inside the
Auth component so any decode/role extraction errors are caught and you return a
safe fallback (null/undefined or a "guest" role) instead of throwing; update
references to authRole to expect the fallback and ensure getRole/getAuthJwt
calls are only made inside the guarded block so the component never throws
during render.

In `@components/auth/SeizeConnectContext.tsx`:
- Around line 804-846: The while loop in seizeDisconnectAndLogoutAll can hang if
removeAuthJwt fails silently; add a defensive max-iteration guard (e.g.,
maxIterations or maxProfiles) when looping over getConnectedWalletAccounts() to
break out after a sensible limit, log an error via logError (use the same
"seizeDisconnectAndLogoutAll" tag) and throw an AuthenticationError if the loop
exceeds the limit; keep current check that compares nextRemainingProfiles to
remainingProfiles, but also increment an iteration counter each loop and use
refreshStoredConnectedAccounts()/setDisconnected() only on successful exit.

In `@components/header/AppHeader.tsx`:
- Around line 166-182: The call to seizeSwitchConnectedAccount in
switchToNextConnectedAccount is unguarded and can throw, so wrap the
seizeSwitchConnectedAccount(nextAccount.address) invocation in a try/catch; on
catch, log or handle the error, return false, and trigger the existing
account-menu fallback (invoke the component’s menu-opening handler or set the
menu state to open) so activation falls back to showing the menu instead of
breaking; references: switchToNextConnectedAccount, seizeSwitchConnectedAccount,
connectedAccounts.

In `@components/header/AppUserConnect.tsx`:
- Around line 33-47: runAsyncAndNavigate currently only console.error's caught
errors and thus swallows them; update it to use the same toast UX as
HeaderUserProxyDropdown.runMenuAction by importing and using AuthContext's
setToast, and in the catch block call setToast with a user-facing message
(errorMessage) and include error details for debugging, while preserving the
existing console.error and the final onNavigate() call; ensure you reference
runAsyncAndNavigate and HeaderUserProxyDropdown for parity when implementing the
toast behavior.

---

Outside diff comments:
In `@components/auth/Auth.tsx`:
- Around line 283-330: The stale-address checks comparing currentAddress to
address inside validateImmediately are invalid because both values are from the
same closure; replace them with a runtime check against a mutable ref or current
operation id to detect later changes. Add a ref like latestAddressRef (updated
in a useEffect whenever address changes) or an operationIdRef (set to
operationId at start) and then: (1) before/after awaiting validateAuthImmediate,
compare currentAddress !== latestAddressRef.current (or operationIdRef.current
!== operationId) to detect stale operations, and (2) when cleaning up, verify
abortControllerRef.current === abortController (not
abortController.signal.aborted) before clearing abortControllerRef and setting
setAuthLoadingState("idle"); update usages of currentAddress !== address in
validateImmediately to use the chosen ref comparison and ensure
abortControllerRef is only cleared by the active operation.

---

Duplicate comments:
In `@package.json`:
- Line 41: The "format:changed" npm script currently pipes a possibly-empty
NUL-separated file list into "xargs -0 prettier" which will run prettier with no
args if the list is empty; update the "format:changed" script string to use
"xargs -0 -r prettier" (add the -r / --no-run-if-empty flag) so prettier is
skipped when there are no files, and also scan the other related scripts
"quality:changed" and "audit:changed" and add the same "-r" to any similar
"xargs -0" invocations to keep changed-only behavior consistent.

---

Nitpick comments:
In `@__tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx`:
- Around line 38-42: Add a test in HeaderUserProxyDropdown.test.tsx that
verifies error handling when seizeSwitchConnectedAccount throws: mock
seizeSwitchConnectedAccount to throw (jest.fn().mockImplementation(() => { throw
new Error("Switch failed") })) and pass it into renderDropdown along with
profileBase, isConnected true and two connectedAccounts (one active, one
inactive); trigger the account-switch UI action the same way other tests trigger
switching (or call the exposed handler used by the component), then await a
waitFor that asserts setToast was called with an object containing type: "error"
(use the existing setToast/onClose helpers returned by renderDropdown to observe
the toast).

In `@components/auth/SeizeConnectContext.tsx`:
- Around line 952-958: The useMemo for seizeAddConnectedAccount incorrectly
depends on storedConnectedAccounts.length (a redundant dependency) while the
callback actually uses canStoreAnotherWalletAccount and possibly the full
storedConnectedAccounts; update the dependency array to include
canStoreAnotherWalletAccount and/or the entire storedConnectedAccounts (or
ensure refreshStoredConnectedAccounts is stable) so the memo recalculates
correctly when account storage state changes; locate the
seizeAddConnectedAccount definition and replace storedConnectedAccounts.length
with either canStoreAnotherWalletAccount or storedConnectedAccounts (or add
refreshStoredConnectedAccounts) depending on which value the callback truly
relies on.
- Around line 200-201: Replace the hardcoded ADD_FLOW_CANCEL_GRACE_MS = 5000
with a clearly typed exported constant or configuration value (e.g., export
const ADD_FLOW_CANCEL_GRACE_MS: number = 5000 or pull from a config object) and
add a short comment explaining why 5000ms was chosen and how it impacts wallet
transition UX; update any references (e.g., where ADD_FLOW_CANCEL_GRACE_MS is
used in SeizeConnectContext logic) to use the new typed/centralized value so the
timeout can be easily adjusted or overridden in the future.
- Around line 984-1030: The memo for contextValue includes unnecessary
dependencies (connectedAddress, impersonatedAddress, and walletInfo?.type) that
aren't referenced in the returned object and can cause extra re-renders; update
the useMemo dependency array to remove connectedAddress, impersonatedAddress,
and walletInfo?.type while keeping activeAddress, walletInfo?.name,
walletInfo?.icon, all seize* handler references (seizeConnect, seizeDisconnect,
seizeDisconnectAndLogout, seizeDisconnectAndLogoutAll, seizeAcceptConnection,
seizeSwitchConnectedAccount, seizeAddConnectedAccount), state.open,
account.isConnected, walletState, hasInitializationError, initializationError,
connectedAccounts, and canAddConnectedAccount so the memo dependencies exactly
match the symbols used in the SeizeConnectContextType value.

In `@components/header/user/proxy/HeaderUserProxyDropdown.tsx`:
- Around line 138-150: The onAddAccount handler in HeaderUserProxyDropdown calls
seizeAddConnectedAccount() directly without running it through the existing
runMenuAction wrapper; change the onAddAccount callback given to
HeaderUserConnectedAccounts to invoke seizeAddConnectedAccount via runMenuAction
(e.g., runMenuAction(() => seizeAddConnectedAccount())) so its
errors/side-effects are handled consistently, and then call onClose after the
wrapped action completes (or in a finally-equivalent post-action step) to
preserve current behavior; update the onAddAccount arrow handler in
HeaderUserProxyDropdown accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7601576 and 8d7bf57.

📒 Files selected for processing (13)
  • __tests__/components/auth/Auth.test.tsx
  • __tests__/components/auth/connection-state-indicator.test.ts
  • __tests__/components/header/AppUserConnect.test.tsx
  • __tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx
  • app/accept-connection-sharing/page.client.tsx
  • components/auth/Auth.tsx
  • components/auth/SeizeConnectContext.tsx
  • components/header/AppHeader.tsx
  • components/header/AppSidebarHeader.tsx
  • components/header/AppUserConnect.tsx
  • components/header/user/proxy/HeaderUserProxyDropdown.tsx
  • components/layout/sidebar/WebSidebarUser.tsx
  • package.json

Comment thread __tests__/components/auth/Auth.test.tsx
Comment thread components/auth/Auth.tsx Outdated
Comment thread components/auth/SeizeConnectContext.tsx Outdated
Comment thread components/header/AppHeader.tsx Outdated
Comment thread components/header/AppUserConnect.tsx
prxt6529 added 2 commits March 2, 2026 09:48
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/auth/Auth.tsx`:
- Around line 636-639: The calls to seizeDisconnect() are fire-and-forget and
can produce unhandled rejections; update places that call seizeDisconnect()
(e.g., the branches around setShowSignModal and the other call near line 737) to
await the Promise or attach a .catch handler so rejections (including
WalletDisconnectionError) are handled cleanly; specifically, wrap the call in a
try/catch when using await (try { await seizeDisconnect(); } catch (err) {
process or log the error and continue cleanup }) or use
seizeDisconnect().catch(err => { /* log/handle WalletDisconnectionError and
ensure session cleanup */ }) to avoid leaving the session in an incomplete state
while preserving the existing control flow (returning { success: false } where
applicable).

In `@components/auth/SeizeConnectContext.tsx`:
- Around line 951-957: The delayed reconnect callback currently calls
seizeConnect() directly inside the setTimeout (see
retryConnectTimeoutRef.current and isMountedRef.current) which allows its thrown
WalletConnectionError to escape and leave isAddingConnectedAccount stuck; wrap
the seizeConnect() invocation in a try/catch inside that timeout callback, catch
any errors (including WalletConnectionError), log or rethrow as appropriate, and
ensure you reset the isAddingConnectedAccount state (or clear any flags) in the
catch/finally so the add-account flow cannot remain blocked.

In `@components/header/user/proxy/HeaderUserProxyDropdown.tsx`:
- Around line 145-152: The call to onClose is invoked twice because
runMenuAction (used earlier around line 94) already triggers onClose on success;
remove the redundant .finally(() => { onClose(); }) wrapping this invocation so
onClose is only called once. Specifically, update the block that calls
runMenuAction({ action: () => seizeAddConnectedAccount(), pendingKey:
"add-account", errorMessage: ... }) to omit the .finally handler and rely on
runMenuAction's internal close behavior (or alternatively, if you intend the
caller to control closing, remove the close logic from runMenuAction
instead—choose one consistent place to call onClose).

In `@eslint.config.tight.mjs`:
- Line 149: The tight ESLint config currently sets includeTestResultsIgnore:
false which re-enables linting of Playwright/test artifacts; change the setting
back to true (or restore the "**/test-results/**" ignore entry) so the
test-results directory remains ignored—update the includeTestResultsIgnore value
referenced in eslint.config.tight.mjs to true (or re-add the test-results ignore
pattern) to prevent generated artifacts from being linted.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d7bf57 and 3a110b4.

📒 Files selected for processing (11)
  • __tests__/components/auth/Auth.test.tsx
  • __tests__/components/header/user/proxy/HeaderUserProxyDropdown.test.tsx
  • components/auth/Auth.tsx
  • components/auth/SeizeConnectContext.tsx
  • components/header/AppHeader.tsx
  • components/header/AppUserConnect.tsx
  • components/header/user/proxy/HeaderUserProxyDropdown.tsx
  • components/notifications/NotificationsContext.tsx
  • eslint.config.mjs
  • eslint.config.tight.mjs
  • package.json

Comment thread components/auth/Auth.tsx
Comment thread components/auth/SeizeConnectContext.tsx Outdated
Comment thread components/header/user/proxy/HeaderUserProxyDropdown.tsx
Comment thread eslint.config.tight.mjs Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
components/auth/Auth.tsx (1)

740-751: ⚠️ Potential issue | 🟠 Major

Handle rejected logout promise in cancel flow.

Line 750 invokes seizeDisconnectAndLogout() without a rejection handler. If logout/disconnect fails, this can surface as an unhandled promise rejection and silently fail the cancel flow.

Suggested fix
   const onCancelSignRequest = useCallback(() => {
     setShowSignModal(false);

     if (!isAddressAuthorized) {
       void seizeDisconnect().catch((error) => {
         logErrorSecurely("onCancelSignRequest_disconnect", error);
       });
       return;
     }

-    void seizeDisconnectAndLogout();
+    void seizeDisconnectAndLogout().catch((error) => {
+      logErrorSecurely("onCancelSignRequest_logout", error);
+      setToast({
+        message: "Failed to sign out. Please try again.",
+        type: "error",
+      });
+    });
   }, [isAddressAuthorized, seizeDisconnect, seizeDisconnectAndLogout]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/Auth.tsx` around lines 740 - 751, The onCancelSignRequest
handler calls seizeDisconnectAndLogout() without handling rejection; update
onCancelSignRequest so after setShowSignModal(false) you await or attach a
.catch to seizeDisconnectAndLogout() (similar to the seizeDisconnect branch) and
call logErrorSecurely("onCancelSignRequest_disconnectAndLogout", error) on
failure; keep the existing behavior for the unauthorized branch using
seizeDisconnect and ensure the function dependencies (isAddressAuthorized,
seizeDisconnect, seizeDisconnectAndLogout) remain unchanged.
🧹 Nitpick comments (1)
eslint.config.tight.mjs (1)

150-150: Explicit strict enforcement can silently degrade if plugin loading fails.

At line 150 in eslint.config.tight.mjs, reactCompilerRule: "error" declares strict intent. However, in eslint.config.mjs (lines 15–23, 480–482), the rule is only applied when reactCompilerPlugin successfully loads. While the plugin is declared as a dependency (eslint-plugin-react-compiler: ^19.1.0-rc.2), a loading failure is caught silently without warning—causing the explicit "error" setting to be ignored.

To match the caller's intent, either fail loudly when the plugin cannot load, or apply enforcement regardless of plugin presence, or at minimum emit a warning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.tight.mjs` at line 150, The config sets reactCompilerRule:
"error" but the loader for reactCompilerPlugin swallows load failures, so the
strict rule can be silently ignored; update the plugin-loading logic that
initializes reactCompilerPlugin to either rethrow the loading error or at
minimum emit a clear warning via the same logger when the plugin cannot be
required, and ensure the code that applies reactCompilerRule checks the plugin
load result and fails fast (throw) or explicitly applies a noop/fallback rule so
the "error" intent is not silently skipped; reference reactCompilerRule and
reactCompilerPlugin and adjust the loader/initializer where the plugin is
required and the rule is applied (the module that conditionally applies the
rule) to implement the loud failure or warning behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/auth/SeizeConnectContext.tsx`:
- Around line 930-1001: seizeAddConnectedAccount needs a re-entrancy guard to
avoid races: at the top of the function return early if an add flow is already
in progress (use the existing isAddingConnectedAccount state or, preferably, a
mutable ref like isAddingRef.current to avoid stale closures), then set that
guard immediately before starting any async work; ensure you clear the guard and
reset addFlowOriginAddressRef.current and retryConnectTimeoutRef.current in
every error path and after the final successful seizeConnect call (both the
immediate try/catch path and the disconnect().then timer branch). Update the
logic inside seizeAddConnectedAccount (and its error/then handlers) to set
isAdding (or isAddingRef) true at start and false on all exits so concurrent
invocations cannot run overlapping disconnect/retry timers.

---

Duplicate comments:
In `@components/auth/Auth.tsx`:
- Around line 740-751: The onCancelSignRequest handler calls
seizeDisconnectAndLogout() without handling rejection; update
onCancelSignRequest so after setShowSignModal(false) you await or attach a
.catch to seizeDisconnectAndLogout() (similar to the seizeDisconnect branch) and
call logErrorSecurely("onCancelSignRequest_disconnectAndLogout", error) on
failure; keep the existing behavior for the unauthorized branch using
seizeDisconnect and ensure the function dependencies (isAddressAuthorized,
seizeDisconnect, seizeDisconnectAndLogout) remain unchanged.

---

Nitpick comments:
In `@eslint.config.tight.mjs`:
- Line 150: The config sets reactCompilerRule: "error" but the loader for
reactCompilerPlugin swallows load failures, so the strict rule can be silently
ignored; update the plugin-loading logic that initializes reactCompilerPlugin to
either rethrow the loading error or at minimum emit a clear warning via the same
logger when the plugin cannot be required, and ensure the code that applies
reactCompilerRule checks the plugin load result and fails fast (throw) or
explicitly applies a noop/fallback rule so the "error" intent is not silently
skipped; reference reactCompilerRule and reactCompilerPlugin and adjust the
loader/initializer where the plugin is required and the rule is applied (the
module that conditionally applies the rule) to implement the loud failure or
warning behavior.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a110b4 and 3c7078c.

📒 Files selected for processing (4)
  • components/auth/Auth.tsx
  • components/auth/SeizeConnectContext.tsx
  • components/header/user/proxy/HeaderUserProxyDropdown.tsx
  • eslint.config.tight.mjs

Comment thread components/auth/SeizeConnectContext.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
components/auth/Auth.tsx (1)

669-678: ⚠️ Potential issue | 🟡 Minor

Gate cache invalidation on successful re-auth.

invalidateAll() runs even if requestSignIn(...) fails. That creates unnecessary refetch churn while still unauthenticated.

💡 Suggested patch
       if (!isValid) {
         removeAuthJwt();
-        await requestSignIn({
+        const { success } = await requestSignIn({
           signerAddress: address,
           role: activeProfileProxy
             ? validateRoleForAuthentication(activeProfileProxy)
             : null,
         });
-        invalidateAll();
+        if (success) {
+          invalidateAll();
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/Auth.tsx` around lines 669 - 678, The current flow calls
invalidateAll() regardless of whether requestSignIn(...) succeeds; change it so
cache invalidation only runs after a successful re-auth: await
requestSignIn(...) inside a try/catch, and only call invalidateAll() in the try
(after the awaited call resolves); if requestSignIn throws, catch the error (log
or handle it) and do not call invalidateAll(). Keep removeAuthJwt() where it is
if you intend to clear the token before re-auth, but ensure invalidateAll() is
gated on requestSignIn success (use the symbols isValid, removeAuthJwt,
requestSignIn, and invalidateAll to locate the code).
♻️ Duplicate comments (1)
components/auth/SeizeConnectContext.tsx (1)

975-977: ⚠️ Potential issue | 🟠 Major

Re-entrancy guard is released too early in add-account flow.

clearAddConnectedAccountGuard() is called immediately after seizeConnect() succeeds, so a second seizeAddConnectedAccount() can start while the first add flow is still in progress.

💡 Suggested patch
   const seizeAddConnectedAccount = useCallback((): void => {
-    if (isAddingConnectedAccountRef.current) {
+    if (isAddingConnectedAccount || isAddingConnectedAccountRef.current) {
       return;
     }
@@
               try {
                 seizeConnect();
-                clearAddConnectedAccountGuard();
               } catch (error: unknown) {
                 clearAddConnectedAccountGuard();
                 setIsAddingConnectedAccount(false);
@@
     try {
       seizeConnect();
-      clearAddConnectedAccountGuard();
     } catch (error: unknown) {
       clearAddConnectedAccountGuard();
       setIsAddingConnectedAccount(false);

Also applies to: 1012-1015

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/auth/SeizeConnectContext.tsx` around lines 975 - 977, The guard is
being cleared too early: when seizeConnect() succeeds we call
clearAddConnectedAccountGuard() allowing another seizeAddConnectedAccount() to
start while the first add-account flow is still active; instead, keep the guard
set until the add-account flow fully completes (success or failure) — move the
clearAddConnectedAccountGuard() call out of the immediate post-seizeConnect()
path and into the finalization path (or finally block) that runs after the
entire add-account flow finishes, and apply the same change to the duplicate
block around lines 1012-1015 so clearAddConnectedAccountGuard() is only called
once the add flow is done.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/auth/SeizeConnectContext.tsx`:
- Around line 745-748: seizeDisconnect() currently returns early when
!isActiveWalletConnected which can skip disconnecting the live wallet if
activeAddress was switched via seizeSwitchConnectedAccount; update
seizeDisconnect to verify the wallet's real connection state (e.g., query the
provider/current signer or compare the live wallet address from the provider to
activeAddress) and proceed to disconnect the wallet if the provider indicates a
connected session even when isActiveWalletConnected is false; apply the same
change to the other seizeDisconnect implementation around the 905-923 block so
both use the provider-based check (or explicit address comparison to the live
wallet) instead of relying solely on isActiveWalletConnected.

In `@eslint.config.mjs`:
- Around line 441-457: DEFAULT_GLOBAL_IGNORES currently contains broad globs
("*.js", "*.mjs", "*.ts", "*.tsx") that accidentally exclude root Next.js
runtime entries like instrumentation.ts and instrumentation-client.ts; remove
those four broad patterns from DEFAULT_GLOBAL_IGNORES and instead add narrowly
scoped ignore patterns that target only config files (for example,
config/**/*.js, config/**/*.mjs, config/**/*.ts and/or root config filenames
like *.config.js, *.config.mjs, *.config.ts) so application entry files such as
instrumentation.ts and instrumentation-client.ts remain linted; update the
DEFAULT_GLOBAL_IGNORES constant accordingly.

---

Outside diff comments:
In `@components/auth/Auth.tsx`:
- Around line 669-678: The current flow calls invalidateAll() regardless of
whether requestSignIn(...) succeeds; change it so cache invalidation only runs
after a successful re-auth: await requestSignIn(...) inside a try/catch, and
only call invalidateAll() in the try (after the awaited call resolves); if
requestSignIn throws, catch the error (log or handle it) and do not call
invalidateAll(). Keep removeAuthJwt() where it is if you intend to clear the
token before re-auth, but ensure invalidateAll() is gated on requestSignIn
success (use the symbols isValid, removeAuthJwt, requestSignIn, and
invalidateAll to locate the code).

---

Duplicate comments:
In `@components/auth/SeizeConnectContext.tsx`:
- Around line 975-977: The guard is being cleared too early: when seizeConnect()
succeeds we call clearAddConnectedAccountGuard() allowing another
seizeAddConnectedAccount() to start while the first add-account flow is still
active; instead, keep the guard set until the add-account flow fully completes
(success or failure) — move the clearAddConnectedAccountGuard() call out of the
immediate post-seizeConnect() path and into the finalization path (or finally
block) that runs after the entire add-account flow finishes, and apply the same
change to the duplicate block around lines 1012-1015 so
clearAddConnectedAccountGuard() is only called once the add flow is done.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7078c and 4bc9263.

📒 Files selected for processing (3)
  • components/auth/Auth.tsx
  • components/auth/SeizeConnectContext.tsx
  • eslint.config.mjs

Comment thread components/auth/SeizeConnectContext.tsx
Comment thread eslint.config.mjs Outdated
prxt6529 added 7 commits March 2, 2026 11:19
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
prxt6529 added 15 commits March 2, 2026 16:51
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 3, 2026

@prxt6529 prxt6529 merged commit 640d15a into main Mar 3, 2026
7 checks passed
This was referenced Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants