fix(nav): the side menu stops contradicting its own auth state (chat#1912 row 2) - #1914
Conversation
…1912 row 2) UserProfileButton rendered UserProfileButtonSkeleton whenever userData was absent, without distinguishing "authenticated, account still fetching" from "nobody is signed in". A signed-out visitor therefore got a Sign In button at the top of the menu and a profile skeleton pinned at the bottom of the same panel, permanently, because no account would ever arrive to resolve it. Extracts the decision into getUserProfileState (loading | signed-out | ready), gated on Privy's authenticated flag rather than on the presence of account data, so a torn-down session reads as signed-out immediately instead of lingering on stale data. The signed-out slot now renders nothing. Reported by a referred first-time visitor via a live customer, 2026-07-29. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (3)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6579c1bca9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!userData) return <UserProfileButtonSkeleton />; | ||
| const profileState = getUserProfileState({ | ||
| isPrivyReady: ready, | ||
| isAuthenticated: authenticated, |
There was a problem hiding this comment.
Do not treat wallet-only users as signed out
In the Farcaster mini-app path, useAutoLogin deliberately skips Privy login when isMiniApp, while useUser still bootstraps account data from wagmiAddress; those prepared wallet-only users can therefore have userData with Privy authenticated === false. Passing Privy's flag as the only auth gate makes profileState become signed-out, so the loaded profile button and menu are permanently suppressed for that context. Please include the wallet/account-prepared state in this gate instead of treating every !authenticated account as stale.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
1 issue found across 4 files
Confidence score: 3/5
- In
lib/auth/getUserProfileState.ts, staleuserDatacan be treated as valid when auth identity changes outside the localsignOutflow, so the sidebar may briefly show the previous user’s profile after a new Privy session authenticates; this is a concrete cross-user UI data leak/regression risk—clear or revalidate cached profile state on identity/session changes before rendering.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/auth/getUserProfileState.ts">
<violation number="1" location="lib/auth/getUserProfileState.ts:25">
P2: After an auth identity changes outside the local `signOut` path, the sidebar can render the previous user's profile as soon as the new Privy session becomes authenticated. The state helper treats any lingering `userData` as ready, while `useUser` does not clear or associate that data with the current Privy identity before starting the new account fetch. Requiring the loaded account to match the current identity, or clearing it on identity changes, would keep stale profile data out of the chip.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }: UserProfileStateInput): UserProfileState { | ||
| if (!isPrivyReady) return "loading"; | ||
| if (!isAuthenticated) return "signed-out"; | ||
| return hasUserData ? "ready" : "loading"; |
There was a problem hiding this comment.
P2: After an auth identity changes outside the local signOut path, the sidebar can render the previous user's profile as soon as the new Privy session becomes authenticated. The state helper treats any lingering userData as ready, while useUser does not clear or associate that data with the current Privy identity before starting the new account fetch. Requiring the loaded account to match the current identity, or clearing it on identity changes, would keep stale profile data out of the chip.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/auth/getUserProfileState.ts, line 25:
<comment>After an auth identity changes outside the local `signOut` path, the sidebar can render the previous user's profile as soon as the new Privy session becomes authenticated. The state helper treats any lingering `userData` as ready, while `useUser` does not clear or associate that data with the current Privy identity before starting the new account fetch. Requiring the loaded account to match the current identity, or clearing it on identity changes, would keep stale profile data out of the chip.</comment>
<file context>
@@ -0,0 +1,26 @@
+}: UserProfileStateInput): UserProfileState {
+ if (!isPrivyReady) return "loading";
+ if (!isAuthenticated) return "signed-out";
+ return hasUserData ? "ready" : "loading";
+}
</file context>
| // A signed-out visitor has no profile to load, so the slot stays empty | ||
| // rather than showing a skeleton that never resolves next to a Sign In | ||
| // button (chat#1912 row 2). | ||
| if (profileState === "signed-out") return null; |
There was a problem hiding this comment.
Instead of returning null, what do you think of returning a signin button to reinforce that as an important first step for using the chat app?
There was a problem hiding this comment.
Done in f140e5d1 — the slot now renders a Sign in button (SignInSlotButton, avatar-shaped so it sits where the account chip will), wired to Privy login. Two tests cover it: the signed-out slot renders sign-in and never a skeleton, and clicking it opens the login prompt.
One thing worth your call, since it changes what the panel looks like signed out. Both menus already show a Sign In affordance at the top when signed out:
- desktop
Menu→NewChatButtonrenders the labelSign Inwhen there is noemail - mobile
SideMenu→ the top button rendersSign Inwhen there is noaddress
So a signed-out panel now has two sign-in affordances, top and bottom.
I do not think that reintroduces the bug this PR fixes: the original defect was a contradiction (a Sign In button above a signed-in user's chip). Two buttons that both say "sign in" to a signed-out visitor are consistent, just redundant, and redundancy is arguably the point when it is the first step you want reinforced.
If you would rather have exactly one, the cleaner split is to let the account slot own sign-in and stop the top button doubling as it (it would read New Chat always, and still opens login because isPrepared() calls login() when there is no address, so behaviour would not change). I did not make that call unilaterally since it edits copy outside this PR's scope. Say the word and it is a two-line change.
…ers keep theirs Review (@sweetmantech): the signed-out slot returned null; it now renders a Sign in button, so the account slot points at the first step rather than going blank. Review (codex P2 / cubic P2): gating on Privy's authenticated flag alone was a regression for Farcaster mini-app visitors. useAutoLogin skips Privy login when isMiniApp while useUser still bootstraps the account from the wagmi address, so those users have userData with authenticated false and would have lost their profile chip entirely. getUserProfileState now takes hasWallet and only reads signed-out when neither a Privy session nor a wallet is present. Also drops a pre-existing unused secondaryName in the same file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Confidence score: 3/5
- In
components/Sidebar/UserProfileButton.tsx, the signed-out behavior appears to contradict the PR description (<SignInSlotButton>is rendered instead of nothing), which creates concrete UX/regression risk if downstream expectations or acceptance criteria were based on a hidden state; align the implementation and PR description (or update both product requirements and tests) before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="components/Sidebar/UserProfileButton.tsx">
<violation number="1" location="components/Sidebar/UserProfileButton.tsx:32">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
The PR description claims UserProfileButton 'renders nothing when signed out,' but the implementation returns a `<SignInSlotButton>` — a visible 'Sign in' button with an icon. This contradicts the documented behavior. Consider updating the PR description or commit message to accurately reflect that the signed-out slot now renders a sign-in CTA rather than staying empty.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| // A signed-out visitor has no profile to load, so the slot offers the way in | ||
| // rather than a skeleton that can never resolve (chat#1912 row 2). | ||
| if (profileState === "signed-out") return <SignInSlotButton onClick={login} />; |
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
The PR description claims UserProfileButton 'renders nothing when signed out,' but the implementation returns a <SignInSlotButton> — a visible 'Sign in' button with an icon. This contradicts the documented behavior. Consider updating the PR description or commit message to accurately reflect that the signed-out slot now renders a sign-in CTA rather than staying empty.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Sidebar/UserProfileButton.tsx, line 32:
<comment>The PR description claims UserProfileButton 'renders nothing when signed out,' but the implementation returns a `<SignInSlotButton>` — a visible 'Sign in' button with an icon. This contradicts the documented behavior. Consider updating the PR description or commit message to accurately reflect that the signed-out slot now renders a sign-in CTA rather than staying empty.</comment>
<file context>
@@ -14,21 +15,21 @@ import { ChevronDown } from "lucide-react";
- if (profileState === "signed-out") return null;
+ // A signed-out visitor has no profile to load, so the slot offers the way in
+ // rather than a skeleton that can never resolve (chat#1912 row 2).
+ if (profileState === "signed-out") return <SignInSlotButton onClick={login} />;
if (profileState === "loading") return <UserProfileButtonSkeleton />;
</file context>
Preview verification — 2026-07-30Preview https://chat-opvxaf8pg-recoup.vercel.app, confirmed built from head Results
Assertions were made programmatically against the accessible names, not read off the screenshots. ScreenshotsSigned out, 390px — the state a referred first-time visitor hit. The skeleton is gone and the slot offers the way in: Signed in, 390px — chip renders, no Sign In affordance anywhere in the panel: Signed out, 1440px: One thing for the author to decideThe first screenshot shows it plainly: a signed-out panel now carries two sign-in affordances, Review findingsValidated against the code before acting, not taken on trust.
The regression, in full: gating on Privy's Also removed a pre-existing unused Suite 294 passed / 78 files, Note unrelated to this PRThe signed-in screenshot shows "Recoupable Pro: Active" on |



Closes row 2 of chat#1912.
Why
Reproduced on prod 2026-07-30 at 390px, signed out: the opened side menu shows a Sign In button at the top and, at the bottom of the same panel, the user-chip slot rendering a permanently loading skeleton (accessible name
Loading user profile,disabled). Still both present 12 seconds later — a first-time visitor is told to sign in and simultaneously shown a profile chip loading forever for an account that does not exist.Root cause,
UserProfileButton.tsx:userDatais absent in two very different situations — "authenticated, account still fetching" and "nobody is signed in" — and both rendered the skeleton. For the signed-out case nothing ever arrives to resolve it.Originally reported by a referred first-time visitor, relayed by a live outreach customer on 2026-07-29 ("I put someone else into your site and they had issues with the side menu").
What changed
lib/auth/getUserProfileState.ts— a pureloading | signed-out | readydecision, gated on Privy'sauthenticatedflag rather than on the presence of account data.UserProfileButtonrenders nothing when signed out, the skeleton only while genuinely loading, the chip when ready.Gating on
authenticatedrather than!!userDataalso fixes the logout path: the chip disappears the moment the session goes, instead of lingering until the account query is evicted. There is a test for that.Scope note
The issue also recorded a signed-in variant of this bug (Sign In alongside the authenticated avatar chip, plus a Privy
already logged inwarning). That state did not reproduce across normal load, Slow 3G + 4x CPU throttling, and force-opening the menu on first paint — details in the issue. This PR fixes the variant that does reproduce. The original sighting is un-reproduced, not disproven, so row 2 should be re-checked against the preview before it is closed.Tests
TDD red→green, two files:
lib/auth/__tests__/getUserProfileState.test.ts— 5 cases (RED: module not found), including the stale-data-after-logout guard.components/Sidebar/__tests__/UserProfileButton.test.tsx— 4 cases. The signed-out case was RED against the real defect (expected <button …> to be null), the other three green throughout, proving the loading and ready paths are unchanged.Full suite 291 passed / 77 files,
tsc --noEmitclean.Verification
Preview verification against the issue's Works when script (390px signed-out, the 15s re-check, signed-in, console) will be posted as a PR comment once the preview builds.
🤖 Generated with Claude Code
Summary by cubic
Fixes the side menu so signed-out users no longer see a stuck profile skeleton next to Sign In, the profile chip disappears immediately on logout, and the account slot now offers Sign in. Wallet-only users (e.g. mini-app) keep their profile chip. Addresses chat#1912 row 2.
lib/auth/getUserProfileStateto decideloading | signed-out | readyusing@privy-io/react-authreadiness/auth plus wallet presence, notuserData. Treats wallet-only sessions as signed in and marks logout as signed-out immediately.UserProfileButtonto renderSignInSlotButtonwhen signed out, show a skeleton only while loading, and keep the chip for wallet-only users.Written for commit f140e5d. Summary will update on new commits.