PURPOSE

Account center screen combining player identity (avatar, display name, badges, ID, email), a guest-to-claimed conversion CTA, dev convenience tooling (unlock all, reset all, +50K gems), an admin tools entry point, and sign-out. Renders inside V32Shell using the medical-UI design system (MedPanel cards, .med-btn variants, Cal Sans headlines, Space Grotesk body) per directive 2026-05-13T08:15Z (decisions/20260513-001). Ticket 77 medical-UI rollout, PR 5/5.

OWNS

  • Local UI state via useState: showClaim, editingName, nameInput, nameError, savingName, copied, unlocking, confirmUnlock, confirmReset.
  • Inline display-name edit flow (input + save/cancel buttons with validation: 1-24 characters).
  • ID-copy interaction with 2-second “Copied!” feedback timer.
  • Paired confirm-then-act flow for both UNLOCK ALL and RESET ALL (each shows a “PERMANENTLY ALTER ACCOUNT?” prompt with YES/NO buttons before executing).
  • The render structure: player card, conditional claim CTA, unlock/reset row, +50K gems row, conditional admin tools button, sign-out.

READS FROM

  • usePlayerStore — selects profile and signOut action; profile drives identity rendering, guest/admin gating (profile.accountState === 'guest', profile.role === 'admin').
  • react-router-dom useNavigate — used for / (post-sign-out) and /admin navigation.
  • navigator.clipboard — used by handleCopyId to write profile.id.

PUSHES TO

  • updateDisplayName (from ../services/profileService) — persists the trimmed display name.
  • unlockEverything and resetEverything (from @starship-survivors/services/unlockService) — dev account mutations.
  • invokeRpc (from ../services/supabase) — calls the grant_dev_currency Supabase RPC with { p_currency: 'gems', p_amount: 50000 }; the returned wallet snapshot is pushed into useWalletStore via replaceFromSnapshot.
  • usePlayerStore.signOut — invoked by handleSignOut, followed by navigate('/').
  • Navigation to /admin via navigate('/admin') for the admin tools button.
  • Renders ClaimAccountModal when showClaim is true.

DOES NOT

  • Does not own or render cosmetics, equipped ship, or inventory grids — the file notes a cosmetics grid is intentionally not present yet.
  • Does not manage wallet balances directly beyond replacing them with the RPC snapshot.
  • Does not perform routing guards or authentication checks; assumes profile is available and shows a “Loading profile…” placeholder otherwise.
  • Does not throttle, batch, or rate-limit the unlock/reset/grant calls beyond the in-flight unlocking flag and confirm-step gating.
  • Does not log errors to Sentry — failures from unlock/reset/grant are caught and console.error-ed only.
  • Does not validate display-name content beyond length (1-24 characters after trim).

Signals

  • Player card visual state switches between read mode (name + edit pencil) and edit mode (input + save/cancel) driven by editingName.
  • GUEST vs CLAIMED badge color: amber (var(--med-amber)) for guests, status-good for claimed.
  • ADMIN badge appears only when profile.role === 'admin', in var(--med-status-bad) red.
  • Claim CTA button (CLAIM ACCOUNT — SAVE YOUR PROGRESS) is rendered only for guest accounts.
  • Unlock button labels: UNLOCK ALL idle, UNLOCKED! while unlocking is true (button disabled).
  • Reset and unlock confirm panels share the same ⚠️ PERMANENTLY ALTER ACCOUNT? copy but color-code by destructiveness (amber for unlock, status-bad for reset).
  • The +50K gems button uses var(--med-magenta) for its border and text.
  • Admin tools button is conditional on isAdmin.
  • Sign-out is rendered last as a ghost variant button.

Entry points

  • Default named export ProfileScreen (function component) — mounted by the metagame router for the profile route inside V32Shell.
  • ClaimAccountModal is mounted as a portal-like overlay sibling to the main flex column when showClaim is true.

Pattern notes

  • Side-by-side dev tools row uses two equal-flex children (flex: 1) for unlock/reset; each child swaps its content between an action button and a MedPanel variant="flat" confirmation panel based on the corresponding confirm* state.
  • The +50K gems row is implemented as an array map over a single-element as const tuple [{ currency: 'gems', label: '💎' }] — structured to accept additional currencies without restructuring.
  • Inline-styled buttons use design-token CSS variables (--med-space-*, --med-radius-*, --med-amber, --med-magenta, --med-status-bad, --med-status-good, --med-border, --med-panel-recessed) rather than hardcoded values.
  • Imports ../screens/collection/collection.css for shared styling consumed elsewhere in collection screens.
  • Avatar is a literal rocket emoji 🚀 at 28px inside a recessed 48x48 square — cosmetic art is not yet wired.
  • Error handling pattern is uniform: try { await op() } catch (e) { console.error(...) } with no user-facing surface for failures other than the in-line nameError for display-name updates.
  • Profile loading guard uses non-null assertion (profile!.id) inside callbacks after the early-return on !profile, relying on the closure capture pattern.