Closer/ClaudeReport.md

12 KiB
Raw Blame History

Claude QA Report — Full-App QA (living report)

RUN-STATE: Round 2 | Pass B in progress (1 game/chunk) | This or That, How Well (both full playthroughs, results match, no crash). NEXT ACTION: play Desire Sync → Connection Challenges → Memory Lane → Spin the Wheel → Date Match (Desire Sync + Memory Lane need a Sam-premium toggle; remember to exit games via Back to Play so the session closes). Then Pass C deep/stateful + nav/back; live notification matrix; D3 non-member; Pass F (incl. F-OBS). Pass-B note: a finished game keeps its session active until a player exits the results (Back to Play); leaving both on results blocks the next game until "End their game". Exit cleanly between games. R2-1 DONE: A-001 couple-shared re-verified live (Desire Sync/Memory Lane/Wheel enter when partner premium; free→paywall). D-001 (P1) FIXED+DEPLOYED (capsules/challenges rules; Memory Lane + Connection Challenges now load). Sam reverted to free (baseline). Round 1 complete (all 5 passes run report-only; P0P2 found were fixed in-line). Fixes: A-001 (e8892a9), E-001 (ce12abb). Open P3: A-003, B-001, E-002. EXECUTION MODE: autonomous run-to-completion — do NOT stop; fix blockers inline; keep cycling fix→re-QA until flawless. Do NOT hand back when context fills — the harness auto-compacts and you continue from THIS run-state (re-read it + coverage after any summary). Commit before interruptible work; recover stuck sessions via the session-start ritual. STANDING AUTHORIZATION (user, 2026-06-24): may firebase deploy --only firestore:rules + has admin access (Firestore reads/writes/seeds + entitlement toggles) — run these without pausing. Only the macOS requirement for iOS (Parts 2/3) remains a hard stop. Playbook: ClaudeQAPlan.md. Coverage matrix: ClaudeQACoverage.md. Report-only during passes (no fixes until the fix phase). Devices: emulator-5554 (QA=Y05AKO) + emulator-5556 (Sam=imDjjO), paired (coupleId Xal3Kw3gjSdn0niERYKJ). Build == HEAD 64f0a7e.

(Prior games/notifications QA from 2026-06-24 was completed + verified; superseded by this full-app effort.)


Severity summary (Round 1)

Severity Open Fixed
P0 0 0
P1 0 2
P2 0 1
P3 4 0

Round 1: all P0P2 found were FIXED (A-001 premium P1, E-001 notif-routing P2). Open = P3 cosmetics only (A-003 badge, B-001 stale-session guard, E-002 informational notif routing). Deferred for a clean "flawless" certification: exhaustive deep/stateful screens (Pass C), full live notification matrix + D3 live non-member test.


Pass A — Couple-shared premium pass complete

Target: if either partner is premium, all premium features unlock for both. Result: only chat is couple-shared. Every other feature gate is per-user → a free user whose partner paid stays locked.

