Closer/ClaudeQACoverage.md

115 lines
13 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 — 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: AJ covered. Open: I-001 (P1, outcomes read), J-OBS (P3, touch targets).**
> 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 · D4D6 | ✅ 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 | font scale 2.0, semantics, targets, reduce-motion | ✅ done · J-OBS (P3) ~4245dp targets |
**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. 15 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 (D1D6) — 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).
## Pass J — Accessibility (R8, emulator-5554)
- **Font scaling (font_scale 2.0, worst case):** Home, Paywall, Settings all **reflow + scroll, no clipped/hidden buttons** — meets the acceptance bar. Minor: long subtitles/email ellipsize, bottom-nav labels wrap ("Mess ages"). Restored to 1.0. ✅
- **TalkBack / semantics:** 0 `Icon()` calls without `contentDescription`; 111 explicit `null` (decorative silenced); meaningful labels on all key controls (Back ×26, Send, Close, Dismiss, photo actions, date-swipe Love/Maybe, capsule, edit/delete); loader uses `clearAndSetSemantics` + "Loading…" message. ✅
- **Touch targets:** most controls 48dp; **J-OBS (P3):** a few conversation icon-buttons measure ~4245dp wide (48dp tall) — single-axis marginal, fully operable; bump to 48dp.
- **Reduce-motion (animator_duration_scale 0):** nav sweep + screens work, no hang/unreachable content, 0 FATAL; honored in code across 7 surfaces (LoadingState, CelebrationOverlay, AnswerReveal, DesireSync, ThisOrThat, BrandMessageRotator, LocalQuestionContent). Restored to 1. ✅
- **Contrast:** covered by Pass C both themes (C-DS-001 dark-contrast fixed); precise WCAG ratios need a measurement tool — spot-checks clean, no new dim areas.
- **Keyboard/IME:** text fields validated functionally in Pass G (sign-up/profile); full hardware-keyboard tab-order **deferred** (emulator HW-keyboard harness).
- **Findings:** J-OBS (P3) only; no P0/P1/P2 a11y blockers.
## Pass H
- **H Branding** — deliverable in `ClaudeBrandingReview.md` (consumer brand walk → ready-to-paste art prompts).
---
## 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.
- **R2R4** — play-as-user game restart + fix phase; all P0P2 fixed + verified (archived IDs above).