Closer/ClaudeReport.md

72 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 *"<their own partner-name> 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 ("<name> 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 `"<name> is playing"` even for finish events (shown verbatim when the app is backgrounded). Now type-aware → `"<name> 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. N3N5).
**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).