# Claude QA Report β€” Games & Notifications **Updated:** 2026-06-24 **Devices:** emulator-5554 (Device A = QATester) + emulator-5556 (Device B = Sam), paired for real (coupleId `xNd1H2UGUDNqvyrDGgfu`). **Focus this pass:** every game works end-to-end **and notifications fire correctly on game start + finish.** **Severity:** πŸ”΄ critical Β· 🟠 high Β· 🟑 medium Β· 🟒 low **Status:** πŸ”Ž found Β· πŸ›  fixing Β· βœ… fixed & builds Β· βœ…βœ… verified live Β· ⚠️ needs deploy --- ## OPEN β€” current error log ### N1. πŸ”΄ FCM token was NEVER registered β†’ no push notifications worked at all β€” βœ…βœ… FIXED & VERIFIED `TokenRegistrar.register()` (which fetches `messaging.token` and stores it) was **never called anywhere**. The only other path, `AppMessagingService.onNewToken`, bails with `currentUserId ?: return` β€” and FCM generates the token at **install, before sign-in**, so `onNewToken` ran with no uid and stored nothing; it never fires again afterwards. Result: **`users/{uid}.fcmToken` was empty for every account**, so no game/message/daily push could ever be delivered. **Fix:** `MainActivity` now observes `authState` and calls `tokenRegistrar.register()` whenever a user is authenticated. ([MainActivity.kt](app/src/main/java/app/closer/MainActivity.kt)) **Verified live:** after the fix both A and B have a stored `fcmToken`; a direct push to A rendered the heads-up "Your partner started a game β€” Tap to join them." ### N2. πŸ”΄ POST_NOTIFICATIONS permission was never requested β†’ notifications can't display on Android 13+ β€” βœ… FIXED `NotificationPermissionHelper` existed but had **no caller**. On API 33+ notifications are silently dropped without the runtime grant. **Fix:** `MainActivity` requests `POST_NOTIFICATIONS` on launch via an Activity Result launcher. ([MainActivity.kt](app/src/main/java/app/closer/MainActivity.kt)) ### N3. 🟠 Game-START notification named the WRONG person β€” βœ… FIXED ⚠️ needs functions deploy `onGameSessionUpdate` passed the **recipient's** name into the body, so the partner saw *" has started a game"* (live: B/Sam received "Sam has started a game"). It should name the **starter**. **Fix:** use `startedByUserId` β†’ starter's name + avatar for both title and body. ([onGameSessionUpdate.ts](functions/src/games/onGameSessionUpdate.ts)) ### N4. 🟠 Game-FINISH notification only reached one partner β€” βœ… FIXED ⚠️ needs functions deploy Completion branch gated the "notify both" path on `currentData.partnerCompletedAt`, **a field the client never writes** (the client tracks `completedByUsers`). So when both finished, the partner who'd been waiting often got nothing (or a "tap to continue playing" that no longer applied). **Fix:** on `active β†’ completed` (both partners done = reveal ready) notify **both** partners, each naming the other (" finished β€” tap to see your results!"). ([onGameSessionUpdate.ts](functions/src/games/onGameSessionUpdate.ts)) ### N5. 🟑 Finish copy was wrong (title + client mapping) β€” βœ…βœ… FIXED & VERIFIED - FCM title was hard-coded `" is playing"` even for finish events (shown verbatim when the app is backgrounded). Now type-aware β†’ `" finished the game"`. ([onGameSessionUpdate.ts](functions/src/games/onGameSessionUpdate.ts)) - Client mapped `partner_finished_game` β†’ `PARTNER_COMPLETED_PART` ("finished their part, open yours when ready") β€” wrong once **both** are done. Added a dedicated `GAME_RESULTS_READY` type ("Your game results are ready! You both finished β€” tap to see how you compare."). ([PartnerNotificationManager.kt](app/src/main/java/app/closer/notifications/PartnerNotificationManager.kt)) **N5 verified live:** pushed both types to A β€” start rendered "Your partner started a game β€” Tap to join them."; finish rendered "Your game results are ready! You both finished…". The client renders the correct copy for each type. ### N6. 🟠 Deployed functions are STALE for Date Match β€” ⚠️ needs functions deploy Source exports `notifyOnDateMatch`, but the **deployed** function is still the old `createDateMatchOnMutualLove`. `onMessageWritten` is current; the rest predate recent edits (incl. N3–N5). **Action:** `firebase deploy --only functions` (allow it to delete `createDateMatchOnMutualLove`). --- ## Per-game status | Game | Functional | Start notif | Finish notif | |---|---|---|---| | Spin the Wheel | βœ…βœ… live (prior) | via `sessions` trigger* | via `sessions` trigger* | | This or That | βœ…βœ… live (prior) | via `sessions` trigger* | via `sessions` trigger* | | How Well | βœ… fixed (prior) | via `sessions` trigger* | via `sessions` trigger* | | Desire Sync | βœ… fixed (prior) | via `sessions` trigger* | via `sessions` trigger* | | Connection Challenges | βœ… clean (prior) | n/a (completion-based) | n/a | | Date Match | βœ… E2EE + rules (prior) | ⚠️ `notifyOnDateMatch` not deployed (N6) | β€” | | Daily reveal | βœ…βœ… live (prior) | `onAnswerWritten` / `sendPartnerAnsweredNotification` | reveal-ready | \* **All games share one notification trigger:** start writes `couples/{id}/sessions/{sessionId}.status="active"`, finish writes `"completed"`, and `onGameSessionUpdate` fires on that single doc. So N3/N4/N5 fix start+finish notifications for **every** game at once. Pipeline is proven (function writes `notification_queue` + sends FCM; FCM delivery verified in N1); the start-name/finish-both fixes take effect after the deploy in N6. --- ## DEPLOY CHECKLIST (your call β€” prod deploys/admin writes are blocked for the agent) 1. `firebase deploy --only functions` β€” ships N3, N4, N5 (server side) and the `notifyOnDateMatch` rename (N6). 2. `firebase deploy --only firestore:rules` β€” Date Match (`date_swipes`/`date_matches`) + sealed `releaseKeys` sender-read, if not already live. 3. Install the refreshed APK (`Closer-v0.1.0-debug-2026-06-24.apk`) β€” ships N1, N2, N5 (client) + the in-app message bubble. 4. A leftover **active `this_or_that` session** is in `couples/{id}/sessions` from prior testing; it blocks starting a new game until that game is finished (or the doc is removed β€” admin delete needs your authorization). ## Completed earlier (kept for reference, no longer open) - Daily reveal sealed-key exchange (release-key tolerant read, epoch-millis `updatedAt`, idβ†’label mapping) β€” βœ…βœ… verified live. - Game-start crash (`saveSession` empty id β†’ invalid path) β€” βœ…βœ… fixed; This or That verified live. - Game re-entry flicker/re-submit (This or That, How Well, Desire Sync) β€” βœ… pre-check β†’ WAITING. - Daily question determinism + shared `DailyQuestionResolver` β€” βœ…βœ… verified live. - Partner identity (users partner-read rule) β€” βœ…βœ… verified live ("Connected with Sam/QATester"). - Date Match E2EE + rules rewrite β€” βœ… (⚠️ still needs the deploy in checklist #1/#2).