PURPOSE
Admin/debug tooling screen for beta management. Surfaces three destructive/privileged operations against the cloud save state: granting gems to any player, resetting pity counters on banners, and wiping a player’s cloud save. All operations are gated behind server-side admin role checks and are unreachable for non-admin profiles (the screen renders an “Admin access required” notice if the local profile lacks the admin role).
OWNS
- Local form state for three independent admin actions (grant gems, reset pity, wipe save), each with its own target input, busy flag, and result badge.
- A
resolveTargethelper that maps blank input or the literal stringselfto the current admin’s player ID, otherwise passes the trimmed input through unchanged. - A local
ResultBadgecomponent that renders success/error feedback inline below each action button. - A local
stylesrecord holding the screen’s inline CSS (title, card chrome, input rows, action button, hint text). - The visual layout for the admin panel (player ID readout card plus three action cards) inside the standard shell.
READS FROM
usePlayerStore— selectsprofileto read the current player’sidandrole. The screen rejects rendering its tools unlessprofile.role === 'admin'.
PUSHES TO
invokeRpcfrom../services/supabase— calls three Supabase RPC endpoints:admin_grant_gemswithp_target_player_idandp_amount.admin_reset_pitywithp_target_player_idandp_banner_id(null for all banners).admin_wipe_savewithp_target_player_id.
- All RPC calls re-verify the caller’s admin role server-side; the screen does not bypass server authorization.
DOES NOT
- Does not navigate to or from other screens; routing into the admin screen is gated by
ProfileScreenshowing the link only to admin profiles. - Does not modify the player store, profile, or any local cache; results are reflected in the cloud and re-fetched by other systems as needed.
- Does not validate target player IDs beyond trimming and substituting
self; the server enforces existence and authorization. - Does not log telemetry, post to Discord, or write to Supabase tables directly outside the three admin RPCs.
- Does not poll, subscribe, or refresh after an action; result feedback is one-shot per button press.
- Does not render anything game-side (no canvas, no engine state).
Signals
- Success and error results are surfaced via the inline
ResultBadgecomponent using green (#34d399) for success and red (#f87171) for error, with truncated target IDs (first 8 chars) in the message. - Each action sets its own
*Busyflag while the RPC is in flight, swapping the button label to a progress state (GRANTING...,RESETTING...,WIPING...) and disabling the button. - The wipe action button uses a distinct red palette and the label
WIPE SAVE (DESTRUCTIVE)to mark it as the highest-risk operation. - The admin-gate path renders a red “Admin access required” message inside the shell when
profileis missing orprofile.role !== 'admin'.
Entry points
- Default export is
AdminScreen(named export). Rendered by the metagame screen router when the player hasprofile.role === 'admin'and navigates fromProfileScreen. - No props; the screen reads everything it needs from
usePlayerStore. - Wrapped in
V32Shell(from../components/V32Shell) for consistent chrome with the rest of the metagame screens.
Pattern notes
- Three structurally identical async handlers (
handleGrantGems,handleResetPity,handleWipeSave) each follow the same pattern: set busy → clear prior result → resolve target → callinvokeRpc→ set success or error result → clear busy infinally. The duplication is intentional and matches the “three similar lines is fine, no premature abstraction” project rule. resolveTargetis the only shared input-preprocessing helper; numeric parsing forgrantAmountis inlined in its handler with a manualisNaN/< 1rejection.- Error messages are
err instanceof Error ? err.message : 'Failed'— surfaces the server error string when present, falls back to a generic label. - Inline styles are declared once in a module-scope
stylesrecord cast toRecord<string, React.CSSProperties>; the screen does not depend on a shared theme module. - The screen uses non-emoji ASCII labels everywhere except the title heading, which prefixes
ADMIN TOOLSwith a shield glyph as a visual marker. - The wipe button overrides
actionBtnbackground and color via spread to mark the destructive variant rather than introducing a new style key.