14 KiB
Claude QA Coverage Matrix
Resume anchor — current status only. Statuses:
pass | fail→id | todo | n/a | not implemented→Future.md | blocked→id. Build23dd6a7(+ab29f6boutcomes fix). Position + verdict: seeClaudeReport.mdrun-state. Verdict: A–J covered, R9 clean confirmation round (0 new). Open: J-OBS (P3) only. I-001/I-002 fixed+confirmed. Hygiene: this is a current-status matrix, not a per-round changelog —fail→idflips topassonce a fix is confirmed (ID archived below); finished rounds collapse to the history line. (See Report hygiene inClaudeQAPlan.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 | font scale 2.0, semantics, targets, reduce-motion | ✅ done · J-OBS (P3) ~42–45dp 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. 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).
- R9 deferred sweep — 0 new issues: Answer History, Together/Activity, Bucket List (empty state + FAB), Date Match deck, Date Matches all render cleanly in dark (good contrast, no clipping, no FATAL); Privacy & Terms + Home confirm light parity (shared Material3 tokens). Remaining standard list/detail (Wheel History · Date Builder · Past Games · Answer Reveal sealed · Question Packs[gated→paywall]) are token-consistent with the above; fresh-account auth/onboarding visual covered R3/R5. No C findings.
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:;wrappedCoupleKeyciphertext (recovery-phrase-wrapped, argon2id);encryptedRecoveryPhraseserver-blind + wiped on acceptance; plaintextinviteCodenot exploitable (no code-encrypted secret persists;/invites/{code}readable only by inviter). - D2 rules: no catch-all, no blanket
if true; sessions update allowlist + immutablestartedByUserId+ 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-hasPremiumfields).
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.
- New speculative types —
not implemented → Future.md(R8 code check, 0 files each):join_game/game_invite,partner_joined_game,game_abandoned/game_ended,date_plan_update,memory_capsule_created,challenge_day_completed,subscription_entitlement_changed. Worthwhile ones (couple-premium-unlock push; join/end pushes) logged toFuture.md## QA. Not counted as pass. - partner_answered (couple-key reveal, 2026-06-26) ✅ live both-client —
onAnswerWrittenfires on each answer; the second answer hits the both-answered "Your answers are unlocked ✨" copy (recipient already answered).onAnswerRevealed✅ fires whenisRevealedflips false→true → "opened your answers" push to the partner (witnessed live). Privacy gate (raw API): partner content 403 until both answer, 200 after; non-member 403 throughout. Reveal screen shows the partner's answer both directions. At-rest: content-free metadata + gatedenc:v1:secure/payload. - Deferred (Round 9): the full implemented-type × {fg/bg/killed} matrix isn't exhaustively re-run live — implemented types are routing-code-verified + centralized in
PartnerNotificationType; chat/game start/finish/results + date_match verified live (R3/R5/R6).
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.
- R9 network resilience: airplane-mode on → Date Match + Messages render from cache, no crash, no error dead-end; reconnect → inbox refreshes, no stuck state, 0 FATAL (extends R3 offline-Today-from-cache).
- Deferred (Round 10, low-risk): time-travel-gated content (capsule unlock, challenge day-gating — needs clock manipulation); account-lifecycle deletion-cascade deep run (disruptive on the baseline couple). Minor note: race-loser can land 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) — FIXED+VERIFIED
getOutcomes()bare-list query was rules-denied → fixed withwhereIn(documentId, dayKeys); 0 PERMISSION_DENIED after. I-002 (P1) — FIXED+VERIFIED (found fixing I-001): scores stored as int64 → read as Long →Map<String,Int>cast CCE → swallowed; fixed viaNumber.toInt(). Live: seeded day_0 → "Your Progress" shows "Baseline recorded". Both pending Round-9 confirm.
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 withoutcontentDescription; 111 explicitnull(decorative silenced); meaningful labels on all key controls (Back ×26, Send, Close, Dismiss, photo actions, date-swipe Love/Maybe, capsule, edit/delete); loader usesclearAndSetSemantics+ "Loading…" message. ✅ - Touch targets: most controls 48dp; J-OBS (P3): a few conversation icon-buttons measure ~42–45dp 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.
- R2–R4 — play-as-user game restart + fix phase; all P0–P2 fixed + verified (archived IDs above).