feat(username): clearer verified name and a clan tag picker - #4983
feat(username): clearer verified name and a clan tag picker#4983ryanbarlow97 wants to merge 23 commits into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Walkthrough
ChangesUsername controls
Estimated code review effort: 3 (Moderate) | ~25 minutes Mergeability Score: ⚪ Minimal · up to The PR is merge-ready after normal checks and review; no actionable merge-blocking risk remains. Sequence Diagram(s)sequenceDiagram
participant User
participant UsernameInput
participant getUserMe
participant ClanBrowser
UsernameInput->>getUserMe: Load cached account data
User->>UsernameInput: Open clan menu
UsernameInput-->>User: Show owned clans and custom-tag actions
User->>UsernameInput: Select, clear, or enter a clan tag
UsernameInput-->>User: Update the username control
User->>UsernameInput: Open the clan browser
UsernameInput->>ClanBrowser: Navigate to clan browser
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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 |
The check led the name in the identity bar, which is the opposite of every other place a verified name is rendered (PlayerName, lobby lists, profile modal, in game) — all of them put the mark after the name. The name span now shrinks rather than growing, so the mark hugs it instead of drifting to the far edge, and the VERIFIED pill keeps the right edge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The verified chip and Use verified button used Tailwind's stock blues (blue-400 #60a5fa, blue-500 #3b82f6, blue-200 #bfdbfe). The page accent is malibu-blue #0084d1 hovering to aquarius #3fa9f5 — roughly 15 degrees of hue apart and leaning violet, so a blue-tinted chip sitting above the malibu SOLO button read as two competing blues. The clan menu was already on brand, which is why it never had the problem. Chip, pill and toggle now use malibu-blue, and the check inside the bar uses aquarius via VerifiedBadge's colour parameter. The shared badge's default is untouched, so lobby lists, the profile modal and the in-game mark are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collapsed a ternary onto one line by hand in the previous commit; at 81 characters Prettier reflows it, so CI's --check failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Toggling verified moved the name 26px sideways at desktop width. Two causes, both in this branch: - The row was centred, so the group re-centred whenever the trailing control changed width — "Use verified" (127px) versus "Change" (72px) moved everything before it by half the difference. The row now starts at the leading edge and the slack collects at the end, which is also robust to translations of any length. - The verified chip used px-2 sm:px-2.5 against the input's px-2 sm:px-3, worth another 2px in the opposite direction. Below sm the labels are hidden and the two buttons were 38px and 34px wide, resizing the field by 4px; the Change icon now matches the badge's box. Measured across the toggle at 1000px and 400px: leading edge, field width and glyph origin are all identical in both states. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Left-aligning the row stopped the name shifting, but parked all the slack at one end, so a bar with no live-streamer panel beside it had a visible gap hanging off the right. Both trailing buttons now share one grid cell, so the slot is always as wide as the wider of the two — "Use verified" (127px) against "Change" (72px), and further apart in some languages. The inactive one is visibility:hidden, which also keeps it out of the tab order and the accessibility tree. With the slot width fixed, centring the row is safe again and the slack splits either side as padding. Measured across the toggle at 1600/1000/820/420px: glyph origin and slot width are identical in both states, and exactly two buttons are focusable either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured from the reported screenshots: both states draw the same 44px field and the same 18px-tall glyphs, but the typed name sits 14px below the field's top border and 12px above its bottom, while the verified name sits 13/13. Toggling verified therefore lifted the text by 1px. An <input> centres its editor box from the font's own metrics, whereas a span centres a line box and then distributes half-leading; when the font's ascent+descent doesn't divide evenly against the line height the two round a pixel apart. Setting the leading equal to the field's content box (the 40/44px box less its 1px borders) leaves no leftover leading in either element, so both reduce to the same placement. Verified the invariant holds in both states at both breakpoints: content box 42px/38px, computed line-height 42px/38px. Not reproducible here — none of the six fonts available in this container shows the offset, before or after — so this fixes the mechanism rather than a locally observed symptom. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dropped the 24rem cap: the field now takes whatever the tag picker and the trailing button leave, so the row always fills the strip — widest on a play page with no live-streamer panel beside it, narrower when there is one. Measured: 705px at a 1000px viewport, 580px at 1600px, 230px at 420px, with no slack left over at any width. The box and text classes both states share are now single constants. They had already drifted twice — mismatched padding and mismatched leading — each time showing up as the name jumping when verified was toggled, so the shared metrics belong in one place rather than two class strings. Field width and left edge stay identical across the toggle at every width, since the trailing slot is still a fixed size. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tag picker, name field and verified chip all drew their own border and fill, which stacked three boxes on top of the cosmetic background the bar already sits on. All three are transparent now, tinting only on hover and while the picker is open, with a malibu-blue focus ring where the border used to indicate focus. The two trailing controls keep their button chrome, since that is what marks them as pressable. State reads off the content instead of the container: the aquarius check and the VERIFIED pill sit directly after the name, rather than the pill floating at the far edge of a now full-width field. Losing the borders grows each field's content box from 42 to 44px (40 to 40 below sm), so the shared leading moves with it — content box and computed line-height still agree in both states, which is what keeps the name from hopping when verified is toggled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…owse tab Three fixes. The name still moved a pixel when toggling verified. Measuring the reported screenshots against the TAG label — which sits in an untouched control, so it references the two crops to each other — put the verified name exactly 1px high. Pinning the leading to the content box, the previous attempt, was the wrong direction: an <input> sizes its editor box from the font's own metrics, so the span only agrees with it at `line-height: normal`. Sweeping three fonts and 17 sizes, the span drifts a whole pixel from the input in 21/51 combinations at a fixed 44px leading and 24/51 at 1.2, against 0/51 with this change. Joining or leaving a clan invalidates the cached /users/@me and announces itself, but dispatches no fresh userMeResponse, so the picker kept listing whatever clans the player had at page load. It now refreshes on those events and when the picker opens, and drops the selected tag when that clan is left, the same way a server-side rejection does. "Browse clans" only called showPage, which opens the modal on its default my-clans tab rather than the one the action is named after; it now passes the tab explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It was a pencil below sm and a "Change" label above it; now it is a square pencil button throughout, with the label carried by title and aria-label. The name beside it already says what would be changed. That drops the reserved trailing slot with it. The slot existed so the two differently sized buttons couldn't resize the name field, back when the field had a visible border and the row was centred — a resize moved the name. The field is transparent now, left-aligned, and everything in it hugs the leading edge, so only an invisible right edge moves and just the active button need exist. Measured across the toggle at 1280px and 420px: the name's ink is unmoved in both axes while the trailing control goes 127x44 to 44x44. username.verified_use_custom_short is dropped, having no remaining use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The check mark already carries the state, sits next to the name, and is the labelled element for assistive tech; the pill repeated it. Removes the now-unused username.verified_toggle string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Free-form names allowed 27 characters against the account name's 20, so the two didn't line up. MAX_USERNAME_LENGTH is now 20 and validateUsername enforces it. UsernameSchema deliberately keeps its old 27 — it gates ClientJoinMessageSchema, so the server validates every join against it, and tightening it would refuse a client still running an older build. Names already stored, or supplied by CrazyGames, are trimmed to the cap on the way in rather than rejected, since an over-long name would fail validation and block play through no action of the player's. A Steam persona is still rejected outright rather than truncated to a stub, per the existing seeding test. The tag picker gives up 16px — a W at this size — to the name: the tag text drops to text-base at all widths and the control to a flat 8rem, which still fits WWWWW. A 20-character name now renders in full at 1280px wide. Also handles two review findings: clan-disbanded, which a leader dissolving their own clan emits instead of clan-left, now clears the tag the same way; and the ownership check re-runs after a membership change, since the "not a member" error links into the clan modal, so joining is the usual way out of it — previously the error kept play disabled and clanCheck kept resolving null until the tag was edited by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings, and the wire cap. getUserMe caches false for a transient failure — network error or non-200 — as readily as for a real sign-out. The background refresh assigned that straight through, so a clan action taken while the API hiccuped turned verified play off and emptied the picker for the rest of the session, with the cached false leaving no way back. That refresh now only ever replaces the snapshot with a better one; an explicit userMeResponse still reports a real sign-out. Membership also changes server-side — kicked by another member, or a pending request approved — and nothing invalidates this tab's cached profile when it does, so refreshing on open just re-read the page-load snapshot. Opening the picker now drops the cache first. UsernameSchema drops to 20 alongside MAX_USERNAME_LENGTH, so the wire carries the same bound as the form rather than tolerating the old 27. The tag control gives up another 28px — 7.25rem now, against 9rem before this pass — from tighter padding, a smaller chevron and normal letter spacing. WWWWW still fits exactly (90px of glyphs in 90px of box). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trims the comments added across this branch to one or two lines each — enough for the non-obvious constraints, without restating the code. Two review findings on refreshUserMe. A 401 makes getUserMe log out and return false, and it dispatches no userMeResponse, so treating every false as transient kept the old verified name and clan list alive until a reload. The local session tells the two apart without a network call: false from userAuth(false) means getUserMe's logOut() already cleared the JWT. An uncached picker refresh and a clan-event refresh can also overlap, and the older one settling last reinstated the membership the newer one had just corrected. Both now carry a generation, as the ownership check already did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts UsernameSchema to .max(27). The 20-character cap belongs on the form, not on the wire: this schema backs PlayerSchema, so it also parses names already written into archived GameRecords, and narrowing it doesn't rewrite those records — it makes them unparseable. Three confirmed consequences, each permanent for any game whose player list holds a 21-27 character name: JoinLobbyModal parses the archive before the gitCommit check, so a failure returns version_mismatch and never reaches the versioned-shell fallback that makes cross-version replays work; GamePreviewBuilder parses the same payload, so share links 302 to home; and Worker closes a join with 1002, which no client-side clamp can help. Clients updating doesn't fix any of it, since the records were written under the old rules. The schema also now accepts hyphens, which AccountUsernameSchema has always allowed. Verified play submits the account name and skips free-form validation, so a subscriber named cool-guy got an enabled Play button and a 1002 close. Widening is the safe direction; a test asserts every account name stays representable, discriminator included. getUserMe now dispatches userMeResponse when a 401 logs it out. That is the signal the identity bar was missing: it can't tell an expired session from a transient failure, and the userAuth(false) probe it used instead was wrong — Auth also reports false within three minutes of expiry, which would have dropped a verified player to their free-form name. Fixes the clan-draft test, which set the input's value itself and passed with the fix removed. The bug needed the bound value to change between renders, so it types a character at a time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The /users/@me 401 branch was the wrong place for this. When the access token expires and /auth/refresh answers non-200, Auth logs out and userAuth returns false, so getUserMe returns before that branch and the identity bar kept the old verified name and clan list against a cleared session. Two more paths in Auth, and eight more logOut calls in Api, had the same gap. Moves the dispatch to clearLocalSession, which every one of them runs through. Only fires when a session was actually in place, so a guest clearing on each failed refresh doesn't churn every consumer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0aded6b to
ea975ed
Compare
Retaining the last good snapshot wasn't enough: checkClanTagOwnership calls getUserMe itself, so against the cached false left by a failed refresh it takes the player for a member of nothing and reports their own clan as "not a member" — disabling play until some later invalidation happens to succeed. Opening the picker while the API was unreachable was enough to trigger it, since the public clan endpoint answering is what turns the empty membership into a rejection. refreshUserMe now reports whether it was conclusive, and the check only re-runs when it was. A superseded refresh counts as inconclusive: the newer one runs the check itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two follow-ups. Selecting a clan from the picker still asked the API whether the player owned it, and after a failed refresh that reads getUserMe's cached false — rejecting the very clan the player just picked from their own list. The check now answers from the membership already in hand, which is the same source it would have consulted, and skips the request entirely. The server still re-checks the tag at join. Dispatching userMeResponse from clearLocalSession reached components but not Main's onUserMe, which is where the nav button, its cached profile and window.adsEnabled are updated — so a background 401 could leave the nav showing the expired account and ads disabled for someone who is no longer a subscriber. Auth now emits session-cleared and Main answers it by running the same no-session path it runs at startup, which broadcasts userMeResponse itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two follow-ups. A /users/@me request issued before a logout can still be in flight when the session goes, and its 200 was fetched with a JWT valid at the time. Applying it afterwards put the expired account back in the nav and re-enabled the ad-free state. Responses now carry the generation of the session they were fetched under, and a logout bumps it. Widening UsernameSchema with hyphens — so verified account names stay representable on the wire — also widened the free-form name, since validateUsername delegated its character check to that schema. Players could type foo-bar despite the error message naming only letters, numbers, spaces and underscores. The form's charset is now its own rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A 401 on any authenticated endpoint clears the session, but getUserMe answers from its cache before it checks authentication, so the profile fetched under the old session was still handed out afterwards — a consumer reading it straight after the logout broadcast would show the expired account and its entitlements. Invalidated from Api, next to the cache, rather than from Auth (which can't import this module) or Main's handler (which only runs where Main has initialised). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem
Three rough edges in the play-page identity bar:
lg:has-[.streaming-live]never matches), leaving a mostly-empty box hundreds of pixels wide.Changes
Clan tag picker. The text field becomes a picker. Members choose from
player.clans(tag + name, tick on the active one); the menu also carries free-text entry,No tag, and aBrowse clanslink, so signed-out players keep the old path. Closes on Escape, outside pointerdown, or selection. The control is a fixed 7.25rem so the bar never reflows when a tag is set or cleared, sized so a full-lengthWWWWWstill fits exactly.Verified name. Renders as the name followed by the shared blue check — the same mark and order used in lobby lists, the profile modal and in game — with a pencil button to switch back. The off state is a labelled
Use verifiedbutton; the subscription upsell path is unchanged.Flat chrome. The tag picker, name field and verified chip are all transparent with no borders, tinting only on hover and while the picker is open, with a
malibu-bluefocus ring. The two trailing controls keep button chrome, since that is what marks them pressable. The name field takes whatever those leave, so the row always fills the strip.20-character cap. Free-form names allowed 27 against the account name's 20.
MAX_USERNAME_LENGTHis now 20, enforced invalidateUsernameand the input'smaxlength. Names already stored, or supplied by CrazyGames, are trimmed to fit rather than rejected — an over-long name would otherwise block play through no action of the player's. A Steam persona is still rejected outright rather than truncated to a stub, per the existing seeding test.UsernameSchemadeliberately keeps.max(27). It backsPlayerSchema, so it also parses names already written into archivedGameRecords, and narrowing it doesn't rewrite those records — it makes them unparseable. That would permanently dead-end replay links for any game holding a longer name (JoinLobbyModalparses the archive before the gitCommit check, so a failure never reaches the versioned-shell fallback), 302 its share preview to home (GamePreviewBuilder), and close joins with 1002 (Worker). The schema does now accept hyphens, whichAccountUsernameSchemahas always allowed: verified play submits the account name and skips free-form validation, so a subscriber namedcool-guypreviously got an enabled Play button and a socket close.Bugs found and fixed along the way
/users/@meand announces itself, but dispatches no freshuserMeResponse, so the picker kept listing whatever clans the player had at page load. It now refreshes on all three events, drops the selected tag when that clan is lost, and re-runs the ownership check — the "not a member" error links into the clan modal, so joining is the usual way out of it, and both the error andclanCheckused to stick until the tag was edited by hand.getUserMecachesfalsefor a transient failure as readily as for a sign-out, so a clan action during an API hiccup turned verified play off and emptied the picker for the session. A background refresh now only ever upgrades the snapshot;getUserMedispatchesuserMeResponsewhen a 401 logs it out, so a real expiry is still handled.clanCheckGen.Browse clansopened the wrong tab —showPagealone lands on the default my-clans view.<input>sizes its editor box from font metrics while a span centres a line box; they only agree atline-height: normal. Sweeping three fonts and 17 sizes, a fixed leading drifted a whole pixel in 21/51 combinations against 0/51 with this change.userMeResponsecan fire before this element connects, leaving the picker and toggle empty; the cached profile is read back on connect.Testing
tests/UsernameInput.test.ts— 23 tests: clan listing, selection, clearing, the draft-field regression, dismissal, membership events, refresh failure and ordering, the verified swap and custom-name round-trip, and the length clamp.tests/UsernameValidation.test.ts— 12 tests covering the cap, the wire schema staying readable, and every account-name shape survivingUsernameSchema.tests/ApiUserMeLogout.test.ts— announce-on-401, stay-quiet-on-503.tsc --noEmitandprettier --checkclean.i18n
New
username.*keys added toresources/lang/en.jsononly; no other translation files touched.🤖 Generated with Claude Code