fix(nav): show loading spinner on account button until auth resolves (#4606)

## Problem

While `/users/@me` is still resolving, the desktop nav account button
says "Sign in", then flips to the avatar once the response lands. For
logged-in users this briefly (and wrongly) claims they're signed out.

## Fix

Start the account pill in a neutral loading state and only commit to a
real state once auth resolves:

- **DesktopNavBar.ts** — added a small inline spinner (same `border-2 …
rounded-full animate-spin` pattern used across the client, e.g.
TokenLoginModal, UsernameInput) inside the pill, visible by default; the
person icon and "Sign in" text now start `hidden`.
- **Main.ts** — `updateAccountNavButton` hides the spinner as soon as it
runs; each of its paths (avatar / email badge / sign-in) already
un-hides the correct elements.
- **CrazyGamesAccountButton.ts** — same spinner hide in
`updateCrazyGamesNavButton`, which updates the same pill on CrazyGames
instead.

The mobile hamburger account item says "Account" rather than "Sign in",
so it's unaffected.

## Testing

Verified end-to-end with headless Chromium against the dev server:

- On load the pill shows only the spinner (sign-in text and person icon
hidden).
- Once auth resolves it swaps to the person icon + "Sign in" (avatar
path exercised by existing `updateAccountNavButton` logic, unchanged).
- Stalled `/auth/refresh` via request interception to screenshot the
loading state — spinner renders centered in the pill, no layout shift
with neighboring nav items.

| Loading | Resolved |
| --- | --- |
| small spinner in pill | person icon + "Sign in" |

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Evan
2026-07-13 20:20:36 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 376061793a
commit 62abdcd64e
3 changed files with 16 additions and 2 deletions
+4
View File
@@ -33,6 +33,10 @@ export async function updateCrazyGamesNavButton() {
const personIconEl = document.getElementById("nav-account-person-icon");
// CrazyGames accounts have no email, so the email badge is always hidden.
document.getElementById("nav-account-email-badge")?.classList.add("hidden");
// Auth state is resolved, so the button no longer shows the loading spinner.
document
.getElementById("nav-account-loading-spinner")
?.classList.add("hidden");
const signInTextEl = document.getElementById("nav-account-signin-text");
if (profile) {
if (avatarEl) {
+5
View File
@@ -103,6 +103,11 @@ function updateAccountNavButton(userMeResponse: UserMeResponse | false) {
"nav-account-signin-text",
) as HTMLSpanElement | null;
// Auth state is resolved, so the button no longer shows the loading spinner.
document
.getElementById("nav-account-loading-spinner")
?.classList.add("hidden");
// Unique token for this update call
const navToken = Symbol();
if (avatarEl) avatarEl._navToken = navToken;
+7 -2
View File
@@ -160,9 +160,14 @@ export class DesktopNavBar extends LitElement {
data-i18n-alt="main.discord_avatar_alt"
referrerpolicy="no-referrer"
/>
<span
id="nav-account-loading-spinner"
class="w-4 h-4 border-2 border-white/30 border-t-white/80 rounded-full animate-spin"
aria-hidden="true"
></span>
<svg
id="nav-account-person-icon"
class="w-5 h-5"
class="hidden w-5 h-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
@@ -197,7 +202,7 @@ export class DesktopNavBar extends LitElement {
</span>
<span
id="nav-account-signin-text"
class="text-xs font-bold tracking-widest"
class="hidden text-xs font-bold tracking-widest"
data-i18n="main.sign_in"
>
</span>