# Claude QA Coverage Matrix > **Resume anchor — current status only.** Statuses: `pass | fail→id | todo | n/a | not implemented→Future.md | blocked→id`. > Build `23dd6a7`. Position + verdict: see `ClaudeReport.md` run-state. **Verdict: A–G + I covered (I-001 open P1 — outcomes read), J pending.** > Hygiene: this is a *current-status* matrix, not a per-round changelog — `fail→id` flips to `pass` once a fix is > confirmed (ID archived below); finished rounds collapse to the history line. (See Report hygiene in `ClaudeQAPlan.md`.) ## Status at a glance | Pass | Coverage | Status | |---|---|---| | A — Couple-shared premium | all gated features × neither/partner/self | ✅ pass | | B — Games lifecycle | all 7 games played full, 2-device, real user-nav | ✅ pass | | C — Visual (light+dark) | ~14 core screen-types both themes | ✅ pass · deep/list screens **deferred** | | D — Security & encryption | D1 at-rest · D2 rules · D3 live raw-API · D4–D6 | ✅ clean | | E — Notifications | chat + game start/finish/results live, both-client + suppression | ✅ pass · full fg/bg/killed matrix **partial** | | F — Resilience | concurrency · offline · lifecycle · process-death · time | ✅ pass | | G — Account creation / fake-account | sign-up · validation · duplicate · invite-abuse | ✅ pass | | H — Branding & artwork | consumer brand walk → prompts | see `ClaudeBrandingReview.md` | | I — Performance & route efficiency | cold-start, jank (core/conversation/hub), leak proxy, caching | ✅ done · **I-001 (P1)** outcomes read denied | | J — Accessibility | — | **todo (Round 8)** | **Archived issue IDs (fixed + confirmed, detail in git):** A-001 · A-003 · A-OBS · B-001 · B-002 · B-003 · B-004 · C-CC-001 · C-DS-001 · C-NAV-001 · D-001 · E-001 · E-002 · E-003 · E-OBS · F-OBS. Pending one confirm: **F-RACE-001**. --- ## Pass A — Couple-shared premium (neither / partner-only / self) | Feature | neither→locked | partner→both unlock | self→unlock | Status | |---|---|---|---|---| | Chat media + reactions | pass | pass | pass | ✅ pass (reference pattern) | | Play: Desire Sync | pass | pass | pass | ✅ pass | | Play: Memory Lane | pass | pass | pass | ✅ pass | | Play: Connection Challenges | pass | pass | pass | ✅ pass | | Question Packs (premium) | pass | pass | pass | ✅ pass | | Wheel: Category Picker / Spin / History | pass | pass | pass | ✅ pass | | Date Match / Plan Date | pass | pass | pass | ✅ pass | | Subscription screen (own status) | n/a | n/a | n/a | ✅ pass (by-design per-user) | Verified live: neither→paywall ("Go deeper together"); partner→couple-shared unlock (Sam free entered Desire Sync + Memory Lane); self→unlock; premium badges hidden under premium / shown when free. (A-001 couple-shared gap + A-003 badge fixed & confirmed.) ## Pass B — Games lifecycle (start / play / finish + results, 2-device, real user-nav) All 7 played one complete time through on both devices via the real in-app path; gameplay all PASS. | Game | starts | plays | finishes/results | no crash | Evidence | |---|---|---|---|---|---| | 1. This or That | pass | pass | pass | pass | 5/5 via Play hub, answers synced, results match both ("Two peas in a pod"). | | 2. How Well Do You Know Me | pass | pass | pass | pass | QA answered 5 (incl. 1–5 scale); Sam predicted via hub → 4/5, wrong one marked ✗ on both, scoring accurate. | | 3. Desire Sync | pass | pass | pass | pass | QA(free) entered w/o paywall; both answered 5 Yes/No → exactly 3 mutual desires, mismatches hidden, match on both. | | 4. Connection Challenges | pass | pass | pass | pass | Gratitude Week → both did Day 1 → 🔥1, advanced to Day 2 synced. (7-day series time-gated; per-day cycle verified.) | | 5. Memory Lane | pass | pass (create+seal) | pass (sealed) | pass | Capsule sealed "Opens in 29 days", encrypted at rest (title+content `enc:v1:`), cross-device. Unlock future-dated. | | 6. Spin the Wheel | pass | pass | pass | pass | Spun → category → both answered all 10, per-Q You/partner breakdown matches both, session synced. | | 7. Date Match | pass | pass | pass | pass | Both swiped deck, 3 mutual likes → 3 `date_matches`, "It's a match!" modal + live push, "Your Matches" shows all 3. | Note: exit each game via "Back to Play" between games so the session closes (B-001 auto-completion fix verified). F-RACE-001 (simultaneous start) fixed — see Pass F. ## Pass C — Visual (light + dark), all ~50 routes ~14 screen-types swept Dark (5554) + several Light (5556): all render clean, readable, no FATAL, no dark-mode contrast issues; **0 `enc:v1:` leaked to conversation UI**. Covered: Home, Play hub, all 7 game screens (setup/play/reveal), Paywall, Settings (+Subscription +Appearance), Today/daily-question (+answer detail), Messages inbox, Conversation (image+voice+text+reaction). Back-stack clean (deep→hub→Home→launcher, no double-back). - **Deferred** (standard list/detail, lower risk; cover in Round 8): Question Packs detail · Bucket List · Past Games · Wheel History · Answer Reveal (sealed) · Date Builder/Plan Date · fresh-account auth/onboarding/pairing. ## Pass D — Security & encryption (D1–D6) — clean, no P0/P1 - **D1 at-rest (admin ground-truth):** messages `text` + `lastMessagePreview`, all 4 game-answer collections (this_or_that/how_well/desire_sync/wheel, both users), capsule title+content, `date_swipes.actions` = `enc:v1:`; `wrappedCoupleKey` ciphertext (recovery-phrase-wrapped, **argon2id**); `encryptedRecoveryPhrase` server-blind + **wiped on acceptance**; plaintext `inviteCode` **not exploitable** (no code-encrypted secret persists; `/invites/{code}` readable only by inviter). - **D2 rules:** no catch-all, no blanket `if true`; sessions update allowlist + immutable `startedByUserId` + monotonic status; `hasPremium` + entitlements server-only; ciphertext enforced on private fields; capsules/challenges member-scoped. - **D3 raw-API negative (LIVE):** non-member ID token → Firestore REST on couple doc/conversation/messages/answers/session/capsules/partner-profile = **all 403**; non-member writes incl. real `users/{uid}/entitlements/premium` = **all 403 → no self-grant**. Member token reads 200 → **App Check not enforced on Firestore; rules are the sole gate and hold**. - **D4/D5/D6:** wrapped couple key + KDF; App Check (client), gitignored SA JSONs, `allowBackup=false`; analytics metadata-only. Unchanged, hold. - Two hardening notes → `Future.md` (App Check off on Firestore; `users/{uid}` update rule allows arbitrary non-`hasPremium` fields). ## Pass E — Notifications (type × {foreground / background / killed} + tap-to-open, both clients) Full live two-device run (games + messages): - **chat_message** ✅ end-to-end — channel `partner_activity`, title "Sam sent a message" (name, not private), body content-free, **text NOT in payload**; tap → exact conversation with content; white monochrome small icon. - **partner_started_game** ✅ — channel `game_activity`, "QA is playing… Tap to join!" (content-free); tap → joins the active session. - **partner_finished_game / results** ✅ — results push delivered to backgrounded partner, channel `game_activity`, content-free; tap → per-session results (per E-003 fix). - **results-suppression** ✅ — partner foregrounded on the session received 0 pushes (ActiveGameSessionMonitor), while backgrounded partner got the results push. Delivery + suppression both confirmed. - **Deferred (Round 8):** the full 17-type × {fg/bg/killed} matrix isn't exhaustively run live — remaining types are routing-code-verified + centralized in `PartnerNotificationType`; date_match push verified live. New types added to the plan's Pass E inventory (`join_game`, `partner_joined_game`, `game_ended`, `date_plan_update`, etc.) = **todo**. ## Pass F — Resilience / lifecycle / concurrency / time - **Concurrency race:** F-RACE-001 (P1) fixed + **re-confirmed live (R8):** simultaneous mood-tap on both devices → **1 session** (was 2); race-loser landed on WaitingForPartner → **"Join the game"** → joined the winner's session at the **same Q1** (shared reveal preserved). Archived. *(Minor pre-existing note: loser can alternatively land on Play hub; not seen this run.)* - **Offline:** airplane mode → Today renders from cache, no crash. - **Lifecycle:** rotation/config-change → state preserved; ~6 cold restarts → clean to Home (auth persists). - **Robustness:** malformed/abusive deep-link intents (unknown type, missing extras, injection/path-traversal) → 0 crash; killed-state cold-start chat deep-link → conversation loads. - **Deferred (Round 8):** time-travel-gated content (capsule unlock, challenge day-gating); broader network-flaky across answers/dates; account-lifecycle (unpair→re-pair, deletion cascade) deep run. Minor note: race-loser sometimes lands on Play hub vs WaitingForPartner (no dup/crash; pre-existing routing). ## Pass G — Account creation, validation & fake-account abuse Sign-up end-to-end (email/pw/confirm → 3-step profile → unpaired home) ✅; weak-password → friendly "at least 8 characters" ✅; fresh-account isolation (zero couple data) ✅; **duplicate-email → `auth/email-already-exists`** rejected ✅; invite single-use + 24h expiry, **bogus code → "Invite not found."** ✅; recovery phrase client-generated ✅; sign-out → onboarding → debug-token restore ✅. **No security findings.** (Non-member READ denial = live D3 above + app-level isolation.) ## Pass I — Performance & route efficiency (R8, build `23dd6a7`, emulator-5554, debug build) Route smoke-test checklist (re-runnable: `dumpsys gfxinfo closer.app reset` → drive route → read `gfxinfo`): | Route / list | Jank / latency | Notes | |---|---|---| | Cold start → Home | 1253ms to first frame | acceptable (debug; release AOT-faster); no skipped frames, no ANR | | Core tabs (Home/Today/Play/Messages/Settings) | 6.3% janky frames | smooth; no Choreographer-skip spam | | Conversation (realtime listener) scroll | 90th 36ms / 95th 53ms | minor debug hitching; **no leak** | | Play hub scroll | 90th 36ms / 95th 38ms | smooth | - **Caching / lazy-load:** LazyColumn/Row/Grid in 17 files; Coil (AsyncImage) in 11; Room DAOs cache static question/category data locally — all in place, no load-all anti-patterns seen. - **Leak check:** conversation open/close ×6 → ViewRootImpl=1, Activities=1, Views +2, PSS bounded after trim → no window/Activity/listener leak. - **Redundant reads:** precise per-read counts need an instrumented/Perfetto build (Firestore success reads aren't in adb logcat); no failing-read spam **except I-001**; no leaked listeners. - **Finding: I-001 (P1)** — `getOutcomes()` bare-list query is rules-denied → "Your Progress"/outcomes silently broken (see ClaudeReport.md). ## Passes H / J - **H Branding** — deliverable in `ClaudeBrandingReview.md` (consumer brand walk → ready-to-paste art prompts). - **J Accessibility** — **todo (Round 8):** font_scale 1.3/1.5/2.0, TalkBack semantics, WCAG-AA contrast, 48dp targets, keyboard, reduce-motion. --- ## Round history (one line each) - **R7** — security/concurrency deep dive (multi-angle): cornerstone clean; F-RACE-001 found+fixed+verified. 0 new open. - **R6** — branding drop + Future.md backlog regression: 0 new open. - **R5** — Cloud Functions deployed (E-OBS/E-003) + new Pass G clean: 0 open. - **R2–R4** — play-as-user game restart + fix phase; all P0–P2 fixed + verified (archived IDs above).