46 KiB
Claude QA Coverage Matrix
Resume anchor — current status only. Statuses:
pass | fail→id | todo | n/a | not implemented→Future.md | blocked→id. R25 (2026-06-30) — full fresh run on the new R24 E2EE backup/restore surface + cornerstone regression; 0 new defects. Cheap gates green (unit 244 · fn 38 · theme-scan CRIT 1=false-pos [HomeScreen:829 brand count pill] · painter-xml 0 · wiring 🔴0 · cold-start smoke 6/6 both · render smoke 4/4). Pass D CLEAN (deep on backup/restore): at-rest manifest=pointers-only, Storage snapshot=enc:v1:(16KB, server-blind), restore_requests=0; rules member-scoped + keybox bound to other member + immutable pubkey; D3 live negative all-denied (backup manifest/chunks/restore_requests + create/write = 403/400; original couple/self-grant = 403); cross-user restore via tokenized capability URL verified (plain GET→200+enc:v1:); R24 storage.rules deploy gap RESOLVED. Pass E:restore_requestedpartner push deployed + firing (Sam queue: 3 today) → RESTORE_CONSENT; R24-b functions NOT deployed (onRestoreFulfilledabsent, nolastRestoreSelfAlertAt, 0restore_self_alert) → self-alert + completion alert =blocked→deploy. Pass M: new Security entries live-verified this session (recovery reveal on no-lock, Copy+IS_SENSITIVEmask, Help-my-partner-restore + back). Cornerstone regression (Sam 5556): A paywall gate ✓, B Play-hub cards+badges ✓, L inbox+thread fully decrypted no-leak ✓, N daily-Q decrypted+reveal ✓, 0 FATAL. ⚠️ PROCESS LANDMINE (I caused):connectedDebugAndroidTestUNINSTALLS+WIPES the app-under-test → wiped QA(5554) data → QA at fresh onboarding (O-ONBOARD-001 stays fixed). NEVER run instrumented tests on 5554/5556 fixtures — use throwaway 5558. QA fixture recovery =blocked→user(password/re-auth needed). User actions to close: (1)firebase deploy --only functions✅ DONE (functions deployed by user; both self-alerts live-validated R25-c). (2) restore QA fixture ✅ DONE R25-b. R25-b (2026-06-30) — QA(5554) fixture RECOVERED; live 2-device partner-assisted restore verified end-to-end, 0 defects. Password reset via admin (user-authorized) → QA signed in → NEEDS_RECOVERY → "Start restore" published request (code 592847) → deployedonRestoreRequestedfired LIVE (Sam got "Help your partner restore 💜" push, id 40038) → Sam's Change-1 consent live-verified (email anchor + name QA decrypted locally + confirm checkbox; Approve gated on code(6) AND confirm) → approve → QA auto-restored: paired Home + "Sam/Revealed" + full chat history decrypts; freshrestore_ok_R25from restored QA decrypts on Sam = bidirectional round-trip = full R24 restore regression. Deferred obs (not a defect): warm-start restore-push tap opened Play hub not RESTORE_CONSENT (likely collapsed-notif-group artifact; cold-start routing smoke-green). R25-c (2026-06-30) — LIVE-FIRE of deployed owner-alerts (Change 3): both restore self-alerts observed on QA's OWN device; last user-gate CLOSED; 0 defects. User-authorized re-wipe QA(5554) → sign in → NEEDS_RECOVERY → Start restore (code 565429). (1) Request self-alert fired LIVE — QA shade (closer.appid 67945,partner_activity, imp 4) + durableusers/{QA}/notification_queue(restore_self_alert, 23:17:38 “New device is restoring your history”) + partner push to Sam (“Help your partner restore 💜”) — all from oneonRestoreRequested. R25-b routing obs CLOSED: tapping Sam’s single restore notif → RESTORE_CONSENT (not Play hub) ⇒ earlier artifact was the collapsed 2-item group header. Consent gate re-verified (code(6) alone Approve-disabled → +confirm enabled). Sam approve →onRestoreFulfilledfired (status ok, 1319ms) on REQUESTED→READY → (2) completion self-alert queued to QA (restore_self_alert, 23:19:50 “Your history was just restored”) — not on shade only because QA was foregrounded (auto-restored); push still reached live tokens. 132s apart (>~60s dedupe → no suppression). Robustness live: 1 stale token (registration-token-not-registered) failed butPromise.allSettled→ function ok. QA auto-restored to paired Home + content decrypts, 0 FATAL → fixture healthy. Minor follow-on (not defect): prunenot-registeredFCM tokens. R25-d (2026-06-30) — implemented FCM stale-token pruning (closes R25-c follow-on); build + 47 fn tests green; NOT deployed (user-gated). Newnotifications/pruneTokens.ts(isDeadTokenErrorprunes ONLYregistration-token-not-registered/invalid-registration-token— never transient/invalid-argument, so a bug can't wipe all tokens;pruneDeadTokensbest-effort, never throws, batch-deletes deadfcmTokensdocs + legacy field, only on an actual dead token). Reuses each caller'stokens+allSettledresults → 1 line wired into all 19 push sites (questions/dates/couples/games/notifications/billing/users/backup).pruneTokens.test.ts+9 tests (fn 38→47). tsc verified every db/uid/tokens/results ref;dist/notifications/pruneTokens.jsemitted. Takes effect on nextfirebase deploy --only functions. R21 (2026-06-29) — brand-voice + Home-bubble polish, then full QA re-run; 0 new defects, 0 FATAL. Copy:prompt→question(~26 strings) + clinical→Closer voice (Outcome/check-in feature, "Your Progress"→"Growing together", "Private sync"→"Just for two", Home eyebrow "Your daily question", paywall "…and growth"). Home partner bubble upgraded (CoilSubcomposeAsyncImage+ gradient ring + a11y; partner photo verified live). Cheap gates all green (210 unit · 24 fn · theme-scan CRIT 0 · painter-xml 0 · smoke 6/6 both). Reveal-when-answered verified LIVE end-to-end (both answer → Home "Reveal is ready / Reveal together" → AnswerReveal shows both picks). Multi-angle nav verified (daily Q via Today+Home, reveal via Home card, Settings→Growing together, Play→Question Packs "250 questions"). Cornerstones A/B/D carry from R20 (no rules/crypto/games-logic change — diff is copy + Home-bubble UI); E re-verified (smoke). Also landed this session (uncommitted): recovery-UX partner-as-backup copy + change-phrase desync guard,SECURITY.md, first instrumented testFirstRunRenderSmokeTest. SeeClaudeReport.mdR21. R20 (2026-06-29) — fresh full ClaudeQAPlan run; found + FIXED 2 escaped bugs. Build HEAD62696a6+ R20 fixes (uncommitted:QuestionSessionRepositoryImpl.kt,GameSessionManager.kt,HomeViewModel.kt). Cheap gates all green (unit 210 · fn 24 · theme-scan CRIT 0 · painter-xml 0 · wiring 🔴0 · smoke 6/6 both). Cornerstones A/B/D/E live-clean, 0 FATAL. B-ABANDON-001 (P2) — Quit/abandon on any game silentlyPERMISSION_DENIED(fullsaveSessionset drops server-only flags → rule rejects removedaffectedKeys) → stranded session; fixed (targetedupdate(status,completedAt)), verified live (quit→active=0→new game starts). B-COPY-001 (P3) — Home GAME_WAITING hero falsely claimed "partner already played their part"; fixed (neutral partner-named copy), verified live both devices. Both pending 1 confirm. SeeClaudeReport.mdR20 run-state. Build = R18b working tree (uncommitted: Wheel finish-gate +partner_joined_game/banner-standardization client+functions+rules + portrait lock + docs — full file list inClaudeReport.mdrun-state); 210 unit + 24 functions tests green; debug APK rebuilt+installed both emulators. Deploy status:functions/+firestore.rulesDEPLOYED by user (join push live; Tier-2 self-constraint verified live — member own-uid add 200, foreign-uid/removal 403). No remaining deploy gates. Position + verdict: seeClaudeReport.mdR18b run-state. R18b polish/hardening round (latest): fixed E1 (P2) Wheel silently-swallowed submit failure → retryable error (no false reveal); modern banner/bubble feel (haptics, spring, JOINED presence dot, tap+swipe, a11y, persistent-not-clobbered); predictive back (enableOnBackInvokedCallback); Wheel "Quit game" abandon; Tier-2 rules self-constraint. Pass-E smoke 6/6 both. Verdict: R18 — fixed the last open visual P2 C-DARKART-002 (uiMode-sync inMainActivityviaAppCompatDelegate.setDefaultNightModeso 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 (painterResourceon 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 bothOnboardingScreen.CtaSlide+AuthVisuals.AuthLogoMark→ rastercloser_launcher_foreground; addedscripts/painter-xml-scan.shguard (proven). Also closed BucketList FAB hardcoded color. Board: 0 open P0/P1 · 1 open P2 (O-AGE-001 pre-ship age gate) · 1 open P3 (BRAND-DARK-COVERAGE); the full confirmed backlog was pruned this round (O-ONBOARD-001, C-DARKART-002, 6× C-THEME, M-001, TEST-001/002, P-GRAMMAR-001, BucketList-FAB, BRAND-ICON-CUSTOM, and C-ORIENT-001 → RESOLVED: portrait-locked in the manifest, verified live). 0 FATAL. R18b feature work (uncommitted; tests 209 unit + 24 functions green): (1) Game finish-gate — no game can finish with unanswered questions (Wheel: skip allowed but Finish bounces to the first blank; the other 3 already require a pick); verified live end-to-end (full 2-player Wheel + This-or-That). (2) "Partner joined your game" push + standardized durable in-app game banner — newpartner_joined_game(joiner's avatar) + all foreground game pushes routed through the themed banner (started/joined transient, your-turn/results persistent); verified live + Pass-E smoke 6/6 both emulators. ⚠ The join push needsfunctions/+firestore.rulesdeployed by the user to fire live.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 — neverpm clear5554/5556) on any onboarding/auth/pairing/branding/res/drawablechange + pre-ship. Don't claim "first-run ✓" off the logged-in fixtures. R20 (2026-06-29) — FULL FRESH-PAIRING LANE RUN ✅ (user-requested), 0 FATAL: two throwaways uninstalled→fresh-installed (5558=Avery + booted CloserCodexQA/5560=Riley). A: notif-permission → onboarding all 3 slides ("Answer honestly / No peeking / Grow Closer" — CtaSlide logo renders, O-ONBOARD-001 stays fixed) → auth landing (AuthLogoMark renders) → sign-up → 3-step profile (name · inclusive gender Female/Male/Non-binary/Prefer-not-to-say · optional photo skip) → unpaired handoff → invite code 47V-JCZ + recovery phrase shown. B: Skip onboarding path verified (slide 1 → auth) → sign-up → profile → Accept-instead → enter 47VJCZ → "Pair up" → "You're connected / Riley & Avery". New couple3sRSEvky7HdSUOY9F1z0(2 userIds,encryptionVersion=2) created server-side; both devices flipped to paired in real-time; B's day-1 check-in modal + first daily Q render. No new bugs. Teardown: throwaways uninstalled, 5560 powered off, QA/Sam (5554/5556) untouched + clean (active=0). (Residual isolated throwaway Firestore couple left as disposable test data.)📖 Architecture reference: see
docs/Engineering_Reference_Manual.md— contains architecture, security model, data model, and the Known landmines section that backs every fix-and-pruned ID below. 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 | 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). R18b: game FINISH-GATE added — no game finishes with unanswered Qs (Wheel skip-then-bounce-to-blank; other 3 require a pick); verified live full 2-player Wheel + ToT to completion (no "Skipped" in reveal). R18b hardening: E1 (P2) FIXED — Wheel no longer swallows a submit failure (was navigating to a false reveal → data loss); now a retryable error (unit-tested). Wheel got a "Quit game" abandon so leaving mid-wheel doesn't strand the session. R20: B-ABANDON-001 (P2) FIXED — that abandon (+ ToT/HowWell Quit; all route through abandonSession) actually failed PERMISSION_DENIED server-side (full saveSession doc.set() dropped the server-only flags → session-update rule rejected the removed affectedKeys) so the session stayed stranded active; fix = targeted update(status,completedAt); verified live (quit→active=0→a different game starts immediately). R20: B-COPY-001 (P3) FIXED — Home GAME_WAITING hero no longer falsely claims "partner already played their part." |
✅ pass · finish-gate + submit-retry + abandon now actually works (B-ABANDON-001) (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. R18b: C-ORIENT-001 RESOLVED — MainActivity locked to portrait (no landscape design; verified requestedOrientation=PORTRAIT holds under forced rotation). C-DARKART-002 confirmed + pruned. |
✅ theme-scan CRIT 0 · C-DARKART-002 + C-ORIENT-001 resolved · ⚠️ 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. R18b: NEW partner_joined_game (joiner avatar) + standardized durable in-app game banner — all foreground game pushes route through the themed banner (started/joined transient, your-turn/results persistent + tappable; foreground OS dupe suppressed, background OS unchanged); verified live per kind + Pass-E smoke 6/6 both emulators (join push pending functions+rules deploy). |
✅ pass · delivery + routing + privacy + both-client confirmed R18b · banner standardized · 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). R25: NEW Date Memories/Reflection feature landed (reverted-then-reinstated → slipped prior rounds); fixed 5 escaped bugs — DR-TYPING (imePadding), DR-DEEPLINK-BG (MainActivity dropped date_id), DR-FEED-ROUTE (Together date→DATE_MATCHES), DR-LOADER (DateMemories infinite spinner on read error), DR-LOCKED (blank dashes when vault locked); + notes field, edit-before-reveal, opened-push. NEEDS live QA pass (both devices, bg+fg notifications). |
⚠️ partial — N-001/N-002 fixed; Date Memories/Reflection = todo (new R25, needs 2-device live run) |
| 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. R18b: confirmed backlog fully pruned — added C-THEME-001/002/004/005/008/009, C-DARKART-002, M-001, TEST-001/002, P-GRAMMAR-001, BucketList-FAB, BRAND-ICON-CUSTOM, O-ONBOARD-001, C-ORIENT-001 (portrait lock) to the archived set. Open: 1 P2 (O-AGE-001) + 1 P3 (BRAND-DARK-COVERAGE) — both blocked on the user.
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.
R18b — FINISH-GATE (every question must be answered before a game can finish): Grounding found only Spin the Wheel let a player finish with blanks (explicit Skip; Next advanced when empty; End session submitted the rest as "Skipped"; it's the only game with text boxes). Per user decision ("Hybrid"): Wheel now uses an index-keyed nullable answer store + an attemptFinish() gate — skip/blank is allowed mid-play, but Finish bounces to the first unanswered prompt with an a11y "N left to finish" banner and submits only when none are blank (enforces non-empty text + ≥1 choice; scale always has a value). The other three already require a pick to advance (ToT/Desire Sync auto-advance on tap; How Well's Continue is disabled until selected) — verified by code + Pass-B observation. Live (both emulators): full 2-player Spin-the-Wheel (all 10, mixed written/choice) → completed reveal with no "Skipped"; gate bounce + persistent "N left" banner + walk-forward confirmed; then a full 2-player This-or-That ("5/5 in sync"). 3 new WheelSessionViewModelTest cases (gaps→bounce/no-submit; all-answered→submit no "Skipped"; completion-walk). 0 FATAL.
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.shreports them in its## Summarysection, 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
painterResourcehits) 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 —primaryActionhero +buildPendingActionsrow), 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 inshellBackRouteswhile screen owns a TopAppBar; C-CC-001 class), C-PW-001 (dark paywall "What's included" pills light-on-light,BenefitPillonSurface 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:;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. - One hardening note →
Future.md(App Check off on Firestore). (R15 correction:users/{uid}update rule enforces a field allowlist — not arbitrary; extended R15 forquietHours*+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 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). - 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_answeredeach 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; theconversation_id=mainsuppression 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/ogame_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— eachdatablock 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 6conversations/main/messagesallenc:v1:(server-blind source). Cross-checks D6. - Real
onMessageWrittenlive 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).
- Cold-start crash-triage smoke 6/6 on BOTH emulators (
- R18b —
partner_joined_game+ standardized durable in-app game banner (FEATURE): new push when the non-starter opens an active session → the starter gets " joined your game" with the joiner's avatar (server-visible join viajoinedByUsers; one-timejoinNotifiedAt;onGameSessionUpdatebranch). All four foreground game pushes now route through the themedGamePromptBannerwith the partner's avatar +sender_name: started/joined transient (~9s), your-turn/results persistent until tapped; foreground OS duplicate suppressed; background OS notification unchanged (already shows the avatar large-icon — the purple banner is in-app only). Live (5554): all four kinds rendered correctly with avatar+name; RESULTS still shown at 15s vs STARTED auto-dismissed by 12s; 0 foreground OS dupes; 0 FATAL. Pass-E cold-start smoke 6/6 on both emulators (shared path regression-clean).PartnerNotificationTypeTestcovers the new type's mapping + routing.blocked→deploy: the join push only fires oncefunctions/+firestore.rulesare deployed (thejoinedByUsersclient write is rule-gated; best-effort + swallowed until then). The banner standardization for already-deployed types works immediately.
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)
- 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 (addenc: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 PLANNEDDatePlanviasavePlan→ 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 sharedBrandIllustration/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 committed2cd0af6. - 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).