Closer/ClaudeQACoverage.md

121 lines
14 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 Coverage Matrix
> Resume anchor. Status: `todo | pass | fail(→id) | n/a`. See `ClaudeReport.md` run-state header for current position.
> **Round 6 (branding + Future.md regression) COMPLETE 2026-06-25, client `f47c8e2`:** new surfaces from `95cad84` (white-keyhole icons, animated chip+fill loader, splash, pairing hero) + `f47c8e2` (inclusive gender, turn copy, push-budget split, results-suppression `ActiveGameSessionMonitor`, paywall retry/offline/hide-Continue, auth rotator). **0 new issues; still 0 open P0P3.** Live: loader (both themes), splash→handoff, launcher icon, ToT+How Well open (no crash → #4 VM injection sound), paywall purchase screen (friendly error + Try again + Continue hidden, online→generic msg), onboarding illustration. Unit tests green. Gender step / rotator / turn-copy / results-timing / weekly-cap = code+unit-verified (live deferred: fragile multi-text-field & 2-device timing; low risk over proven patterns). Baseline restored (QA re-signed-in via admin token; couple intact).
> **Round 5 (functions deploy + expanded re-QA) COMPLETE 2026-06-25, client `765916a` + functions DEPLOYED:** E-OBS FIXED+DEPLOYED (12 senders set channelId; chat push → `partner_activity` live) + E-003 results-ready FIXED+DEPLOYED (finished-game → per-session results). **0 open P0P3.** New Pass G (account creation + fake-account) clean. Varied gameplay (Standard/Deep, 0-match) + nav fuzzing — no new bugs. Baseline restored (couple intact, throwaway deleted, Sam re-paired).
> _Round 4: E-003 + B-004 (P2) + A-OBS (P3) FIXED + verified live._
## Pass G — Account creation, validation & fake-account abuse
**R5 live:** sign-up flow end-to-end (email/pw/confirm → profile 3 steps → unpaired home) ✓; weak-password → friendly
"at least 8 characters" error ✓; fresh-account **isolation** (unpaired "Invite my partner", zero couple data) ✓;
**duplicate-email → `auth/email-already-exists`** rejected ✓; invite code **single-use + 24h expiry** shown, **bogus
code "ZZZ-ZZZ" → "Invite not found."** rejected (friendly, not paired) ✓; **recovery phrase** client-generated ✓;
sign-out → onboarding carousel → debug-token restore ✓. **No security findings.** (Rules-level non-member READ denial:
covered by app-level isolation + static member-scoped rules audit; live crafted-request blocked by App Check.)
## Pass A — Couple-shared premium (states: neither / partner-only / self)
| Feature | neither→locked | partner→both unlock | self→unlock | Status |
|---|---|---|---|---|
| Chat media + reactions | pass | pass | pass | pass (couple-shared) |
| Play: Desire Sync | pass | **fail→A-001** | pass | fail→A-001 |
| Play: Memory Lane | pass | **fail→A-001** | pass | fail→A-001 |
| Play: Connection Challenges | pass | fail→A-001 | pass | fail→A-001 |
| Question Packs (premium) | pass | fail→A-001 | pass | fail→A-001 |
| Wheel: Category Picker / Spin / History | pass | fail→A-001 | pass | fail→A-001 |
| Date Match / Plan Date | pass | fail→A-001 | pass | fail→A-001 |
| Subscription screen (own status) | n/a | n/a | n/a | pass (by-design per-user) |
Pass A: **complete** (1 systemic P1). **A-001 FIXED** (e8892a9) — couple-shared everywhere; re-verify each feature in re-QA. New cosmetic A-003 (P3, badge). Subscription screen by-design.
**R3 re-verified LIVE (2026-06-25):** neither→paywall ("Go deeper together"), partner→couple-shared unlock (Sam free entered Desire Sync + Memory Lane), self→unlock; A-003 badges hidden under premium / shown when free (count 0↔2). New A-OBS (P3): paywall plan-load shows raw "credentials issue" error (env: no RevenueCat sandbox).
## Pass B — Games lifecycle (start / play / finish + results)
**RESTARTED 2026-06-24 (R2-B2): full re-run from game #1 with the PLAY-AS-THE-USER mindset** (navigate only via the
real in-app path; report-first-then-workaround on any broken flow). Prior R2 This or That / How Well passes are
superseded — redo every game cleanly. (Prior result for reference: This or That 5/5 ✅, How Well 5/5 ✅.)
**✅ R2-B2 COMPLETE — all 7 games played one full time through on both devices via real user nav; gameplay all PASS.**
Findings surfaced by playing-as-user: **B-001 (P1)** finished session never closes → blocks next game; **C-NAV-001 (P1)**
back from Home resurfaces onboarding/auth; **B-002 (P2)** Home "Play now" → generic hub; **C-CC-001 (P2)** Connection
Challenges dup header/double-back; **C-DS-001 (P2)** Desire Sync dark-mode low contrast; **B-003 (P3)** confusing Desire
Sync counts. Sam reverted to free (baseline). `date_match` push verified live (Pass E bonus).
| Game | starts | plays | finishes/results | no crash | Status |
|---|---|---|---|---|---|
| 1. This or That | pass | **pass (full, user-nav)** | **pass** | pass | **R2-B2: 5/5 via Play hub, answers synced, results match both (4/5 "Two peas in a pod", Q2 Differ correct), no crash ✅. Session-lifecycle bug B-001 (P1) hit on exit.** |
| 2. How Well Do You Know Me | pass | **pass (full, user-nav)** | **pass** | pass | **R2-B2: QA answered 5 (incl. a 1-5 scale Q5); Sam predicted via Play hub — 3 correct + 1 deliberate miss (Kind tone vs Specific examples) + scale match → results show 4/5 "You really know each other" with the wrong one marked ✗ on BOTH devices, scoring accurate, no crash ✅** |
| 3. Desire Sync | pass | **pass (full, user-nav)** | **pass** | pass | **R2-B2: QA(free) entered w/o paywall (A-001 live ✅); both answered 5 Yes/No → exactly 3 mutual desires revealed, mismatches hidden (privacy correct), results match both, no crash ✅. Findings: B-003 (P3 confusing counts), C-DS-001 (P2 dark-mode low contrast on revealed list).** |
| 4. Connection Challenges | pass | **pass (day-cycle, user-nav)** | **pass** | pass | **R2-B2: opened (D-001 rules hold ✅); started Gratitude Week → both completed Day 1 → day ✓, 🔥1 streak, advanced to Day 2 "Both of you showed up today", synced on both, no crash ✅. (7-day series is time-gated; full per-day cycle verified.) Finding: C-CC-001 (P2 duplicate header + double back). Minor: first partner's view shows next-day content + "waiting for partner" before the day is mutually done (self-resolves).** |
| 5. Memory Lane | pass | **pass (create+seal, user-nav)** | **pass (sealed)** | pass | **R2-B2: loads clean (D-001 ✅, no hung heart); QA wrote a capsule (title+body), picked "1 month" → sealed "Opens in 29 days"; **encrypted at rest** (title+content `enc:v1:`, `unlockAt`=+30d, status=sealed); Sam sees the same sealed capsule cross-device; no crash / no PERMISSION_DENIED ✅. Unlock/reveal is future-dated (can't test w/o time-travel). Single header (no C-CC-001 here).** |
| 6. Spin the Wheel | pass | **pass (full, user-nav)** | **pass** | pass | **R2-B2: QA(free) entered (A-001 ✅); spun → "Date Night" category → both answered all 10 prompts (multi-select) → reveal "Here's how you each answered" with per-Q You/partner breakdown matching on BOTH devices, no crash ✅. Wheel session synced (Sam joined QA's active session). Dark answer text a bit dim (C-OBS pattern, readable).** |
| 7. Date Match | pass | **pass (full, user-nav)** | **pass** | pass | **R2-B2: QA(free) entered (A-001 ✅, in Play hub below Question Packs); both swiped date-idea deck (❌/⭐/💗); QA + Sam both liked the same 3 → 3 `date_matches` created (sunrise_hike/kayak/rock_climbing); Sam got "It is a match!" modal + LIVE "It's a match!" push notification; "Your Matches" shows all 3 "Mutual love"; no crash / no PERMISSION_DENIED ✅. (Premium-badged ideas accessible via couple premium.)** |
_Note: stale active session blocked games (B-001); cleared via in-app "End their game" (recovery verified). Exit each game via Back to Play between games so the session closes._
**REQUIREMENT (updated): each game must be played ONE COMPLETE time through on both devices (every step → finish/
reveal/results), not just launched.** All rows above are currently `launch ok / partial` only → **full playthrough
still owed for every game** in Round 2 (premium games need a premium toggle). A launch-only row counts as `partial`, not `pass`.
## Pass C — Visual (light + dark), all ~50 routes
**R3 (2026-06-25):** ~14 screen-types swept in Dark (5554), several in Light (5556 during A/B) — all render clean,
readable, no FATAL, no new 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). C-DS-001 dark-contrast fix holds.
**Back-stack ✅** deep→hub→Home→launcher clean (no double-back; C-NAV-001 holds). C-OBS resolved (debug menu gated).
_Deferred (nav-drift; standard list/detail, lower-risk): 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 (D1D6)
**R7 DEEP DIVE (multi-angle, 2026-06-25):** **D1 at-rest — CLEAN (admin ground-truth read):** 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** (confirmed absent on accepted invite);
plaintext `inviteCode` **not exploitable** (no code-encrypted secret persists; `/invites/{code}` readable only by
inviter). **D3 raw-API negative (LIVE, executed — no longer deferred):** non-member ID token (Identity Toolkit
`signInWithCustomToken`) → Firestore REST on couple doc/conversation/messages/answers/session/capsules/partner-profile
= **all `403 PERMISSION_DENIED`**; non-member writes (couple doc, partner entitlement, **real path
`users/{uid}/entitlements/premium`**) = **all `403` → no self-grant**. Member token reads `200` (characterizes layer:
**App Check not enforced on Firestore — rules are the sole gate, and they hold**). Only writable = cosmetic own-doc
fields (`plan`) that **no gate reads**. **No P0/P1 security findings.** Two hardening notes → `Future.md`.
**R3:** D2 deployed rules re-audited ✅ (B-001 sessions + D-001 capsules/challenges fixes present; hasPremium +
entitlements server-only; ciphertext enforced; no catch-all). D1 at-rest ✅ (chat text + lastMessagePreview =
`enc:v1:`; how_well answers + capsules = `enc:v1:`). D4/D5/D6 unchanged since R1 (code identical) → hold.
**D3 live non-member: deferred** (needs a 3rd fresh account; only 2 emulators, both couple members; rule logic
statically member-scoped). No P0/P1 security findings.
## Pass E — Notifications (17 types × {foreground, background, killed} + tap-to-open)
**R6 live (games + messages, 2026-06-25, build `f47c8e2`):** full live two-device run.
- **chat_message** ✅ end-to-end: Sam→QA (QA bg) posts on **channel=partner_activity**, title "Sam sent a message"
(partner name, not private), body "Tap to read and reply." — **message text NOT in payload** (privacy holds);
small icon = white monochrome mark; tap→**main conversation with content** (verified via the exact intent —
shade-tap is flaky in the adb harness, lands on launcher, but the contentIntent routing is sound).
- **partner_started_game** ✅: QA started This or That → Sam (bg) posts on **channel=game_activity**, "QA is playing /
QA has started a game. Tap to join!" (content-free); tap→**joins the active session** (same 1/5 prompt).
- **partner_finished_game / results** ✅: both finished → **results push DELIVERED to backgrounded QA** (Round 5
couldn't confirm this live) on **channel=game_activity**, "Sam finished the game / Sam finished — tap to see your
results!" (content-free); tap→**per-session This or That results** (5/5), per E-003.
- **#4 results-suppression** ✅: Sam stayed foreground on the session throughout → received **0** notifications
(the partner_completed_part + partner_finished_game pushes to Sam were suppressed by ActiveGameSessionMonitor),
while backgrounded QA got the results push. Clean confirmation of both delivery + suppression.
- No FATAL either device; baseline tidy (0 active sessions, couple intact). **No issues found.**
**R3 live:** FCM tokens valid for both. **chat_message ✅ full chain** (bg deliver + content-free + tap→exact
conversation w/ content). **partner_started_game**: bg deliver + content-free ✅; tap→Play hub (not the game) =
**E-003 (P2)**. **E-OBS (P3)**: bg pushes use fcm_fallback channel. date_match live-verified R2-B2. E-001/E-002 fixes
present in code. Full 17×{fg/bg/killed} matrix not exhaustively run; routing centralized + code-verified for the rest.
## Pass F — Resilience / lifecycle / concurrency / time
**R7 DEEP DIVE:** **concurrency race — FOUND + FIXED F-RACE-001 (P1):** simultaneous game start created 2 divergent
sessions (TOCTOU in `startGameWithCouple`). **Fixed** via atomic transactional create on a per-couple `sessions/_active`
pointer (`startSessionAtomically`) + rule + deploy. **Verified live:** atomic create → 1 session + pointer; sequential
2nd start → joins (1); **parallel-tap race → 1 session (was 2)**; 0 FATAL. **Malformed/abusive deep-link intents**
(unknown type, missing extras, injection/path-traversal) → 0 crash. **Killed-state cold-start** chat deep-link →
conversation loads, 0 crash. Minor follow-up note: the race-loser sometimes lands on the Play hub rather than
WaitingForPartner→"Join the game" (no dup/crash; pre-existing routing).
**R3:** offline (airplane mode) → Today renders from cache, no crash ✅; rotation/config-change → landscape renders,
state preserved, no crash ✅; process-death/restore → ~6 cold restarts all clean to Home (auth persists) ✅;
concurrency → both devices played games simultaneously, sessions synced + B-001 auto-complete on concurrent finish ✅.
Time-gated content (capsule "Opens in 29 days", challenge day-gating) can't be time-traveled — noted.