Dev tazi 033#38
Conversation
…etry Adds explicit events for matchmaking (started/ai_fallback/human_found/cancelled), match lifecycle (started/completed/forfeit/disconnected/reconnected), per-question answer telemetry from possession matches, auth/onboarding/logout, store flow (viewed/modal_opened/cancelled/avatar_equipped), profile/settings (nickname/club/ language), daily challenge start, and in-app browser blocked. Sets PostHog person properties on identify so cohorts work. Co-Authored-By: swoosh1337 <noreply@anthropic.com>
- AppShell: replace Date.now() in render with 1s-tick state - LocaleContext / useFriendLobbyLogic: defer setState in mount effects via microtask - PossessionMatchViewport: rename hook's `ref` field to `containerRef` and destructure all three values to avoid Compiler's ref-during-render heuristic - ChallengeInvitePrompt / MatchmakingMapScreen: drop useMemo wrappers that React Compiler couldn't preserve (deps array referenced unstable t()) Co-Authored-By: swoosh1337 <noreply@anthropic.com>
- friend_lobby_invite_sent — fires when host copies the lobby code - friend_lobby_invite_accepted — fires when a user joins via invite code - challenge_invite_sent — fires from the social screen challenge button - challenge_invite_accepted / declined — fires from both the notifications dropdown and the floating challenge invite prompt Co-Authored-By: swoosh1337 <noreply@anthropic.com>
Adds a popup/JWT-based Google sign-in path that bypasses the classic OAuth redirect — the redirect endpoint returns `disallowed_useragent` 403 inside in-app browsers (Messenger, Instagram), but the GIS endpoint isn't UA-gated. Flow: 1. Welcome screen loads https://accounts.google.com/gsi/client lazily 2. User taps "Continue with Google" → GIS popup opens with a nonce-bound request to accounts.google.com/gsi/select 3. GIS returns a signed JWT credential + raw nonce 4. We POST { provider, id_token, nonce } to /auth/v1/social-login-token 5. Backend exchanges with Supabase server-side, returns access/refresh Falls back to the old redirect flow if GIS fails to load (CDN blocked) or refuses to render (some webview versions detect the UA and bail). The in-app browser overlay remains the last-resort safety net. Requires NEXT_PUBLIC_GOOGLE_CLIENT_ID env var (Web OAuth client ID from Google Cloud Console with the deployed origin in Authorized JavaScript origins). Co-Authored-By: swoosh1337 <noreply@anthropic.com>
…layout fixes - Countdown matches now show the opponent's REAL live found-count via a new match:opponent_countdown_progress server event. Previously the UI rendered a seeded-RNG simulator that could show a count higher than the opponent's actual answers (you'd see 6 when they only got 4). - For AI opponents, the AI commits answers atomically but the server now drip-feeds the progress events with staggered delays so the human side still sees a realistic counter tick up. - Removed the InAppBrowserOverlay from auth layout so users in Messenger can attempt GIS sign-in instead of being blocked by a fullscreen overlay. - Moved JSON-LD <script> from <head> to <body> + suppressHydrationWarning on <body> to silence the Facebook pcm.js hydration mismatch warning in Messenger's in-app browser. Co-Authored-By: swoosh1337 <noreply@anthropic.com>
`audit-brand-colors.mjs` scans the codebase for raw `bg-[#hex]` / `text-[#hex]` / `border-[#hex]` Tailwind classes and reports them against `brand-colors-allowlist.json`. The existing `no-hex-class-colors.test.ts` CI guard depends on these. `migrate-brand-colors.mjs` rewrites known hex values to brand tokens; `rename-tokens.mjs` handles bulk token renames. Both are dev-only utilities, not part of the runtime bundle.
Nine daily-game screens were using a hardcoded hex equivalent of our `surface-deep` token. Swaps them to the design-system token so the `no-hex-class-colors` regression guard stays green without needing new allowlist entries.
Anonymous visitors used to land on / and immediately get redirected to /auth/welcome, which flashed the URL and split SEO between two paths. Now / is a public WelcomeScreen wrapped in PublicOnlyGate (logged-in users get bounced to /play), and /auth/welcome 301-redirects to / so existing links and the sitemap entry don't break. Updates every internal reference to /auth/welcome: - AppAuthGate redirects anonymous users to / instead of /auth/welcome - AppShell logout, OAuthCallback fallback, forgot-password back button - sitemap drops the /auth/welcome entry (was 0.9 priority duplicate) - removes the dead sticky Play button in AppShell that briefly flashed during navigation because it was gated on currentPath === "/"
Methodical sweep — every entry had zero non-test consumers in src/: - entire src/features/home/ tree (HomeScreen, HomeDashboard, all HomeRightRail/HomeQuickStatsRow/HomeFeaturedChallenge/etc.) - training-offer modal + the commented-out training-gate logic that referenced it from play/page.tsx - orphan components: Timer, HelpButtons, LanguageSelector, both bottom sheets, InlineAvatar, AnswerCard, QuestionArena, CategoryDraftPanel, ResultsShared, WalletSection, RewardQuestCard, ShotOverlay, DevToolbar, IntroScreen, PregameOverlay, PenaltyTransition - unused hooks/utils: features/play/hooks/useDraftLogic.ts, utils/gameHelpers.ts, utils/rankSystem.ts - unused barrels: src/features/daily/index.ts (consumers all import per-file), src/lib/sounds/index.ts (consumers go file-by-file)
`MatchCountdownPuck` consolidates the cyan-border + brand-blue-fill countdown puck used by the kickoff overlay, party-quiz start, and reconnect resume. Three sizes (sm/md/lg), optional yellow progress bar via `durationMs`. Replaces three near-identical implementations. `KickoffCountdownOverlay` now delegates the center column to the shared puck so future tweaks land in one file. `/dev/timers` is rebuilt as a labeled gallery grouped by mode (Ranked, Party Quiz, Daily, Shared) covering all 8 timer surfaces: kickoff, MCQ pill, party MCQ pill, penalty HUD, shot HUD, daily countdown, reconnect grace, forfeit, and a "match complete / calculating" preview. Plus a section that renders the shared puck at all 3 sizes for side-by-side visual comparison.
Results screen - adds personal stats row (Rank / Correct / Points) + XP footer with level-up + xp-to-next, mirroring the ranked results layout - bumps podium rank pill from h-6 to h-10 and name/score sizes so they read at a glance in Georgian (the visible bug — small 1/2 chips) - pending "Returning to lobby…" full-screen LoadingScreen when Play Again is tapped, so the user gets feedback while the server emits fresh lobby state In-match - first-question intro: round transitions only fire after a round resolves, so question 1 used to skip the overlay. New `firstQuestionIntroVisible` one-shot triggers on qIndex===0 - finalizing-results modal: royal-blue Figma style (was border-card with dim font-fun) - forfeit-pending banner + pause overlay: royal-blue Figma style - match-start countdown puck now uses the shared MatchCountdownPuck - RoundTransitionOverlay category label: text-[11px] text-white/55 → text-[16-20px] text-brand-yellow font-bold for legibility i18n - new partyResults.statRank / statCorrect / statPoints + returningToLobby - new results.levelUp for the "Level up! Now Lvl N" line - ka.partyQuizFinished: "Party quiz" → "ფარტი ქვიზი" for consistency
Both in-match overlays moved from `border-surface-deep` rounded-2xl cards with `font-fun text-white/60` text to solid `bg-brand-blue rounded-[20px]` Poppins SemiBold cards, matching the QuitMatchModal / Play With Friend / Match Paused / Calculating Final Scores family. Reads as one consistent modal language across the app.
…a buttons
Ready button used to wait for the server `lobby:state` echo before
flipping; on flaky networks it felt unresponsive. Adds
`optimisticReady` in useFriendLobbyLogic so the click immediately
reflects the intended value and clears once the server confirms.
Non-host members previously saw no UI between "host tapped Start" and
the match-start countdown. Now the "preparing match" banner reads
from `matchIsStarting = isStartingMatch || lobby.status === "active"`
so every member lights up (backend now broadcasts the status flip).
Visual:
- start match: now `bg-brand-green` (solid Figma green) instead of
`bg-brand-blue`, hover → `brand-green-deep`
- leave lobby: now fully filled `bg-brand-red` (Figma node 1155-3037
reference) instead of outlined
- ready checkmark: size-5 → size-7 with shrink-0 and `min-h-14`
wrapper so long Georgian copy ("მზადაა (დააჭირე გასაუქმებლად)")
wraps cleanly without stranding the icon
- all buttons get `active:scale-[0.98]` press feedback
FriendPlayModal "Room Code | Join" used a rigid 3-col grid (2/3 input, 1/3 button). Georgian "შემოერთება" overflowed the 1/3 column. Switches to flex so the button auto-sizes to text content with shrink-0 + whitespace-nowrap, while the input takes the remaining space via flex-1. ModeSelectionScreen mobile ranked card title had `whitespace-nowrap` which forced "რეიტინგული მატჩი" to overflow into the RP score on the right. Drops nowrap, adds `leading-[1.05] break-words [hyphens:auto]` so it wraps cleanly on two lines.
…on phase Backend now sends `correctIndex` on MatchQuestionPayload (was already in the cache, just unsurfaced). Store merges it into the per-question slot, useRealtimeGameLogic prefers it over the older sources, and PossessionQuestionPanel only switches a non-selected option to the "reveal-correct" style when `phase === 'reveal'`. Without the phase gate, if `correctIndex` arrived before the user answered (which now happens reliably for the picker), the correct option would visually highlight as soon as the user picked a wrong one — leaking the answer mid-round.
Two CR findings on realtimeMatch.store.ts: setFinalResults — `trackMatchCompleted` could fire for a stale matchId when a final-results payload arrived after the store had moved on to a new match (e.g. on rejoin). Now gates the analytics call on `state.match?.matchId === payload.matchId`. clearMatchPaused — the previous downtime calculation was algebraically `Date.now() - (state.pauseUntil - remainingMs)` which simplifies to 0 in every case, so reconnect telemetry was always reporting 0s pauses. Adds `pausedAt: number | null` to the state, stamps it in setMatchPaused, and computes downtime as `now - pausedAt`. Also clears pausedAt in all reset paths.
…strings CR flagged hardcoded English in RealtimePartyQuizScreen for the in-match standings label, the "Live scores" caption, and the new "Match Complete / Calculating final scores…" finalizing overlay. Adds partyResults.standings / liveScores / matchComplete / calculatingFinalScores keys (en + ka) and wires them through useLocale().t(...) so Georgian players don't see English fragments inside an otherwise localized screen.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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:
📝 WalkthroughWalkthroughAdds GIS social-login and server token endpoint, broad analytics and PostHog hooks, realtime opponent countdown events and store fields, party-quiz score-flight animations and results XP UI, possession/countdown UI refactors, color-audit/migration CLIs, landing/routing/layout edits, translations, and tests. ChangesUnified application changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant NextApp
participant RealtimeStore
participant Socket
participant Analytics
Client->>NextApp: GIS sign-in (signInWithGoogleIdentity)
NextApp->>NextApp: POST /api/v1/auth/social-login-token (socialLoginWithIdToken)
NextApp->>Analytics: setPersonProperties / trackLoginCompleted
Socket-->>RealtimeStore: 'match:opponent_countdown_progress' events
RealtimeStore->>Client: opponentCountdownFoundCount -> MatchCountdownPuck / score flights
Client->>Analytics: trackMatchStarted / trackMatchResultsViewed
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
The hand-rolled useEffect-based intro had two bugs that produced the weird sequence you saw: 1. It started with `firstQuestionIntroVisible = false` and only flipped true via a useEffect when qIndex === 0 arrived. That left a brief render frame where the question + options were already in DOM before the overlay mounted — hence the split-second flash. 2. It only gated the overlay opacity; the underlying question-phase timer in useRealtimeGameLogic still advanced normally, so even with the overlay on top the panel was animating into its "playing" phase underneath. Switches to `usePossessionFirstQuestionIntro` (the hook the ranked / possession match already uses successfully) and passes its value as `blockReveal` to useRealtimeGameLogic. The hook defaults to true on mount, waits for the kickoff countdown to finish, then runs for FIRST_QUESTION_INTRO_MS (2s) — and blockReveal stops the question timer from advancing during that window, so there's nothing to flash.
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/app/(fullscreen)/dev/party-quiz/page.tsx (1)
280-307:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove explicit
anyfrom the dev socket + mock auth user
src/app/(fullscreen)/dev/party-quiz/page.tsxhas explicitanydespitestrict: trueintsconfig.json:
- Stub socket: lines 281–282 (
const stub: any = { ... }+ eslint disable)- Mock auth user: lines 382–383 (
} as any+ eslint disable)Type the stub/socket and user shape explicitly (or use
unknown+ guards) and drop theno-explicit-anydisables so the file stays strict-TS clean.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`(fullscreen)/dev/party-quiz/page.tsx around lines 280 - 307, The stub socket currently uses an explicit any and disables the rule; update createStubSocket to return a properly typed Socket-like interface (or a named DevStubSocket type) that declares id, connected, active, auth, emit, on, once, off, removeListener, removeAllListeners, connect, disconnect, listenersAny, and listeners with proper parameter and return types (use the onMatchAnswer callback type for the emit payload) and remove the eslint-disable comment; do the same for the mock auth user by declaring an explicit AuthUser (or DevAuthUser) shape and casting the mock object to that type (or validate via unknown + type guards) instead of using "as any" so the file remains strict-TS clean.src/features/game/components/MatchmakingMapScreen.tsx (1)
1267-1293:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMemoize
opponentPinto prevent repeated zoom/logging churn
opponentPinis recreated on every render via the IIFE, and it’s used in the “Zoom to opponent when found” effect and the “Matchmaking opponent location resolved” effect (both depend onopponentPin), plusmapPlayers’useMemo. That can retrigger animations/work when unrelated state changes.Suggested fix
- const opponentPin: FakePlayer | null = (() => { + const opponentPin = useMemo<FakePlayer | null>(() => { if (!rankedFoundOpponent) return null; const resolved = resolveOpponentLocation(rankedFoundOpponent, t("matchmaking.unknownCity"), t("matchmaking.unknownCountry")); const [oppX, oppY] = projectPoint(resolved.lon, resolved.lat); return { id: 10000 + (hashString( rankedFoundOpponent.id ?? rankedFoundOpponent.username ?? "opponent", ) % 1000), lon: resolved.lon, lat: resolved.lat, x: oppX, y: oppY, color: "`#FF4B4B`", avatarUrl: rankedFoundOpponent.avatarUrl ?? getAvatarAsset("blue"), avatarCustomization: rankedFoundOpponent.avatarCustomization ?? null, name: rankedFoundOpponent.username || t("matchmaking.opponentFallback"), flag: resolved.flag, city: resolved.city, country: resolved.country, delay: 0, source: resolved.source ?? "resolved", }; - })(); + }, [rankedFoundOpponent, t]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/game/components/MatchmakingMapScreen.tsx` around lines 1267 - 1293, opponentPin is recreated every render via the IIFE which causes the "Zoom to opponent when found" and "Matchmaking opponent location resolved" effects (and mapPlayers useMemo) to retrigger; wrap that creation in a useMemo to stabilize the object. Specifically, replace the IIFE that defines opponentPin with a React.useMemo that returns null if rankedFoundOpponent is falsy, otherwise runs resolveOpponentLocation and projectPoint and builds the same object; include all inputs used to compute the pin in the dependency array (rankedFoundOpponent, t, resolveOpponentLocation/projectPoint/getAvatarAsset/hashString references if they can change) so the pin only updates when relevant data changes.src/features/party/RealtimePartyQuizScreen.tsx (1)
833-858: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy liftAlign game UI classes with tokenized game-style guidelines.
These changed overlays use arbitrary pixel radii (
rounded-[20px],rounded-[28px]) andfont-poppinsin core game UI. The repo guidelines requirerounded-2xl/rounded-3xl, chunky 3D border treatment, andfont-funfor game typography.As per coding guidelines, “Use
font-funclass for game typography”, “Use chunky 3D borders pattern withborder-b-4 border-surface-card-deepon cards, buttons, and badges”, and “Use rounded corners ofrounded-2xlorrounded-3xlfor game elements”.Also applies to: 1028-1037
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/party/RealtimePartyQuizScreen.tsx` around lines 833 - 858, The overlay components (the forfeit/finalizing overlay and the pause overlay rendered in the motion.div blocks around forfeitPending/forfeitPendingTitle and the one keyed "party-pause" using state.matchPaused and pauseSeconds) use arbitrary radii and font classes; update them to follow game-style tokens: replace rounded-[20px]/rounded-[28px] with rounded-2xl or rounded-3xl as appropriate, swap font-poppins for font-fun on the text container, and add the chunky 3D border classes (border-b-4 border-surface-card-deep) to the overlay container divs so they match the repo guidelines.
🧹 Nitpick comments (10)
src/app/(fullscreen)/dev/timers/page.tsx (1)
186-222: 🏗️ Heavy liftAlign newly added timer UI with the game design-system classes.
New blocks/buttons are using
font-poppins,rounded-[...]/rounded-xl, and non-chunky border patterns. Please switch these tofont-fun,rounded-2xl|rounded-3xl, andborder-b-4 border-surface-card-deepwhere applicable to match the required game UI primitives.As per coding guidelines, "
**/*.{ts,tsx}: Usefont-funclass for game typography (Nunito font family)", "Use chunky 3D borders pattern withborder-b-4 border-surface-card-deepon cards, buttons, and badges", and "Use rounded corners ofrounded-2xlorrounded-3xlfor game elements."Also applies to: 257-317, 511-515, 574-575
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`(fullscreen)/dev/timers/page.tsx around lines 186 - 222, Replace the game UI utility classes in the new timer components to match the design-system: in the Pause preview block and FinalizingResultsPreview component (look for PreviewFrame usage, the motion.div with key={`finalizing-${runId}`}, TimerMeta and any elements using useRemainingMs), swap font-poppins -> font-fun, change any rounded-[...] or rounded-xl to rounded-2xl or rounded-3xl as appropriate, and add the chunky 3D border classes (border-b-4 border-surface-card-deep) to card/button/badge containers (e.g., the root motion.divs and status badges). Apply the same substitutions in the other affected ranges mentioned (around lines 257-317, 511-515, 574-575) so all timer UI uses font-fun, rounded-2xl/3xl, and border-b-4 border-surface-card-deep consistently.src/components/game/RoundTransitionOverlay.tsx (1)
116-116: ⚡ Quick winUse
font-funfor this game overlay label.The updated category label switched to
font-poppins, which conflicts with the typography rule for game UI text.♻️ Suggested class update
- className="font-poppins text-[16px] sm:text-[18px] md:text-[20px] font-bold uppercase tracking-[0.22em] text-brand-yellow mb-4" + className="font-fun text-[16px] sm:text-[18px] md:text-[20px] font-bold uppercase tracking-[0.22em] text-brand-yellow mb-4"As per coding guidelines, "Use
font-funclass for game typography (Nunito font family)".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/game/RoundTransitionOverlay.tsx` at line 116, The category label in the RoundTransitionOverlay component is using "font-poppins" instead of the required game typography; update the className on the label inside the RoundTransitionOverlay component (the element with className="font-poppins text-[16px] sm:text-[18px] md:text-[20px] font-bold uppercase tracking-[0.22em] text-brand-yellow mb-4") to use "font-fun" in place of "font-poppins" so the overlay follows the Nunito-based game font rule.src/components/auth/PrivacyPolicyScreen.tsx (1)
84-84: ⚡ Quick winApply the required chunky 3D border pattern to the updated button/card.
The updated button and primary content card omit the required border treatment for interactive/game UI surfaces.
♻️ Suggested class updates
- className="gap-2 rounded-full bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20 hover:text-white" + className="gap-2 rounded-full border border-surface-card-deep border-b-4 bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20 hover:text-white" - <div className="rounded-[24px] bg-surface-card/40 backdrop-blur-sm p-6 md:p-10"> + <div className="rounded-[24px] border border-surface-card-deep border-b-4 bg-surface-card/40 backdrop-blur-sm p-6 md:p-10">As per coding guidelines, "Use chunky 3D borders pattern with
border-b-4 border-surface-card-deepon cards, buttons, and badges".Also applies to: 101-101
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/auth/PrivacyPolicyScreen.tsx` at line 84, The button and primary content card in the PrivacyPolicyScreen component are missing the required chunky 3D border; update the relevant JSX elements in PrivacyPolicyScreen (the element using className="gap-2 rounded-full bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20 hover:text-white" and the main content card element) to include the classes border-b-4 and border-surface-card-deep so they follow the "chunky 3D borders" pattern for cards, buttons, and badges.src/components/auth/WelcomeScreen.tsx (1)
933-936: ⚡ Quick winAlign the “Browse all categories” CTA with the shared game button pattern.
This updated CTA should use the required chunky border treatment and guideline corner token.
♻️ Suggested class update
- className="h-14 min-w-[280px] rounded-[20px] bg-brand-green px-10 font-poppins text-base font-semibold uppercase tracking-wide text-white shadow-none transition-colors hover:bg-brand-green/90 hover:shadow-none" + className="h-14 min-w-[280px] rounded-2xl border border-surface-card-deep border-b-4 bg-brand-green px-10 font-poppins text-base font-semibold uppercase tracking-wide text-white shadow-none transition-colors hover:bg-brand-green/90 hover:shadow-none"As per coding guidelines, "Use chunky 3D borders pattern with
border-b-4 border-surface-card-deepon cards, buttons, and badges" and "Use rounded corners ofrounded-2xlorrounded-3xlfor game elements".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/auth/WelcomeScreen.tsx` around lines 933 - 936, Update the Browse all categories CTA Button in WelcomeScreen.tsx (the Button that calls setCategoriesOpen(true)) to use the chunky 3D border and guideline corner token: add the classes border-b-4 and border-surface-card-deep and replace the current rounded-[20px] with the specified token (rounded-2xl or rounded-3xl) while keeping existing sizing and color classes; ensure the className on that Button includes border-b-4 border-surface-card-deep and rounded-2xl (or rounded-3xl) so it matches the shared game button pattern.src/components/auth/TermsOfServiceScreen.tsx (1)
68-68: ⚡ Quick winUse the required 3D border treatment on the updated terms button/card.
Both newly styled surfaces currently miss the project’s card/button border pattern.
♻️ Suggested class updates
- className="gap-2 rounded-full bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20 hover:text-white" + className="gap-2 rounded-full border border-surface-card-deep border-b-4 bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20 hover:text-white" - <div className="rounded-[24px] bg-surface-card/40 backdrop-blur-sm p-6 md:p-10"> + <div className="rounded-[24px] border border-surface-card-deep border-b-4 bg-surface-card/40 backdrop-blur-sm p-6 md:p-10">As per coding guidelines, "Use chunky 3D borders pattern with
border-b-4 border-surface-card-deepon cards, buttons, and badges".Also applies to: 85-85
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/auth/TermsOfServiceScreen.tsx` at line 68, The button/card in TermsOfServiceScreen.tsx using className "gap-2 rounded-full bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20 hover:text-white" is missing the project's chunky 3D border; update the element(s) in TermsOfServiceScreen (both the instance at the shown diff and the other occurrence around line 85) to include the 3D border utility classes by adding "border-b-4 border-surface-card-deep" to their className strings so the button/card follows the card/button/badge border pattern.src/components/shared/FriendPlayModal.tsx (1)
155-156: ⚡ Quick winAdd the required chunky 3D border tokens to the Join button.
The updated button classes keep sizing behavior, but the required button border pattern is missing.
Suggested patch
- "shrink-0 h-14 rounded-2xl px-5 text-base font-black uppercase tracking-wide text-white", + "shrink-0 h-14 rounded-2xl border-b-4 border-surface-card-deep px-5 text-base font-black uppercase tracking-wide text-white",As per coding guidelines, "Use chunky 3D borders pattern with
border-b-4 border-surface-card-deepon cards, buttons, and badges".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/shared/FriendPlayModal.tsx` around lines 155 - 156, The Join button is missing the chunky 3D border tokens; update the class list used for the Join button in FriendPlayModal (the class array containing "shrink-0 h-14 rounded-2xl px-5 text-base font-black uppercase tracking-wide text-white" and the responsive "transition-all md:h-16 md:text-lg md:px-7 whitespace-nowrap") to include the required border tokens (e.g., add "border-b-4 border-surface-card-deep") so the button keeps its sizing and responsive classes but also applies the chunky 3D border pattern.src/features/friend/components/FriendLobbyScreen.tsx (1)
120-120: ⚡ Quick winUse standard rounded tokens on the updated action controls.
The edited controls still use arbitrary rounded values (
rounded-[20px],rounded-[14px]). Please switch these torounded-2xl/rounded-3xlto match the shared game UI convention.As per coding guidelines, "Use rounded corners of
rounded-2xlorrounded-3xlfor game elements".Also applies to: 149-149, 174-174, 188-188
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/friend/components/FriendLobbyScreen.tsx` at line 120, In FriendLobbyScreen component replace any arbitrary Tailwind rounded classes (e.g., rounded-[20px], rounded-[14px]) used on action control className strings with the standard tokens: use rounded-3xl for the larger controls and rounded-2xl for smaller ones so they match the shared game UI convention; update every occurrence (the one shown and the other instances) in the component’s JSX className values accordingly (search for rounded-[20px] and rounded-[14px] in FriendLobbyScreen and swap to rounded-3xl/rounded-2xl).src/features/party/PartyQuizResultsScreen.tsx (1)
182-203: ⚡ Quick winAlign new game UI elements with required design tokens/patterns.
The new rank pill/badge/buttons use custom corner radii (
rounded-[12px],rounded-[14px],rounded-[16px]) and omit the required chunky 3D border treatment for buttons/badges. Please switch these changed elements to approved rounded tokens (rounded-2xl/rounded-3xl) and applyborder-b-4 border-surface-card-deepwhere applicable.As per coding guidelines, "Use chunky 3D borders pattern with
border-b-4 border-surface-card-deepon cards, buttons, and badges" and "Use rounded corners ofrounded-2xlorrounded-3xlfor game elements".Also applies to: 477-486
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/party/PartyQuizResultsScreen.tsx` around lines 182 - 203, Replace the custom arbitrary radii and missing 3D border on the rank pill and related badges with the approved tokens: change classes using rounded-[12px], rounded-[14px], rounded-[16px] to rounded-2xl or rounded-3xl (use rounded-2xl for smaller pills, rounded-3xl for larger/button-like elements) and add the chunky 3D border classes border-b-4 border-surface-card-deep where applicable; specifically update the span that renders standing.rank (the rank pill) and the self indicator span (the rounded-full bg-brand-orange badge) to use the new rounded-* tokens and include border-b-4 border-surface-card-deep so they follow the design token/pattern.src/lib/auth/google-identity.ts (1)
76-83: 💤 Low valueConsider adding a guard for
crypto.subtleavailability.
crypto.subtlerequires a secure context (HTTPS or localhost). While modern browsers support it, if called in an insecure context (e.g., HTTP during development), this will throw. The error would bubble up to the caller, which may be acceptable, but documenting or guarding against this edge case would improve robustness.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/auth/google-identity.ts` around lines 76 - 83, In generateNoncePair add a runtime guard for crypto.subtle (e.g., check globalThis.crypto && globalThis.crypto.subtle) and if missing either throw a clear, descriptive error or use an explicit fallback (such as Node's webcrypto) before calling crypto.subtle.digest; update the function to detect the environment and fail fast with a helpful message like "crypto.subtle unavailable: require secure context or provide webcrypto fallback" so callers get actionable feedback instead of an unexpected exception.src/lib/posthog.ts (1)
71-98: ⚡ Quick winPrefer
posthog.setPersonProperties()overposthog.capture('$set', ...)In
src/lib/posthog.ts(setPersonProperties), useposthog.setPersonProperties(set, setOnce)when either argument is provided—the SDK has a dedicated API for$set/$set_onceperson property updates, making the intent more explicit than emitting a$setcapture event.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/posthog.ts` around lines 71 - 98, The setPersonProperties function currently builds a payload and calls posthog.capture('$set', payload); replace that capture call with the SDK's dedicated method by invoking posthog.setPersonProperties(set, setOnce) (when either set or setOnce is provided) so person properties use the explicit API; keep the existing environment checks and try/catch/error logging around the call and remove the posthog.capture('$set', ...) usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/migrate-brand-colors.mjs`:
- Around line 63-65: The results array is only receiving entries when a file had
replacements (changed or mapped > 0), which omits files that only had unmapped
hex usages (skipped) and undercounts the "Hex usages left as-is" total; update
the conditional around results.push in the migration loop (where variables
changed, mapped, skipped and results are used) to include files with skipped > 0
(e.g., change if (changed || mapped > 0) to if (changed || mapped > 0 || skipped
> 0)) so every file with unmapped hits is recorded; also review the aggregation
logic that computes the totals (the block referencing results later) to ensure
it sums skipped values from results rather than relying on presence of mapped
entries.
In `@src/app/`(app)/profile/page.tsx:
- Around line 69-70: Tracking calls (trackNicknameChanged()) inside the same try
that awaits updateMe(...) can throw and trigger the outer catch, turning a
successful update into an error UX; wrap the analytics call in its own try/catch
(or move it after the mutation and guard with try { await
trackNicknameChanged(); } catch { /* swallow/log locally */ }) so that
toast.success(t("profile.nicknameUpdated")) and the successful update are not
rolled back by tracking errors; apply the same pattern to the other occurrence
around line 114-115.
In `@src/app/`(fullscreen)/dev/timers/page.tsx:
- Around line 53-55: The description text contradicts the preset's behavior: the
object with durationMs: 8_000 (and source 'RealtimePossessionMatchScreen /
RealtimePartyQuizScreen forfeitPending block') currently says "No timer" but
renders countdown metadata; update the description field to accurately reflect
an 8s countdown (e.g., change "No timer — shown while server confirms forfeit
result." to something like "Static finalizing-results modal — 8s countdown shown
while server confirms forfeit result.") so QA/devs are not misled.
In `@src/features/friend/components/CreateJoinPanel.tsx`:
- Around line 71-78: Currently trackFriendInviteAccepted(code) is called before
emitting getSocket().emit("lobby:join_by_code"), which can record “accepted”
even if join fails; move the trackFriendInviteAccepted call into the
confirmed-join handler instead (the socket response/listener that establishes
lobby membership, e.g., where you handle the successful join event or update
membership state). Specifically, remove the try/catch around
trackFriendInviteAccepted from CreateJoinPanel and invoke
trackFriendInviteAccepted(code) inside the success callback/handler for the
"lobby:join_by_code" flow (the code path that runs when the server confirms
join/membership), keeping existing error handling and the
toast.info(t("friend.joiningCode", { code })) placement as appropriate.
- Line 71: The code uses inviteCode.toUpperCase() which permits leading/trailing
whitespace; change it to trim before uppercasing (e.g. use
inviteCode?.trim().toUpperCase() or (inviteCode || '').trim().toUpperCase()) so
the CreateJoinPanel logic and the const code variable produce a normalized code
without surrounding spaces.
In `@src/features/friend/hooks/useFriendLobbyLogic.ts`:
- Around line 350-355: Remove the state mutation inside the reconciliation
useEffect in useFriendLobbyLogic: delete the useEffect body that calls
setOptimisticReady(null) when me?.isReady === optimisticReady and instead
compute a derived value for the effective optimistic-ready flag (e.g.,
derivedOptimisticReady = optimisticReady !== null ? optimisticReady :
me?.isReady) and return/use that variable; this keeps optimisticReady as the
optimistic override state without setting state in an effect and avoids the
extra render and the react-hooks/set-state-in-effect warning.
In `@src/features/party/RealtimePartyQuizScreen.tsx`:
- Around line 394-397: When matchId changes the effect currently resets
displayedTotalsByUserId and pendingDisplayedTotalsRef but leaves other tracking
refs that must also be cleared; update the useEffect that runs on
partyState?.matchId to also reset/clear spawnedFlightKeysRef,
liveDeltaShownKeysRef, liveFlightShownKeysRef and the previous totals/answered
refs (previousTotalsRef, previousAnsweredRef) so animations and delta
calculations restart correctly when qIndex resets; locate the useEffect with
setDisplayedTotalsByUserId and pendingDisplayedTotalsRef and add appropriate
clear/reset calls for spawnedFlightKeysRef, liveDeltaShownKeysRef,
liveFlightShownKeysRef, previousTotalsRef and previousAnsweredRef.
In `@src/features/possession/components/BarBattleFlightOverlay.tsx`:
- Around line 78-99: The hook useScrollPinTransform creates a window scroll
listener per sprite which causes many duplicate listeners; instead move the
single scroll listener to the overlay/root component (BarBattleFlightOverlay) so
it publishes the shared CSS vars (--scroll-pin-x, --scroll-pin-y) once for all
sprites, remove per-sprite useScrollPinTransform usage (including the other
occurrences around the mentioned ranges), and have sprite elements read those
CSS vars; ensure the listener is added with { passive: true }, stores
initialX/initialY at mount, updates the two CSS custom properties on the root
node, and is cleaned up on unmount.
In `@src/features/possession/RealtimePossessionMatchScreen.tsx`:
- Around line 227-236: Replace ad-hoc typography and radius on the pause/forfeit
overlay card with the game tokens: change instances of "font-poppins" to
"font-fun", replace "rounded-[20px]" with the standardized rounded class (use
"rounded-2xl" or "rounded-3xl" depending on surrounding components), and add the
chunky 3D border pattern "border-b-4 border-surface-card-deep" to the card
container; apply the same changes to the other overlay elements referenced in
this component (the nodes rendering forfeitPendingTitle and
forfeitPending.message and the other overlay block around lines 258-274) so all
game UI overlays use font-fun, the standard rounded-xl token, and the border-b-4
border-surface-card-deep pattern.
In `@src/stores/realtimeMatch.store.ts`:
- Around line 676-694: The code reads id/questionId, difficulty and category
from the wrong object (the currentQuestion root) before calling
trackAnswerSubmitted; instead, extract these metadata fields from
state.match.currentQuestion.question (use currentQuestion.question.questionId or
.id, .difficulty, and .categoryName or .category) and pass those values to
trackAnswerSubmitted (fall back to the payload matchId:qIndex only if question
id is truly missing). Update the references around the q variable and the
trackAnswerSubmitted call to use q.question for id/questionId, difficulty, and
categoryName/category to ensure accurate analytics metadata.
---
Outside diff comments:
In `@src/app/`(fullscreen)/dev/party-quiz/page.tsx:
- Around line 280-307: The stub socket currently uses an explicit any and
disables the rule; update createStubSocket to return a properly typed
Socket-like interface (or a named DevStubSocket type) that declares id,
connected, active, auth, emit, on, once, off, removeListener,
removeAllListeners, connect, disconnect, listenersAny, and listeners with proper
parameter and return types (use the onMatchAnswer callback type for the emit
payload) and remove the eslint-disable comment; do the same for the mock auth
user by declaring an explicit AuthUser (or DevAuthUser) shape and casting the
mock object to that type (or validate via unknown + type guards) instead of
using "as any" so the file remains strict-TS clean.
In `@src/features/game/components/MatchmakingMapScreen.tsx`:
- Around line 1267-1293: opponentPin is recreated every render via the IIFE
which causes the "Zoom to opponent when found" and "Matchmaking opponent
location resolved" effects (and mapPlayers useMemo) to retrigger; wrap that
creation in a useMemo to stabilize the object. Specifically, replace the IIFE
that defines opponentPin with a React.useMemo that returns null if
rankedFoundOpponent is falsy, otherwise runs resolveOpponentLocation and
projectPoint and builds the same object; include all inputs used to compute the
pin in the dependency array (rankedFoundOpponent, t,
resolveOpponentLocation/projectPoint/getAvatarAsset/hashString references if
they can change) so the pin only updates when relevant data changes.
In `@src/features/party/RealtimePartyQuizScreen.tsx`:
- Around line 833-858: The overlay components (the forfeit/finalizing overlay
and the pause overlay rendered in the motion.div blocks around
forfeitPending/forfeitPendingTitle and the one keyed "party-pause" using
state.matchPaused and pauseSeconds) use arbitrary radii and font classes; update
them to follow game-style tokens: replace rounded-[20px]/rounded-[28px] with
rounded-2xl or rounded-3xl as appropriate, swap font-poppins for font-fun on the
text container, and add the chunky 3D border classes (border-b-4
border-surface-card-deep) to the overlay container divs so they match the repo
guidelines.
---
Nitpick comments:
In `@src/app/`(fullscreen)/dev/timers/page.tsx:
- Around line 186-222: Replace the game UI utility classes in the new timer
components to match the design-system: in the Pause preview block and
FinalizingResultsPreview component (look for PreviewFrame usage, the motion.div
with key={`finalizing-${runId}`}, TimerMeta and any elements using
useRemainingMs), swap font-poppins -> font-fun, change any rounded-[...] or
rounded-xl to rounded-2xl or rounded-3xl as appropriate, and add the chunky 3D
border classes (border-b-4 border-surface-card-deep) to card/button/badge
containers (e.g., the root motion.divs and status badges). Apply the same
substitutions in the other affected ranges mentioned (around lines 257-317,
511-515, 574-575) so all timer UI uses font-fun, rounded-2xl/3xl, and border-b-4
border-surface-card-deep consistently.
In `@src/components/auth/PrivacyPolicyScreen.tsx`:
- Line 84: The button and primary content card in the PrivacyPolicyScreen
component are missing the required chunky 3D border; update the relevant JSX
elements in PrivacyPolicyScreen (the element using className="gap-2 rounded-full
bg-white/10 px-4 font-poppins text-sm font-semibold text-white hover:bg-white/20
hover:text-white" and the main content card element) to include the classes
border-b-4 and border-surface-card-deep so they follow the "chunky 3D borders"
pattern for cards, buttons, and badges.
In `@src/components/auth/TermsOfServiceScreen.tsx`:
- Line 68: The button/card in TermsOfServiceScreen.tsx using className "gap-2
rounded-full bg-white/10 px-4 font-poppins text-sm font-semibold text-white
hover:bg-white/20 hover:text-white" is missing the project's chunky 3D border;
update the element(s) in TermsOfServiceScreen (both the instance at the shown
diff and the other occurrence around line 85) to include the 3D border utility
classes by adding "border-b-4 border-surface-card-deep" to their className
strings so the button/card follows the card/button/badge border pattern.
In `@src/components/auth/WelcomeScreen.tsx`:
- Around line 933-936: Update the Browse all categories CTA Button in
WelcomeScreen.tsx (the Button that calls setCategoriesOpen(true)) to use the
chunky 3D border and guideline corner token: add the classes border-b-4 and
border-surface-card-deep and replace the current rounded-[20px] with the
specified token (rounded-2xl or rounded-3xl) while keeping existing sizing and
color classes; ensure the className on that Button includes border-b-4
border-surface-card-deep and rounded-2xl (or rounded-3xl) so it matches the
shared game button pattern.
In `@src/components/game/RoundTransitionOverlay.tsx`:
- Line 116: The category label in the RoundTransitionOverlay component is using
"font-poppins" instead of the required game typography; update the className on
the label inside the RoundTransitionOverlay component (the element with
className="font-poppins text-[16px] sm:text-[18px] md:text-[20px] font-bold
uppercase tracking-[0.22em] text-brand-yellow mb-4") to use "font-fun" in place
of "font-poppins" so the overlay follows the Nunito-based game font rule.
In `@src/components/shared/FriendPlayModal.tsx`:
- Around line 155-156: The Join button is missing the chunky 3D border tokens;
update the class list used for the Join button in FriendPlayModal (the class
array containing "shrink-0 h-14 rounded-2xl px-5 text-base font-black uppercase
tracking-wide text-white" and the responsive "transition-all md:h-16 md:text-lg
md:px-7 whitespace-nowrap") to include the required border tokens (e.g., add
"border-b-4 border-surface-card-deep") so the button keeps its sizing and
responsive classes but also applies the chunky 3D border pattern.
In `@src/features/friend/components/FriendLobbyScreen.tsx`:
- Line 120: In FriendLobbyScreen component replace any arbitrary Tailwind
rounded classes (e.g., rounded-[20px], rounded-[14px]) used on action control
className strings with the standard tokens: use rounded-3xl for the larger
controls and rounded-2xl for smaller ones so they match the shared game UI
convention; update every occurrence (the one shown and the other instances) in
the component’s JSX className values accordingly (search for rounded-[20px] and
rounded-[14px] in FriendLobbyScreen and swap to rounded-3xl/rounded-2xl).
In `@src/features/party/PartyQuizResultsScreen.tsx`:
- Around line 182-203: Replace the custom arbitrary radii and missing 3D border
on the rank pill and related badges with the approved tokens: change classes
using rounded-[12px], rounded-[14px], rounded-[16px] to rounded-2xl or
rounded-3xl (use rounded-2xl for smaller pills, rounded-3xl for
larger/button-like elements) and add the chunky 3D border classes border-b-4
border-surface-card-deep where applicable; specifically update the span that
renders standing.rank (the rank pill) and the self indicator span (the
rounded-full bg-brand-orange badge) to use the new rounded-* tokens and include
border-b-4 border-surface-card-deep so they follow the design token/pattern.
In `@src/lib/auth/google-identity.ts`:
- Around line 76-83: In generateNoncePair add a runtime guard for crypto.subtle
(e.g., check globalThis.crypto && globalThis.crypto.subtle) and if missing
either throw a clear, descriptive error or use an explicit fallback (such as
Node's webcrypto) before calling crypto.subtle.digest; update the function to
detect the environment and fail fast with a helpful message like "crypto.subtle
unavailable: require secure context or provide webcrypto fallback" so callers
get actionable feedback instead of an unexpected exception.
In `@src/lib/posthog.ts`:
- Around line 71-98: The setPersonProperties function currently builds a payload
and calls posthog.capture('$set', payload); replace that capture call with the
SDK's dedicated method by invoking posthog.setPersonProperties(set, setOnce)
(when either set or setOnce is provided) so person properties use the explicit
API; keep the existing environment checks and try/catch/error logging around the
call and remove the posthog.capture('$set', ...) usage.
🪄 Autofix (Beta)
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 55060fad-4ca2-4cc6-b26d-0ecc0409e698
📒 Files selected for processing (114)
.env.examplescripts/audit-brand-colors.mjsscripts/brand-colors-allowlist.jsonscripts/migrate-brand-colors.mjsscripts/rename-tokens.mjssrc/app/(app)/daily/challenges/[challengeId]/page.tsxsrc/app/(app)/page.tsxsrc/app/(app)/play/page.tsxsrc/app/(app)/profile/page.tsxsrc/app/(auth)/auth/forgot-password/page.tsxsrc/app/(auth)/auth/welcome/page.tsxsrc/app/(fullscreen)/dev/party-quiz/page.tsxsrc/app/(fullscreen)/dev/timers/page.tsxsrc/app/(fullscreen)/onboarding/page.tsxsrc/app/(public)/privacy/PrivacyClient.tsxsrc/app/(public)/terms/TermsClient.tsxsrc/app/layout.tsxsrc/app/page.tsxsrc/app/sitemap.tssrc/components/FriendModeBottomSheet.tsxsrc/components/HelpButtons.tsxsrc/components/InlineAvatar.tsxsrc/components/LanguageSelector.tsxsrc/components/RankedModeBottomSheet.tsxsrc/components/Timer.tsxsrc/components/auth/AppAuthGate.tsxsrc/components/auth/OAuthCallbackScreen.tsxsrc/components/auth/PrivacyPolicyScreen.tsxsrc/components/auth/TermsOfServiceScreen.tsxsrc/components/auth/WelcomeScreen.tsxsrc/components/game/AnswerCard.tsxsrc/components/game/PossessionQuestionPanel.tsxsrc/components/game/QuestionArena.tsxsrc/components/game/RoundTransitionOverlay.tsxsrc/components/layout/AppShell.tsxsrc/components/layout/ChallengeInvitePrompt.tsxsrc/components/layout/NotificationsDropdown.tsxsrc/components/shared/FriendPlayModal.tsxsrc/components/shared/MatchCountdownPuck.tsxsrc/contexts/LocaleContext.tsxsrc/features/daily/CareerPathGame.tsxsrc/features/daily/ClueGame.tsxsrc/features/daily/CountdownGame.tsxsrc/features/daily/EmojiGuessGame.tsxsrc/features/daily/FootballLogicGame.tsxsrc/features/daily/HighLowGame.tsxsrc/features/daily/ImposterGame.tsxsrc/features/daily/PutInOrderGame.tsxsrc/features/daily/TrueFalseGame.tsxsrc/features/daily/index.tssrc/features/friend/components/CategoryDraftPanel.tsxsrc/features/friend/components/CreateJoinPanel.tsxsrc/features/friend/components/FriendLobbyScreen.tsxsrc/features/friend/components/LobbyHeader.tsxsrc/features/friend/hooks/useFriendLobbyLogic.tssrc/features/game/GameStageRouter.tsxsrc/features/game/RealtimeResultsScreen.tsxsrc/features/game/__tests__/GameStageRouter.test.tsxsrc/features/game/components/MatchmakingMapScreen.tsxsrc/features/game/components/ResultsShared.tsxsrc/features/game/hooks/useRealtimeGameLogic.tssrc/features/home/HomeDashboard.tsxsrc/features/home/HomeScreen.tsxsrc/features/home/challenges.tssrc/features/home/components/DailyChallengeCard.tsxsrc/features/home/components/DailyChallengesDrawer.tsxsrc/features/home/components/DailyChallengesSection.tsxsrc/features/home/components/GameHistorySection.tsxsrc/features/home/components/RewardsObjectivesSection.tsxsrc/features/home/components/dashboard/HomeDailyObjectives.tsxsrc/features/home/components/dashboard/HomeEventTeaser.tsxsrc/features/home/components/dashboard/HomeFeaturedChallenge.tsxsrc/features/home/components/dashboard/HomePlayHero.tsxsrc/features/home/components/dashboard/HomeQuickStatsRow.tsxsrc/features/home/components/dashboard/HomeRightRail.tsxsrc/features/party/PartyQuizResultsScreen.tsxsrc/features/party/RealtimePartyQuizScreen.tsxsrc/features/play/ModeSelectionScreen.tsxsrc/features/play/hooks/useDraftLogic.tssrc/features/possession/RealtimePossessionMatchScreen.tsxsrc/features/possession/components/BarBattleFlightOverlay.tsxsrc/features/possession/components/DevToolbar.tsxsrc/features/possession/components/IntroScreen.tsxsrc/features/possession/components/KickoffCountdownOverlay.tsxsrc/features/possession/components/LiveSpecialQuestionPanel.tsxsrc/features/possession/components/PenaltyTransition.tsxsrc/features/possession/components/PitchVisualization.tsxsrc/features/possession/components/PossessionMatchViewport.tsxsrc/features/possession/components/PregameOverlay.tsxsrc/features/possession/components/ShotOverlay.tsxsrc/features/possession/hooks/__tests__/usePossessionFieldState.test.tssrc/features/possession/hooks/usePossessionBarBattleFlights.tssrc/features/settings/SettingsScreen.tsxsrc/features/social/SocialScreen.tsxsrc/features/store/StoreScreen.tsxsrc/features/store/components/RewardQuestCard.tsxsrc/features/store/components/WalletSection.tsxsrc/features/training/components/TrainingOfferModal.tsxsrc/lib/analytics/game-events.tssrc/lib/auth/auth.service.tssrc/lib/auth/google-identity.tssrc/lib/posthog.tssrc/lib/realtime/socket-handlers.tssrc/lib/realtime/socket.types.tssrc/lib/sounds/index.tssrc/messages/en.jsonsrc/messages/ka.jsonsrc/stores/__tests__/realtimeMatch.store.test.tssrc/stores/auth.store.tssrc/stores/rankedMatchmaking.store.tssrc/stores/realtimeMatch.store.tssrc/types/api.generated.tssrc/utils/gameHelpers.tssrc/utils/rankSystem.ts
💤 Files with no reviewable changes (41)
- src/app/sitemap.ts
- src/features/home/components/dashboard/HomeEventTeaser.tsx
- src/features/store/components/RewardQuestCard.tsx
- src/features/possession/components/ShotOverlay.tsx
- src/features/possession/components/IntroScreen.tsx
- src/app/(app)/page.tsx
- src/features/possession/components/PregameOverlay.tsx
- src/components/InlineAvatar.tsx
- src/features/possession/components/DevToolbar.tsx
- src/features/home/components/GameHistorySection.tsx
- src/lib/sounds/index.ts
- src/features/daily/index.ts
- src/components/LanguageSelector.tsx
- src/components/HelpButtons.tsx
- src/features/home/HomeDashboard.tsx
- src/components/game/AnswerCard.tsx
- src/features/game/components/ResultsShared.tsx
- src/features/training/components/TrainingOfferModal.tsx
- src/features/home/components/dashboard/HomePlayHero.tsx
- src/components/Timer.tsx
- src/utils/gameHelpers.ts
- src/utils/rankSystem.ts
- src/features/store/components/WalletSection.tsx
- src/features/home/components/dashboard/HomeFeaturedChallenge.tsx
- src/features/home/components/DailyChallengesSection.tsx
- src/components/game/QuestionArena.tsx
- src/features/home/HomeScreen.tsx
- src/components/FriendModeBottomSheet.tsx
- src/features/friend/components/CategoryDraftPanel.tsx
- src/features/home/components/DailyChallengeCard.tsx
- src/features/home/components/dashboard/HomeQuickStatsRow.tsx
- src/features/home/components/RewardsObjectivesSection.tsx
- src/components/RankedModeBottomSheet.tsx
- src/features/play/hooks/useDraftLogic.ts
- src/features/home/challenges.ts
- src/features/home/components/dashboard/HomeRightRail.tsx
- src/features/home/components/dashboard/HomeDailyObjectives.tsx
- src/app/(app)/play/page.tsx
- src/features/possession/components/PenaltyTransition.tsx
- src/features/home/components/DailyChallengesDrawer.tsx
- src/features/possession/components/PitchVisualization.tsx
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/features/party/RealtimePartyQuizScreen.tsx (1)
23-23: ⚡ Quick winCross-feature import violates module isolation guideline.
This imports
usePossessionFirstQuestionIntrofrom@/features/possession/into@/features/party/. Consider moving this shared hook to@/lib/hooks/or a common location to maintain feature module isolation.As per coding guidelines: "Feature modules in
src/features/should be self-contained with their own components, hooks, and logic — do NOT import from other features directly; use shared stores or libs instead."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/party/RealtimePartyQuizScreen.tsx` at line 23, The file imports usePossessionFirstQuestionIntro from another feature which breaks module isolation; move the hook (usePossessionFirstQuestionIntro) out of src/features/possession and into a shared location such as src/lib/hooks (or a new common hooks module), export it from there, update its original export to re-export from the new location if needed, and change the import in RealtimePartyQuizScreen.tsx to import from the new shared path; ensure any internal dependencies of the hook are either moved or adapted to avoid pulling more feature-private modules into a shared area.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/features/party/RealtimePartyQuizScreen.tsx`:
- Line 23: The file imports usePossessionFirstQuestionIntro from another feature
which breaks module isolation; move the hook (usePossessionFirstQuestionIntro)
out of src/features/possession and into a shared location such as src/lib/hooks
(or a new common hooks module), export it from there, update its original export
to re-export from the new location if needed, and change the import in
RealtimePartyQuizScreen.tsx to import from the new shared path; ensure any
internal dependencies of the hook are either moved or adapted to avoid pulling
more feature-private modules into a shared area.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3e4cd208-397e-42a3-ac86-0563cb5e7500
📒 Files selected for processing (1)
src/features/party/RealtimePartyQuizScreen.tsx
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Fixes Applied SuccessfullyFixed 9 file(s) based on 10 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken: |
Fixed 9 file(s) based on 10 unresolved review comments. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/friend/components/CreateJoinPanel.tsx (1)
59-71:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winValidate the normalized code before emitting join.
Line 59 validates raw input length, but Line 71 normalizes with
trim(). Inputs like" AB "can pass the guard, then emit an invalid 2-char code.💡 Suggested fix
const handleJoin = () => { if (isJoining) return; - if (!inviteCode || inviteCode.length < 3) { + const code = inviteCode.trim().toUpperCase(); + if (!code || code.length < 3) { toast.error(t("friend.enterValidCode")); return; } @@ - const code = inviteCode.trim().toUpperCase(); try { trackFriendInviteAccepted(code);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/friend/components/CreateJoinPanel.tsx` around lines 59 - 71, The validation currently checks the raw inviteCode length but the code is later normalized (trim().toUpperCase()), allowing inputs like " AB " to bypass the guard; change the logic to normalize first (use const code = inviteCode?.trim().toUpperCase()), then validate code length (e.g., if (!code || code.length < 3) { toast.error(...); return; }) and use that normalized code for the join emit and timeout handling (references: inviteCode, code, setIsJoining, joinTimeoutRef).
♻️ Duplicate comments (1)
src/features/party/RealtimePartyQuizScreen.tsx (1)
405-413:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAlso clear live delta timers/state on match reset.
This effect still leaves
liveDeltaTimeoutsRefandliveScoreDeltascarrying over from the previous match. IfmatchIdflips before an old 1.5s timeout fires, a stale badge can flash in the new match or the old timeout can remove a fresh delta for the sameuserId.Suggested patch
useEffect(() => { setDisplayedTotalsByUserId({}); + setLiveScoreDeltas({}); pendingDisplayedTotalsRef.current.clear(); spawnedFlightKeysRef.current.clear(); liveDeltaShownKeysRef.current.clear(); liveFlightShownKeysRef.current.clear(); previousPartyTotalsRef.current.clear(); previousPartyAnsweredRef.current.clear(); + for (const timeoutId of liveDeltaTimeoutsRef.current.values()) { + window.clearTimeout(timeoutId); + } + liveDeltaTimeoutsRef.current.clear(); }, [partyState?.matchId]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/party/RealtimePartyQuizScreen.tsx` around lines 405 - 413, The effect that resets state on match change must also cancel and clear any outstanding live-delta timers and the live score delta state: inside the useEffect tied to partyState?.matchId (the same block that calls setDisplayedTotalsByUserId and clears pendingDisplayedTotalsRef, spawnedFlightKeysRef, liveDeltaShownKeysRef, liveFlightShownKeysRef, previousPartyTotalsRef, previousPartyAnsweredRef), iterate liveDeltaTimeoutsRef.current to call clearTimeout on each stored timeout handle and then clear the container, and also clear liveScoreDeltas (e.g., empty the map/set holding per-user deltas) so stale timeouts or delta entries cannot affect the new match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/features/friend/components/CreateJoinPanel.tsx`:
- Around line 59-71: The validation currently checks the raw inviteCode length
but the code is later normalized (trim().toUpperCase()), allowing inputs like "
AB " to bypass the guard; change the logic to normalize first (use const code =
inviteCode?.trim().toUpperCase()), then validate code length (e.g., if (!code ||
code.length < 3) { toast.error(...); return; }) and use that normalized code for
the join emit and timeout handling (references: inviteCode, code, setIsJoining,
joinTimeoutRef).
---
Duplicate comments:
In `@src/features/party/RealtimePartyQuizScreen.tsx`:
- Around line 405-413: The effect that resets state on match change must also
cancel and clear any outstanding live-delta timers and the live score delta
state: inside the useEffect tied to partyState?.matchId (the same block that
calls setDisplayedTotalsByUserId and clears pendingDisplayedTotalsRef,
spawnedFlightKeysRef, liveDeltaShownKeysRef, liveFlightShownKeysRef,
previousPartyTotalsRef, previousPartyAnsweredRef), iterate
liveDeltaTimeoutsRef.current to call clearTimeout on each stored timeout handle
and then clear the container, and also clear liveScoreDeltas (e.g., empty the
map/set holding per-user deltas) so stale timeouts or delta entries cannot
affect the new match.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3930f93f-e3d7-4f55-836c-af0b53bf0f7e
📒 Files selected for processing (9)
scripts/migrate-brand-colors.mjssrc/app/(app)/profile/page.tsxsrc/app/(fullscreen)/dev/timers/page.tsxsrc/features/friend/components/CreateJoinPanel.tsxsrc/features/friend/hooks/useFriendLobbyLogic.tssrc/features/party/RealtimePartyQuizScreen.tsxsrc/features/possession/RealtimePossessionMatchScreen.tsxsrc/features/possession/components/BarBattleFlightOverlay.tsxsrc/stores/realtimeMatch.store.ts
…style CodeRabbit's autofix swapped the forfeit and pause overlay cards from `rounded-[20px] font-poppins` (the Figma flat style) to `rounded-2xl border-b-4 border-surface-card-deep font-fun` (the retired Duolingo chunky 3D pattern). frontend CLAUDE.md explicitly says: > Do NOT use border-b-4 ... on new work. > font-fun (Nunito) is retired except in legacy screens. These overlays were among the screens being migrated AWAY from chunky 3D, so applying that pattern is the opposite of what we want. Restores the Figma-pinned royal-blue Poppins style.
… analytics Mirrors the backend change (server no longer ships correctIndex on match:question). Removes the field from the frontend socket type and the three store updaters that previously merged it into per-question state; the merge becomes a passthrough since the payload no longer carries it. useRealtimeGameLogic's correctIndex memo drops the payload fallback and reads from the canonical sources (questions[]. correctIndex set on round resolve, answerAck.correctIndex, and roundResult.reveal.correctIndex). trackAnswerSubmitted used to fire synchronously inside submitAnswer and was gated on `correctIndex !== undefined` — which would have broken now that the payload no longer carries it. Moved the analytics call into a dedicated effect that fires when match: answer_ack arrives, using answerAck.isCorrect as the source of truth (server-authoritative rather than a client-side compare). Adds a trackedAckQIndexRef so the event fires exactly once per question.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/features/game/components/MatchmakingMapScreen.tsx (1)
1273-1299: 💤 Low valueConsider restoring
useMemoforopponentPincomputation.The refactor from
useMemoto an IIFE meansresolveOpponentLocationandprojectPointrun on every render, even whenrankedFoundOpponenthasn't changed. While this is unlikely to cause visible performance issues given the component's usage, wrapping this inuseMemowith[rankedFoundOpponent, t]dependencies would avoid unnecessary recomputation during animations and timer ticks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/game/components/MatchmakingMapScreen.tsx` around lines 1273 - 1299, The opponentPin IIFE forces resolveOpponentLocation and projectPoint to run on every render; wrap the computation in useMemo so it only recomputes when inputs change: replace the IIFE that creates opponentPin with useMemo(() => { ... }, [rankedFoundOpponent, t]) so that opponentPin, which depends on rankedFoundOpponent and the translation function t (and indirectly resolveOpponentLocation/projectPoint), is memoized and avoids unnecessary recomputation during animations/ticks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/game/hooks/useRealtimeGameLogic.ts`:
- Around line 396-410: Duplicate analytics are emitted because
trackAnswerSubmitted is called both inside the useEffect in useRealtimeGameLogic
(the effect that listens to answerAck/currentQuestion and uses
trackedAckQIndexRef and answerSubmitElapsedRef) and inside setAnswerAck in
realtimeMatch.store.ts; remove the duplication by deleting this useEffect (the
block starting with useEffect(() => { if (!answerAck || !currentQuestion)
return; ... }, [answerAck, currentQuestion]) ) so the store’s setAnswerAck
remains the single emitter, and then remove or repurpose trackedAckQIndexRef and
answerSubmitElapsedRef in this hook if they are no longer used elsewhere.
---
Nitpick comments:
In `@src/features/game/components/MatchmakingMapScreen.tsx`:
- Around line 1273-1299: The opponentPin IIFE forces resolveOpponentLocation and
projectPoint to run on every render; wrap the computation in useMemo so it only
recomputes when inputs change: replace the IIFE that creates opponentPin with
useMemo(() => { ... }, [rankedFoundOpponent, t]) so that opponentPin, which
depends on rankedFoundOpponent and the translation function t (and indirectly
resolveOpponentLocation/projectPoint), is memoized and avoids unnecessary
recomputation during animations/ticks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3514d20d-276a-4a08-884e-cb70efb31e60
📒 Files selected for processing (4)
src/features/game/components/MatchmakingMapScreen.tsxsrc/features/game/hooks/useRealtimeGameLogic.tssrc/lib/realtime/socket.types.tssrc/stores/realtimeMatch.store.ts
💤 Files with no reviewable changes (1)
- src/lib/realtime/socket.types.ts
Mirrors the backend revert. Server now ships MCQ correctIndex on match:question again (intentional UX leak, server still validates independently). Restores `payload.correctIndex` as a source in the question slot + the useRealtimeGameLogic correctIndex memo so the client-side compare lights up green/red the moment the user taps, matching Trivia Crack / QuizUp behavior. Keeps the analytics-on-ack effect from the previous change — that's a genuine improvement independent of the leak decision. trackAnswerSubmitted still fires once per question from match:answer_ack using the server- authoritative isCorrect, not the optimistic client compare.
… modal
Two polish fixes for the friendly party-quiz flow:
1. Category preload — when the host picks "random categories", the
server resolves the category inside startFriendlyMatch and then
immediately emits match:start. The client used to fall back to a
"Football" placeholder on the round-1 intro and flicker once
match:question arrived. Backend now ships the resolved category
name on match:start; this commit mirrors the type, stores it on
MatchStatus, and prefers it in the round-1 transition snapshot
(locale-resolved via useLocale, falling back to en, then to the
existing question?.categoryName, then to the legacy placeholder).
2. Forfeit overlay — was a small top-banner without any backdrop or
blur, while the matching possession (ranked) overlay was a centered
modal with bg dim + backdrop-blur. Both modes now use the same
centered-modal layout, with the same Poppins typography, the
same brand-yellow eyebrow ("Finalizing Match"), and a shared
translation key. Reads as one consistent overlay across modes.
Summary by CodeRabbit