6.7 KiB
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)
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)
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)
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)
N5. 🟡 Finish copy was wrong (title + client mapping) — ✅✅ FIXED & VERIFIED
- FCM title was hard-coded
"<name> is playing"even for finish events (shown verbatim when the app is backgrounded). Now type-aware →"<name> finished the game". (onGameSessionUpdate.ts) - Client mapped
partner_finished_game→PARTNER_COMPLETED_PART("finished their part, open yours when ready") — wrong once both are done. Added a dedicatedGAME_RESULTS_READYtype ("Your game results are ready! You both finished — tap to see how you compare."). (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)
firebase deploy --only functions— ships N3, N4, N5 (server side) and thenotifyOnDateMatchrename (N6).firebase deploy --only firestore:rules— Date Match (date_swipes/date_matches) + sealedreleaseKeyssender-read, if not already live.- Install the refreshed APK (
Closer-v0.1.0-debug-2026-06-24.apk) — ships N1, N2, N5 (client) + the in-app message bubble. - A leftover active
this_or_thatsession is incouples/{id}/sessionsfrom 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 (
saveSessionempty 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).