Skip to content

feat(username): clearer verified name and a clan tag picker - #4983

Open
ryanbarlow97 wants to merge 23 commits into
mainfrom
feat/username-entry-polish
Open

feat(username): clearer verified name and a clan tag picker#4983
ryanbarlow97 wants to merge 23 commits into
mainfrom
feat/username-entry-polish

Conversation

@ryanbarlow97

@ryanbarlow97 ryanbarlow97 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Three rough edges in the play-page identity bar:

  • Verified name is unclear. Playing under a verified account name showed only as a disabled input with blue text, plus a small "Verified" toggle pinned to the far right of a very wide bar — the two never read as one thing, and a disabled field reads as broken.
  • The bar stretches. With no live streamers the identity strip spans the whole column (lg:has-[.streaming-live] never matches), leaving a mostly-empty box hundreds of pixels wide.
  • Picking a clan is guesswork. The tag was a bare text field: you had to recall and type your tag, and only learned it was wrong once the ownership check came back "not a member".

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 a Browse clans link, 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-length WWWWW still 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 verified button; 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-blue focus 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_LENGTH is now 20, enforced in validateUsername and the input's maxlength. 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.

UsernameSchema deliberately keeps .max(27). It backs PlayerSchema, so it also parses names already written into archived GameRecords, 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 (JoinLobbyModal parses 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, which AccountUsernameSchema has always allowed: verified play submits the account name and skips free-form validation, so a subscriber named cool-guy previously got an enabled Play button and a socket close.

Bugs found and fixed along the way

  • Stale clan membership. Joining, leaving or disbanding 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 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 and clanCheck used to stick until the tag was edited by hand.
  • Server-side membership changes. Being kicked, or having a join request approved, never invalidates this tab's cache, so opening the picker now drops it first rather than re-reading the page-load snapshot.
  • Failed refresh wiped account state. getUserMe caches false for 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; getUserMe dispatches userMeResponse when a 401 logs it out, so a real expiry is still handled.
  • Overlapping refreshes. An uncached picker refresh and a clan-event refresh can be in flight together, and the older settling last reinstated the membership the newer had corrected. Both now carry a generation, matching the existing clanCheckGen.
  • Browse clans opened the wrong tabshowPage alone lands on the default my-clans view.
  • Escape didn't close the picker when focus was on the trigger, which is the menu's sibling.
  • The name jumped 1px when toggling verified. An <input> sizes its editor box from font metrics while a span centres a line box; they only agree at line-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.
  • The picker's free-text field blanked mid-keystroke when the typed tag matched one of your own clans.
  • userMeResponse can 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 surviving UsernameSchema.
  • tests/ApiUserMeLogout.test.ts — announce-on-401, stay-quiet-on-503.
  • Full suite green (3046 + 316); lint, tsc --noEmit and prettier --check clean.
  • Driven in headless Chromium against the dev server throughout: measurements for the pixel shift, the reflow, and the tag width were taken from real rendered output rather than by eye.

i18n

New username.* keys added to resources/lang/en.json only; no other translation files touched.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 12, 2026 23:02
@ryanbarlow97
ryanbarlow97 requested a review from a team as a code owner August 12, 2026 23:02
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 715a05a2-ed98-46fe-8a9f-6e9754f3a0ed

📥 Commits

Reviewing files that changed from the base of the PR and between abfad6e and e80edc3.

📒 Files selected for processing (1)
  • src/client/UsernameInput.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/UsernameInput.ts

Walkthrough

UsernameInput now provides a menu-based clan selector with custom tag entry, clearing, dismissal, and clan-browser navigation. It also renders verified names as badge controls with custom-name fallback and restoration. English translations and interaction tests were added.

Changes

Username controls

Layer / File(s) Summary
Clan state and interactions
src/client/UsernameInput.ts
Loads cached account data, tracks clan state, supports owned-clan selection and custom tags, handles clearing and dismissal, and synchronizes sanitized input.
Control rendering and translations
src/client/UsernameInput.ts, src/client/components/ui/VerifiedBadge.ts, resources/lang/en.json
Replaces the inline clan field with a menu control. Renders verified-name badges with configurable colors and accessible labels. Adds English strings for clan and verified-name actions.
UsernameInput behavior validation
tests/UsernameInput.test.ts
Tests clan selection, clearing, manual entry, menu dismissal, verified-name toggling, restoration, and ineligible-account fallback.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Mergeability Score: ⚪ Minimal · up to e80ed

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
Loading

Possibly related PRs

Suggested reviewers: evanpelle

Poem

Clans open in a tidy menu,
Badges show names clear and true.
Custom tags can clear or stay,
Verified names guide the way.
Tests check each choice in view.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main changes: improved verified-name display and a clan tag picker.
Description check ✅ Passed The description directly explains the identity bar, clan picker, verified name, validation, bug fixes, and testing changes.

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.

@ryanbarlow97
ryanbarlow97 marked this pull request as draft August 12, 2026 23:03
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 12, 2026
@ryanbarlow97 ryanbarlow97 added the UI/UX UI/UX changes including assets, menus, QoL, etc. label Aug 12, 2026
@ryanbarlow97 ryanbarlow97 added this to the v34 milestone Aug 12, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 12, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 12, 2026
ryanbarlow97 and others added 16 commits August 15, 2026 11:42
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>
chatgpt-codex-connector[bot]

This comment was marked as resolved.

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>
chatgpt-codex-connector[bot]

This comment was marked as resolved.

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>
chatgpt-codex-connector[bot]

This comment was marked as resolved.

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>
chatgpt-codex-connector[bot]

This comment was marked as resolved.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

UI/UX UI/UX changes including assets, menus, QoL, etc.

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

3 participants