155 lines
32 KiB
Markdown
155 lines
32 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 = **R18 working tree** (uncommitted: `MainActivity.kt`, `MemoryCapsuleGenerator.kt`+test, asset `app.db` grammar fix, docs); debug APK rebuilt+installed both emulators. Position + verdict: see `ClaudeReport.md` R18 run-state. **Verdict: R18 — fixed the last open visual P2 C-DARKART-002** (uiMode-sync in `MainActivity` via `AppCompatDelegate.setDefaultNightMode` so ALL art follows the in-app theme; verified live across all 4 theme/art states) + flaky **TEST-002** (capsule determinism — injected clock) + content **P-GRAMMAR-001** (13 stress-Q subject-verb agreement errors → asset data fix). Live passes this round: **A** (premium gate, Desire Sync + premium pack → Paywall; free content reachable), **B** (Wheel playthrough end-to-end), **L** (chat E2E send/receive-decrypt/at-rest/receipt/no-leak), **E** (backgrounded FCM delivery, privacy-safe, deep-links to chat), **M-001 confirmed** (client mirror intact). **R18b (Future.md review): found+fixed a P0 — O-ONBOARD-001** onboarding/auth crash on EVERY fresh install (`painterResource` on the `<bitmap>` `ic_launcher_foreground`, a regression from icon-redesign commit 334cb07; invisible to logged-in QA emulators). Verified live before/after on fresh 5558 (API 34); fixed both `OnboardingScreen.CtaSlide` + `AuthVisuals.AuthLogoMark` → raster `closer_launcher_foreground`; added `scripts/painter-xml-scan.sh` guard (proven). Also closed BucketList FAB hardcoded color. **Board: 0 open P0/P1 · 1 open P2 (O-AGE-001 pre-ship age gate) · 3 open P3 (BRAND-DARK-COVERAGE, BRAND-ICON-CUSTOM, C-ORIENT-001); fixed-pending-1-confirm: O-ONBOARD-001, C-DARKART-002, M-001, TEST-002, P-GRAMMAR-001, BucketList-FAB.** 0 FATAL.
|
||
>
|
||
> **Scope expanded (plan review):** the playbook now has first-class passes **K–O** (billing money-path · messaging/chat E2E · functional settings · daily-Q/outcomes/interactive · release-build/store-readiness). These surface **coverage GAPS, not defects** — the recurring defect bar is clean, but **K (real purchase/restore/cancel path), L (full chat), M (settings take-effect), N (outcomes/Bucket-List/Date-Builder), O (minified release + App Check + store)** are `todo`/`partial`/`blocked→needs-device`. **Next-priority work = close these (start L + M on-emulator; K + O need a real device / pre-ship).** **Device/OS matrix = `blocked→needs-device` (pre-ship):** all per-round QA runs on two **identical** emulators (5554/5556, same API + screen) — minSdk/targetSdk · small/large screen · ≥1 physical device are NOT covered; don't claim "device matrix ✓".
|
||
>
|
||
> **First-run / cold-path = `blocked→fixture` (run the fresh-install lane):** the recurring emulators are **paired + signed-in + onboarding-complete**, so the recurring passes **cannot reach onboarding / sign-up / login / auth-logo / pairing / new-device-recovery / day-1 empty states** — this is the fixture blind-spot that hid **O-ONBOARD-001** (P0, every fresh install crashed). Cover it on a **throwaway** device (e.g. `emulator-5558` / fresh AVD — never `pm clear` 5554/5556) on any onboarding/auth/pairing/branding/`res/drawable` change + pre-ship. Don't claim "first-run ✓" off the logged-in fixtures.
|
||
>
|
||
> **📖 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 | R18: re-verified live (Sam free) — Desire Sync + premium **Boundaries** pack → Paywall; Mixed pack free prompt opens (gate not over-broad); Free filter graceful empty. R13 A-201 (Date Match) holds. | ✅ pass (multi-surface gate re-confirmed R18) |
|
||
| 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) | R16: 9 theme-scan hits triaged → **3 reclassified** (1 @Preview false-pos [scanner now excludes], 2 dead `PlaceholderScreen` deleted) + **6 real FIXED** (BucketList/DateMatch/WheelHistory/QuestionThread tokens); theme-scan CRITICAL **9→0**. R18: **C-DARKART-002 FIXED** (all art follows in-app theme via uiMode-sync) — verified all 4 theme/art states; incidental confirms paywall/pack/Today/chat both directions. | ✅ theme-scan CRIT 0 · C-DARKART-002 fixed (pending 1 confirm) · ⚠️ **BRAND-DARK-COVERAGE (P3) open** — light-only illustrations, see `ClaudeBrandingReview.md` |
|
||
| D — Security & encryption | R13 LIVE (rules/functions unchanged this session): D3 non-member GET couple+messages → 403; D5 self-grant entitlement PATCH → 403; member GET own couple → 200; D1 chat at-rest `enc:v1:`. D2/D4/D6/D7 carried R7/R10 | ✅ clean — cornerstone holds |
|
||
| E — Notifications | **R18b LIVE full suite:** cold-start crash-triage smoke **6/6 both emulators** (launcher + 5 push types killed→tap→opens&stays); **routing verified background→tap for 7 types on Sam + 3 on QA (both clients)** — chat→exact conversation, answered/daily_question→Today, started_game/completed_part→game screen, finished_game→per-session results, date_match→Matches; **foreground** game-start banner + chat bubble ✅; **malformed/stale** intents all graceful; **payload privacy** P0 clean (code audit of all 6 trigger payloads + at-rest D1 all `enc:v1:`). 0 FATAL. R18 warm chat deep-link carried. | ✅ pass · delivery + routing + privacy + both-client confirmed R18b · Doze/battery = needs-device |
|
||
| F — Resilience | R12: concurrency (F-RACE-001 atomic-start code + R8 live) · process-death (smoke `am kill`×5 → push → cold-start recovered each) · 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 | R13: ToT redesign + Premium-unlock modal done. **2026-06-27 brand audit opened 2 backlogs.** | ⚠️ **BRAND-DARK-COVERAGE** (light-only illustrations need dark variants) + **BRAND-ICON-CUSTOM** (~60 generic Material icons → bespoke `glyph_*`) — full asset lists in `ClaudeBrandingReview.md` |
|
||
| I — Performance & route efficiency | R10: core-tabs jank 5.53% (R8 6.3%), 90th 32ms, leak proxy bounded | ✅ no regression |
|
||
| J — Accessibility | R13: **J-OBS FIXED + verified live** (composer media/voice/retry buttons → 48dp; measured 126px=48dp both axes); font scale 2.0 reflows + reduce-motion×7 (R10) hold | ✅ done · J-OBS fixed (pending 1 confirm) |
|
||
| K — Billing & subscription lifecycle | Gate (couple-shared unlock) verified via admin toggle (Pass A) + Premium-unlock modal + `onEntitlementChanged` push live (R13/R14). **Real money path (purchase/restore/cancel→expiry-relock/refund/plan-switch) NOT tested** — needs a real device + Play sandbox. | ⚠️ **todo** — money path `blocked→needs-device`; gate ✅ |
|
||
| L — Messaging & chat (E2E) | R15: conversation render driven live — decrypt **both dirs**, attribution, timestamps, **Seen** receipt, ❤️ **reaction**, ordering, day-separators, voice-note + image bubbles, E2E composer lock glyphs; inbox decrypted previews **no `enc:` leak**; live QA→Sam send delivered; at-rest `enc:v1:`. **R18 re-verified live round-trip** (Sam→QA: received + decrypted on partner, `enc:v1:`(79) at rest, marker absent from server docs = no leak, Seen receipt). Remaining: failed-send/offline retry, delete-message, fresh image/voice send, Discuss-thread live send. | ✅ **pass (core)** — re-confirmed R18; 4 sub-items carry |
|
||
| M — Settings & account management | R15: **M-001 (quiet hours) FIXED + verified live** (server-side fail-open suppression); per-type notif toggle take-effect confirmed live (server-enforced; field flips in Firestore; toggle-off → 0 delivery); theme/DataStore persistence across relaunch ✅; biometric lock code-sound (cold-start re-lock; background-resume observation → Future.md). Remaining: edit-profile persist, unpair/delete-cascade (disruptive — deferred). **R18: M-001 re-confirmed** — toggling QH writes the client mirror (`quietHoursEnabled`/`StartMinutes 1320`/`EndMinutes 480`/`timezone`) to `users/{uid}` correctly; server suppression deployed + R15-verified. Recommend prune. | ✅ **pass (core)** — M-001 confirmed (prune next); unpair/delete deferred |
|
||
| N — Daily Q / reveal / check-ins / interactive | R15 (driven): daily-Q + **reveal both-answered gate** ✓; **Bucket List CRUD FIXED+verified (N-001)** — add(`enc:v1:`)/complete/delete/list; **Date Builder FIXED+verified (N-002)** — Create Plan → PLANNED `date_plan` (`enc:v1:`) → Home "Date coming up"; Outcomes/Your Progress code-correct (resolves coupleId, submits); Activity feed render-checked (prior). | ✅ **pass** — N-001 + N-002 fixed (pending 1 confirm) |
|
||
| O — Release build & store readiness | **Not started.** All QA to date is on the **debug** APK. Minified release build, signing/AAB, App Check enforcement, i18n/RTL, App-Links, Play Data-Safety = pre-ship gate, not yet run. | ❌ **todo (pre-ship gate)** |
|
||
| P — Content, copy & language | R15: UI-microcopy swept (warm/inclusive; debug rows `BuildConfig.DEBUG`-gated; friendly error fallbacks; on-brand privacy copy) + **question-bank audit live: 6103 Qs — 0 empty, 0 exact dupes, 0 placeholder tokens, complete/mutually-exclusive answer configs, good type variety, consent-framed sensitive content.** No typos/off-voice/non-inclusive copy found. **R18: found+fixed P-GRAMMAR-001** — in-game wheel surfaced a subject-verb agreement error; bank scan found **13 stress-Q** from one template family where plural subjects hit a singular "{x} is …" frame ("busy weeks/health worries/… is affecting you"); fixed the 13 rows in asset `app.db` (data-only); root fix belongs in the content generator. | ✅ **pass** — copy clean; P-GRAMMAR-001 fixed (asset) + grammar-audit recommended |
|
||
|
||
**Archived issue IDs (fixed + confirmed, detail in git):** A-001 · A-003 · A-201 · A-OBS · B-001 · B-002 · B-003 · B-004 · C-CC-001 · C-DARKART-001 · C-DARK-UI-001 · C-DARK-UI-002 · C-DARK-UI-003 · C-DS-001 · C-ART-EDGE-001 · C-ART-EDGE-002 · C-HOME-001 · C-NAV-001 · C-NAV-002 · C-NAV-003 · C-PW-001 · C-SEC-001 · D-001 · E-001 · E-002 · E-003 · E-GAME-002 · E-GAME-003 · E-OBS · F-OBS · F-RACE-001 · I-001 · I-002 · J-OBS. **R15: 9 open P2 theme defects (C-THEME-001..009); 3 fixed pending 1 confirm (M-001 quiet hours, N-001 Bucket List, N-002 Date Builder); 2 open P3 brand backlogs.**
|
||
|
||
---
|
||
|
||
## 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).
|
||
- **Theme-scan execution tracking (MANDATORY):** every Pass C round must record the counts exactly as
|
||
`scripts/theme-scan.sh` reports them in its `## Summary` section, then file the appropriate items:
|
||
| Round | Scanner run | CRITICAL | MAJOR | REVIEW | CRITICAL filed to ClaudeReport.md | MAJOR filed (if any break themes) |
|
||
|---|---|---|---|---|---|---|
|
||
| R15 | ✅ | 9 | 8 | 32 | ✅ (C-THEME-001..009) | — |
|
||
| R16 | — | — | — | — | — | — |
|
||
|
||
R15 scanner: 9 CRITICAL hardcoded surface/background colors filed as C-THEME-001..009; 8 MAJOR (1 component
|
||
color override + 7 direct `painterResource` hits) and 31 REVIEW items deferred to the visual sweep. Update this
|
||
row if any MAJOR is promoted to a filed defect.
|
||
- **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.
|
||
- One hardening note → `Future.md` (App Check off on Firestore). _(R15 correction: `users/{uid}` update rule enforces a **field allowlist** — not arbitrary; extended R15 for `quietHours*`+`timezone`.)_
|
||
|
||
## 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).
|
||
- **R18b (2026-06-28) — full live re-run, 0 FATAL:**
|
||
- **Cold-start crash-triage smoke 6/6 on BOTH emulators** (`qa/entrypoint_smoke.sh`): launcher + `partner_started_game`/`partner_completed_part`/`partner_finished_game`/`chat_message`/`partner_answered` each killed (`am kill`) → real push → tapped from shade → **opens & stays** (0 fail, 0 blocked). This is the shared cold-start path (splash/onCreate) where the splash-exit crash class hid — clean.
|
||
- **Routing (background→tap, landed-screen verified, not just "opens"):** Sam received all 7 types; QA received 3 (both-client). chat_message(conv=main)→**exact conversation thread** (composer + Seen); partner_answered & daily_question→**Today/daily-question**; partner_started_game & partner_completed_part(tot)→**game screen**; partner_finished_game(wheel,session)→**per-session wheel results** ("Here's how you each answered", completed→results not a dead active session); date_match→**"Your Matches"**. Every tap: correct destination, app alive, **0 FATAL**.
|
||
- **Foreground in-app delivery (onMessageReceived intercepts; no OS tray):** `partner_started_game`→**in-app banner** "Your partner started a game · This or That" with Join/dismiss ✅; `chat_message`→**draggable chat-head bubble** ✅ (verified via real open-thread→back→Home→send flow + a distinct conv id; the `conversation_id=main` suppression seen on a process-death-restored back stack is **by-design read-suppression**, `ActiveThreadMonitor`, that clears on normal back-nav — not a defect).
|
||
- **Malformed / stale intents (all graceful, 0 FATAL):** unknown type→no nav, no crash; chat_message w/o `conversation_id`→**Messages inbox** (fallback); partner_started_game w/o `game_type`→**Play hub** (fallback); partner_finished_game w/ **deleted session id**→graceful "waiting" state w/ Back-to-Play/End escape (no crash, no dead-end).
|
||
- **Payload privacy (P0) — code audit of every sender + at-rest D1:** `onMessageWritten`, `onGameSessionUpdate` (+ part-finished), `onAnswerWritten`, `onAnswerRevealed`, `createDateMatch`, `onCoupleLeave` — each `data` block carries only routing IDs (type, couple_id, conversation/question/game/session id) + optional **public** avatar URL; titles use only the partner's display name; bodies are static. **No message/answer/date/swipe content, no keys/invite codes/recovery phrases.** At-rest cross-check: latest 6 `conversations/main/messages` all `enc:v1:` (server-blind source). Cross-checks D6.
|
||
- **Real `onMessageWritten` live re-drive NOT re-run this round** (UI-automation thrash on the composer send button) → carried from **R18 live** (exact copy "Sam sent a message"/"Tap to read and reply", no content) + this round's payload code audit + at-rest D1.
|
||
- **Doze / battery-optimization / App-Standby delivery → `blocked → needs-device`** (emulators never enter these states; the #1 real-world "notifications don't work" cause). Run on a physical device before any store push (`dumpsys deviceidle force-idle`, app Optimized→Restricted).
|
||
|
||
## 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)
|
||
- **R15** — gap-closing round (Passes L/M/N/P + regression smoke); **3 bugs found, 2 fixed.** **M-001 (P2 quiet hours)** — local-only window didn't suppress backgrounded/killed partner pushes; fixed via server-side fail-open `recipientInQuietHours()` in the 4 partner-action senders + client window/tz sync + rules allowlist; verified live (fn log suppress vs notify); deployed prod. **N-001 (P1) Bucket List fully non-functional** (coupleId never set → all CRUD no-ops) — FIXED (VM resolves couple in init) + verified live (add `enc:v1:`/complete/delete/render). **N-002 (P2) Date Builder "Create Plan" no-op** (wrote to unread prefs collection; ids never wired) — FIXED (re-pointed to create a PLANNED `DatePlan` via `savePlan` → Home "Date coming up") + verified live. L chat-core, P copy+question-bank (6103 Qs) clean; smoke 6/6 GREEN. Corrected stale "users/{uid} allows arbitrary fields" claim (there's an allowlist).
|
||
- **R14** — full fresh A–J ClaudeQAPlan run (pure QA, no code), FLAWLESS, 0 new findings: confirmation round on the R13 build — premium enforcement + couple-shared unlock + entitlement push (live); Desire Sync/How Well/Spin-the-Wheel full 2-device + first-finisher nudge; Memory Lane create+seal, CC resume, Date Match deck; decoupled-theme-art mandate; cornerstone live (403s + enc:v1:); offline + process-death; jank 5.25%; J-OBS 48dp holds. The 5 R13 fixes held → pruned (archived line).
|
||
- **R13** — open-backlog fix pass + full fresh A–J, FLAWLESS (0 open P0–P3): fixed C-DARK-UI-001 (ToT dark redesign), C-DARK-UI-002 (check-in label), C-DARK-UI-003 (bottom insets), C-ART-EDGE-002 (8 opaque heroes feathered), J-OBS (48dp targets); confirmed A-201 live→pruned; shipped Premium-unlock modal (one-time, both partners, couple-shared, verified live). Pass D cornerstone re-verified LIVE (non-member 403, self-grant 403, member 200, at-rest enc:v1:). Diff UI-only → E/F/G carried. 0 FATAL both emulators.
|
||
- **R12** — FRESH FULL A–J run + fix phase, FLAWLESS (0 open P0–P2): found+fixed **A-201** (P1 Date Match premium bypass — gated via CouplePremiumChecker→Paywall, verified live); 4 async games full 2-device E2E; security cornerstone live-clean (non-member 403 read+write, self-grant 403); smoke 6/6; jank 4.10%; new P3 C-ART-EDGE-002 (hero edges, deferred); C-DARKART-001+C-ART-EDGE-001 held→pruned; Pass A retrospective added (badge≠gate).
|
||
- **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).
|