ID Area Screen/Route Severity Description Repro Status
A-001 Premium gating PlayHubViewModel, DesireSyncScreen, MemoryLaneScreen, ConnectionChallengesScreen, QuestionPackLibraryViewModel, wheel CategoryPicker/SpinWheel/WheelHistory(VMs) P1 These gated on per-user EntitlementChecker.isPremium() instead of couple-shared. A free partner of a premium user stayed locked. Set Sam premium, QA free → QA Play hub still showed 🔒 on Desire Sync + Memory Lane. FIXED e8892a9 — routed all gates through CouplePremiumChecker (now exposes isPremium/hasPremium resolving partner internally). Verified: Sam premium → QA enters Desire Sync; both free → QA → paywall.
A-003 Premium UI (cosmetic) PlayHubScreen (Desire Sync + Memory Lane cards) P3 The "🔒 Premium" badge on these two cards is static (rendered in separate card composables that don't receive hasPremium), so it still shows a lock even when the couple has premium access. Feature IS accessible (gate fixed in A-001) — only the badge is misleading. With couple premium, QA's Play hub still shows 🔒 Premium on Desire Sync/Memory Lane though tapping enters the game. Open (deferred — thread hasPremium into the card composables)
A-002 Premium (control) ConversationViewModel (chat) Working correctly (couple-shared) — kept as the reference pattern for the A-001 fix. Verified prior round: partner-premium unlocks chat media/reactions for the free partner. OK

Note (by-design, not a bug): SubscriptionScreen uses per-user isPremium() — correct, it reflects the user's own subscription/account state, not a feature gate.

Pass B — Games lifecycle (launch/crash sweep done; full two-device lifecycle partial)

ID Area Screen/Route Severity Description Repro Status
B-001 Games / sessions couples/{id}/sessions P3 (observe) A stale active wheel session (startedBy QA, createdAt missing/undefined) blocked all games ("Waiting for Sam"). In-app "End their game" recovery works (session→completed, games unblocked). Likely a prior-test artifact, but: sessions appearing without a timestamp + one stale session blocking every game is worth a guard. Open This or That → "Waiting for Sam… Sam is playing a Wheel game"; tap End their game → unblocked. Open

Launch/crash sweep (QA, free): This or That (mood/length select), How Well Do You Know Me (intro), Connection Challenges , Spin the Wheel — all render, no FATAL. Desire Sync + Memory Lane are premium-gated (covered in Pass A; gameplay needs premium toggle). Date Match: todo. Full two-device start→finish + results not exhaustively re-run this round (the prior round verified onGameSessionUpdate start/finish end-to-end).

Pass C — Visual (light + dark) (main screens verified; deep/stateful screens pending)

Method: 5554=Dark, 5556=Light; readable dark|light pair montages + a code scan for non-adapting colors. Verified clean (both themes, readable, no clipping): Home, Today, Play, Messages inbox, Settings. closerBackgroundBrush() is theme-aware (adapts). No FATAL on these.

ID Area Screen Severity Description Status
C-OBS Theming ~20 screens (AnswerRevealScreen 15, WheelSessionScreen 14, DateMatchScreen 10, PaywallScreen 9, BucketListScreen 9, SettingsScreen 7, HomeScreen 5, …) observe Use hardcoded Color(0x…) literals (195 total) that don't adapt to theme — a dark-mode contrast risk to verify per-screen. Main screens checked look fine; deep/stateful screens (reveal, wheel session, dates, bucket list) still need visual verification in both themes. Open (verify in continuation)

Deep/stateful screens (answer reveal, wheel session/complete, date match/builder/matches, bucket list, memory capsule, history, paywall, auth/onboarding/pairing) need their states set up — pending next chunk.

Pass B requirement (updated): each game must be played one complete time through on both devices (start → every step → finish/reveal/results), not just launched. Round 1 did launch-only → full playthroughs owed in Round 2 for all 7 (premium games need a premium toggle). A launch-only result = partial, not pass.

Pass C requirement (added): navigation from every entry point (each screen reached from all its links — e.g. conversation from inbox/Discuss/notification; game from Play/notification; paywall from each gate) + back-stack / "double-back" (system back AND in-app back return to the right place from each entry; no dead-ends, no exit-app surprise, no screen needing two backs/duplicate stack entries; deep-link/notification entries land with a sane back stack). Owed in Round 2. Wrong/double back or dead-end = P2 (P1 if it traps the user).

Pass D — Security & Encryption clean (no P0/P1 found)

  • D1 at-rest: all private content is ciphertext — message text + lastMessagePreview + thread messages = enc:v1:; daily answers encryptedPayload = sealed:v1:. Metadata (dates, types, commitmentHash, ids) plaintext as expected. Chat media bytes = Tink ciphertext (verified prior round + unchanged code path). No plaintext content leak.
  • D2 rules: no catch-all match /{document=**}, no blanket if true; hasPremium server-only (client create/update blocked, rules L172/174); entitlements write:false; conversations/messages/typing/reactions + entitlement partner-read scoped to members.
  • D4 key exchange: pairing uses a wrapped couple key (wrappedCoupleKey + kdfSalt/kdfParams + encryptedRecoveryPhrase); invite code is the KDF seed, never stored raw; strict E2EE (invites without a wrapped key rejected) — confirmed in acceptInviteCallable.
  • D5 App Check/secrets: App Check enforced (SecurityModule, PlayIntegrityChecker, FirebaseInitializer); both service-account JSONs gitignored and untracked; allowBackup=false.
  • D6 leak vectors: analytics events carry only metadata (no message/answer content); allowBackup=false.

Follow-ups (not blockers): live non-member negative test (D3) needs a fresh 3rd account (rule logic verified member-scoped); a fresh Storage-bytes spot-check of chat media.

ID Area Severity Description Status
D-001 Rules — missing subcollection rules P1 couples/{id}/capsules and couples/{id}/challenges had no match block → default-deny → Memory Lane hung on its loading heart and Connection Challenges couldn't load (live PERMISSION_DENIED confirmed). Two premium features broken. Sam premium, QA opens Memory Lane → stuck loading heart; logcat Listen for Query(.../capsules) failed: PERMISSION_DENIED.
F-OBS Resilience (UI) P3 MemoryLaneScreen (and likely others) hangs on the loading indicator forever when a Firestore query fails, instead of showing an error/empty state. Masked the D-001 root cause. Add load-failure handling. Was visible before D-001 fix (stuck heart).
(outcomes) Rules The Round-1 outcomes list PERMISSION_DENIED is by-design — the rule restricts reads to specific dayKeys (day_0/30/60/90); a bare list query is correctly denied. Not a bug.

Pass E — Notifications

  • Copy carries no private content: all function notification bodies are generic ("Tap to read and reply", "Answer together before it expires", etc.); ${title} refers to public question/game titles, not user answers. ✓ (ties to D6)
  • Routing: centralized in PartnerNotificationType (fromRemoteTyperouteFor); chat opens the exact conversation, reveal→answerReveal(questionId), games→Play, capsule→Memory Lane, etc.
  • Foundations (prior round, code present): FCM token registration on sign-in, POST_NOTIFICATIONS, channels.
ID Area Severity Description Status
E-001 Notification routing P2 Type-string mismatch: functions send daily_question + challenge_day_ready, but client mapped only daily_question_reminder + challenge_waiting → tapping those did NOT deep-link to Today / Connection Challenges. FIXED <pending-commit> — added daily_question/challenge_day_ready to fromRemoteType (build green; live tap-verify deferred).
E-002 Notification routing P3 partner_left, partner_deleted_account, invite_created, spki are unmapped → tap lands on default (no deep-link). Informational types; acceptable but ideally routed. Open

Full live delivery matrix (17 types × foreground/background/killed) deferred — key types verified prior round; routing now code-correct.