123 lines
18 KiB
Markdown
123 lines
18 KiB
Markdown
# Claude QA Coverage Matrix
|
||
|
||
> **Resume anchor — current status only.** Statuses: `pass | fail→id | todo | n/a | not implemented→Future.md | blocked→id`.
|
||
> Build `2cd0af6` + R11 working-tree art fixes (Theme/BrandIllustration/EmptyState). Position + verdict: see `ClaudeReport.md` run-state. **Verdict: R11 confirmation round COMPLETE — FLAWLESS. 0 open P0–P2. Fixed C-DARKART-001 (P2, dark art follows in-app theme) + C-ART-EDGE-001 (P3, feathered edges), verified live both decoupled theme directions. 5 R10 P2 fixes re-confirmed + pruned. Entrypoint smoke 6/6 green. Only open: J-OBS (P3 touch targets) + 2 freshly-fixed art items pending 1 confirm.**
|
||
>
|
||
> **📖 Architecture reference:** see [`docs/Engineering_Reference_Manual.md`](docs/Engineering_Reference_Manual.md) — contains architecture, security model, data model, and the [Known landmines](docs/Engineering_Reference_Manual.md#known-landmines-and-recent-fixes) section that backs every fix-and-pruned ID below.
|
||
> 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 | R12: code audit (all gates→CouplePremiumChecker) + live couple-shared unlock (Sam prem→QA free unlocks Desire Sync setup + Memory Lane badge) | ⚠️ 1 P1 (A-201 Date Match premium ideas ungated — free user liked a ★Premium idea, no paywall); all other gates couple-shared ✅ |
|
||
| B — Games lifecycle | R12: 4 async games full 2-device end-to-end (ToT Light×5, Wheel mixed-types, How Well asym, Desire Sync shared/private) + start/join/first-finisher/finish/results/back-stack; CC+MemoryLane+DateMatch render/core (full per R10) | ✅ pass (first-finisher nudge + C-NAV-002 + Ready=Start re-verified live; MemoryLane title/preview run-on → Future.md) |
|
||
| C — Visual (light+dark) | R12: Messages(inbox+conv) both themes, Today, Subscription + organic A/B sweep (Home/Play/all game screens/Security/MemoryLane/DateMatch/Paywall) + R11 decoupled-theme art; back-stack spot-checks OK | ✅ regression-clean vs R10 full sweep · NEW **C-ART-EDGE-002 (P3)** direct-call hero art (daily-question, couple_subscription, etc.) hard edges on dark · C-DARKART-001+C-ART-EDGE-001 (shared helpers) hold |
|
||
| D — Security & encryption | R12 LIVE: D1 (4 game collections enc:v1:) · D3 raw-API non-member 403×4 + member-scoped + messages not enumerable · D5 self-grant premium PATCH→403; D2/D4/D6/D7 carried R7/R10 (rules unchanged, no deploy) | ✅ clean — cornerstone holds; bounds A-201 (server blocks real premium self-grant) |
|
||
| E — Notifications | R10 live: E-GAME-002 confirmed (start push+banner+Join), finish/answer/reveal pushes | ✅ pass · E-GAME-002 pruned · full fg/bg/killed matrix **partial** |
|
||
| F — Resilience | R10: concurrency double-start→1 session · process-death→clean+FCM re-register · offline(R9) | ✅ pass · time-travel + deletion-cascade deferred |
|
||
| G — Account creation / fake-account | R10: abuse live via D3 (non-member denied, no self-grant) + invite rules; happy/validation R5-clean (unchanged) | ✅ pass |
|
||
| H — Branding & artwork | R10: existing-art integration clean (0 defects), new game surfaces on-brand | ✅ see `ClaudeBrandingReview.md` |
|
||
| I — Performance & route efficiency | R10: core-tabs jank 5.53% (R8 6.3%), 90th 32ms, leak proxy bounded | ✅ no regression |
|
||
| J — Accessibility | R10: font scale 2.0 reflows (new hero ok), reduce-motion×7 | ✅ 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).
|
||
- **R10 re-sweep (both themes where relevant):** Messages inbox ✅ (dark+light: conversations, avatars, unread dot, previews decrypted, no `enc:` leak), Conversation ✅ (image/voice/text/reaction/read-receipt/date-sep, E2E lock glyphs, correct attribution both dirs), per-question Discussion thread ✅, Today/daily-question ✅ (dark+light, paired-books art on-brand), Activity/Together ✅ (dark). **5 P2 found:** C-HOME-001 (Home shows top pending action twice — `primaryAction` hero + `buildPendingActions` row), C-NAV-002 (wheel results→BACK re-enters finished play screen, no popUpTo), C-NAV-003 (Wheel History/Past Games + PartnerHome double app-bar — route in `shellBackRoutes` while screen owns a TopAppBar; C-CC-001 class), C-PW-001 (dark paywall "What's included" pills light-on-light, `BenefitPill` onSurface text), C-SEC-001 (accepter recovery copy). Premium-locked Wheel History state renders. Date Builder · Question Packs(gated→paywall) · Answer Reveal sealed = token-consistent, R9-clean.
|
||
- **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:`; `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.
|
||
- **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 to `Future.md` `## QA`. Not counted as pass.
|
||
- **partner_answered (couple-key reveal, 2026-06-26)** ✅ live both-client — `onAnswerWritten` fires on each answer; the second answer hits the **both-answered "Your answers are unlocked ✨"** copy (recipient already answered). `onAnswerRevealed` ✅ fires when `isRevealed` flips 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 + gated `enc: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 with `whereIn(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 via `Number.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 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 ~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)
|
||
- **R11** — confirmation round, FLAWLESS (0 open P0–P2): fixed C-DARKART-001 (P2, art follows in-app theme via `LocalAppInDarkTheme` + config-overridden context) + C-ART-EDGE-001 (P3, edge feathering) in shared `BrandIllustration`/`EmptyState`, verified live both decoupled theme directions (system-light+app-Dark→dark art · system-dark+app-Light→light art), 0 FATAL; re-confirmed + pruned the 5 R10 P2 fixes (C-HOME-001/C-NAV-002/C-NAV-003/C-PW-001/C-SEC-001); entrypoint smoke 6/6 green on fresh APK (launcher + 5 notif cold-starts open & stay). Art fixes in working tree; rest committed `2cd0af6`.
|
||
- **R10** — FULL run A–J + fix phase: 5 P2 found+fixed+verified-live (C-HOME-001 dup card · C-NAV-002 wheel back-stack · C-NAV-003 dup app bar · C-PW-001 dark paywall · C-SEC-001 recovery wrong-store); E-GAME-002 confirmed live (start push+banner+Join) & pruned; concurrency double-start→1 session; security D1–D7 clean; perf/a11y no regression. 0 open P0–P2 (5×P2 pending 1 confirm).
|
||
- **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).
|