55 KiB
Claude QA Report — Full-App QA (living report)
Verdict (2026-06-27): R15 = gap-closing round on Passes L/M/N/P + smoke — found & FIXED M-001 (P2 quiet hours). Targeted the previously-uncovered gaps (the new "flawless" bar needs L + P clean + M take-effect). Found M-001 (P2): "Quiet hours — 10 PM–8 AM, no notifications" did not suppress partner pushes when the recipient app was backgrounded/killed (the main case) — quiet hours was local-only (never synced server-side) and the OS shows the FCM
notificationblock directly without running app code. Fixed + verified live: client now mirrors the window+timezone tousers/{uid}; the 4 partner-action senders (onMessageWritten/onAnswerWritten/onAnswerRevealed/onGameSessionUpdate) suppress server-side via a fail-openrecipientInQuietHours(); rules allowlist extended for the new fields. Live: QH ON → function logs…is in quiet hours — suppressing, 0 delivery; QH OFF →notified partner, delivery resumes; per-type chat toggle still suppresses (server-enforced). Then drove Pass N (user "FIX"): N-001 (P1) — Bucket List was entirely non-functional (coupleId never wired → all CRUD silently no-op) → FIXED + verified live (addenc:v1:/complete/delete/render). N-002 (P2) — "Plan a Date"/Date Builder "Create Plan" was a no-op (wrote to an unread prefs collection;dateIdeaId/coupleIdnever wired) → FIXED + verified live (re-pointed to create a PLANNEDDatePlan→ Home shows "Date coming up"). Outcomes/Your Progress code-correct. Clean passes: L (chat decrypt both dirs, attribution, Seen receipt, ❤️ reaction, ordering, day-sep, inbox noenc:leak, at-restenc:v1:); P (UI copy warm/inclusive, debug rowsBuildConfig.DEBUG-gated, friendly error fallbacks; question bank 6103 Qs — 0 empty, 0 dupes, 0 placeholders, complete answer configs, on-guide tone); daily-Q + reveal gate render; smoke 6/6 GREEN both emulators. 2 P3 brand-asset backlogs still open. 0 FATAL.Verdict (2026-06-27): R14 = full fresh A–J ClaudeQAPlan run — 0 open P0–P2, 0 new functional findings. A pure-QA confirmation round (no code changes) on the R13 build. (A follow-up 2026-06-27 brand-standards audit then opened 2 P3 brand-asset backlogs — every image needs a dark variant; every icon must be custom — see the Issues section +
ClaudeBrandingReview.md.) The 5 R13 fixes (C-DARK-UI-001/002/003, C-ART-EDGE-002, J-OBS) held through R14's sweep → pruned; the Premium-unlock modal held + re-verified. Live results: Pass A — premium ENFORCEMENT audited (all 6 gated features have a real gate, not just a badge; A-201 class closed) + free→Paywall confirmed for Date Match / Desire Sync / Question Packs + couple-shared unlock (Sam→QA) with modal +subscription_entitlement_changedpush delivered live. Pass B — Desire Sync / How Well / Spin-the-Wheel full 2-device (mixed answer types incl free-text + multi-select + skipped-answer reveal), first-finisherpartner_completed_partnudge confirmed live, Memory Lane create+seal (premium), Connection Challenges resume, Date Match deck (ToT carried from R13). Pass C — broad both-theme sweep + decoupled-theme-art mandate (system-light+app-Dark → dark UI + correctly-themed feathered art). Pass D — cornerstone LIVE (non-member 403 ×2, self-grant 403, member 200, chat at-restenc:v1:). Pass E — all triggers fired live with content-free copy to the right partner. Pass F — offline cache render + process-death recovery, both 0 FATAL. Pass I — jank 5.25%. Pass J — J-OBS 48dp holds. 0 FATAL across the whole run, both emulators.
Verdict (2026-06-27): R13 = fixed the entire open backlog + full fresh A–J — FLAWLESS (0 open P0–P3). Took over and fixed all 3 open Codex dark-mode findings (C-DARK-UI-001 P2 This-or-That redesign; C-DARK-UI-002 P3 check-in label/value; C-DARK-UI-003 P3 bottom-inset clipping) plus the 2 carried P3s (C-ART-EDGE-002 direct-call hero feathering; J-OBS 48dp touch targets), and confirmed A-201 (P1) live → pruned. Also shipped the branding Premium-unlock modal (
illustration_premium_unlock, one-time, shown to BOTH partners on couple-shared activation). All verified live on both emulators (5554 dark / 5556 light), 0 FATAL. Full fresh A–J run clean: Pass D security cornerstone re-verified LIVE (non-member 403, self-grant 403, member 200, chat at-restenc:v1:); A premium gates → Paywall (Date Match + Desire Sync); B ToT full both themes + Wheel launch; I jank 6.43% (perf-safe); J 48dp confirmed. Diff is UI-only (no rules/functions/crypto change) → E/F/G carried. All app changes in the working tree — user commits.
Verdict addendum (2026-06-27): ad hoc DARK-MODE UI/brand review on dedicated Codex emulator COMPLETE. Built + installed the current debug APK on my own
CloserCodexQAemulator (emulator-5558), forced system dark mode, created a fresh real paired couple through the app invite flow, and swept profile/onboarding, unpaired invite, paired Home, Play, This-or-That, Settings, Notifications, Paywall, Messages, and Today. Button text is generally readable across profile/Home/Settings/Notifications/Messages/Paywall, but the sweep found 1 open P2: This-or-That active gameplay has low-contrast dark option text and an off-brand diagonal/circle backdrop crossing the prompt. Also found 2 open P3s: first-launch check-in modal label/value collision and recurring bottom-inset clipping on scroll content near nav/gesture areas. Logs checked after navigation/game entry: 0 app FATAL/ANR/force-finish; only uiautomator/system noise plus a non-crashing BillingClient unbind warning.
Verdict (2026-06-27): R11 confirmation round COMPLETE — FLAWLESS (0 open P0–P2). Fixed the last open P2 (C-DARKART-001 — dark art now follows the in-app theme) + the open P3 (C-ART-EDGE-001 — art feathers into the surface), both in the shared
BrandIllustration/EmptyStatehelpers, verified live on both decoupled theme directions (system-light+app-Dark → dark aubergine art; system-dark+app-Light → light pastel art), 0 FATAL. Re-confirmed all 5 R10 P2 fixes hold (C-HOME-001 single card · C-NAV-002 wheel-back popUpTo present · C-NAV-003 single app bar live · C-PW-001 dark paywall pills legible live · C-SEC-001 recovery row active for accepter live) → pruned. Entrypoint launch-integrity smoke green (splash-crash class clean on the fresh APK). Only remaining: 2 freshly-fixed art items pending 1 confirm + J-OBS (P3 touch targets). Art fixes in working tree — user commits.
📖 Architecture reference: see
docs/Engineering_Reference_Manual.md. Most fixed-and-pruned IDs above are documented in its Known landmines and recent fixes section — read before re-touching the affected area.Verdict (2026-06-26): R10 FULL ClaudeQAPlan run COMPLETE (A–J + fix phase). 0 open P0–P2; 1 P3 (J-OBS). Found 5 P2 (Home dup card, wheel back-stack, duplicate app bar, dark paywall contrast, recovery-phrase wrong store) — ALL fixed + verified live + regression-clean. E-GAME-002 confirmed live + pruned. Security cornerstone clean (D1–D7). [Pruned in R11.]
This report shows current state only. Fixed issues live here for one confirmation round, then they're pruned to the archived-ID line below (full detail stays in git history). See Report hygiene in
ClaudeQAPlan.md.
Run-state (current)
- R17 (2026-06-28) — continuing full run (user: "complete full run, don't stop"). Both emulators on R16 build (5554=Dark, 5556=Light); HEAD
8b7bbc2+ working-tree fixes. Theme fixes confirmed LIVE (dark, 5554): C-THEME-005 (Wheel-History lock → surfaceVariant/primary), C-THEME-008/009 (Date-Match heart→primaryContainer + count badge→error) — joining 001/002 from R16. NEW finding C-DARKART-002 (P2): dark-variant art doesn't render in the decoupled in-app-Dark + system-light state — pack art (QuestionPackLibraryScreen:223viapackArtworkRes) + ~7 literalpainterResourcesites resolve-nightoff SYSTEM uiMode, not the in-app theme; proven live (in-app-Dark + systemauto→ light pack art; system night=yes → correct dark art). The BRAND-DARK-COVERAGE batch art is correct but only shows under system-night. Pass D1 at-rest = CLEAN (admin read R17): messagestext,lastMessagePreview, Memory Lane capsule content+title, all 4 game answers (this_or_that/desire_sync/how_well/wheel) + date_swipeactionallenc:v1:; only metadata in clear. NEXT: C-DARKART-002 fix (route painterResource art→BrandIllustration); D3 negative-access; M-001 quiet-hours re-test; light-side theme spot-check (5556); Pass A/B/E/P. - R16 (2026-06-28) — full ClaudeQAPlan run STARTED. Session-start: both emulators online (5554/5556), HEAD
8b7bbc2, working tree carries a dark-variant art batch (pack_art_*_dark, together_empty/tonight_partner_prompt night variants → BRAND-DARK-COVERAGE progress) + my doc edits; baseline TBD. Cheap gates (new step 3): functions tests 24/24 ✅; Android unit tests found 5 failures → FIXED → 205 ✅ (TEST-001, test-vs-code drift: (a)PartnerNotificationManagerTeststubbedisInQuietHours(any())but the method's defaultnow: Calendarparam pinned a stale instant that never matched the call-time clock → stub nowany(), any(); (b)CloserBrandCopyTest≤64 cap predated the intentional 150-char flagshipprimaryMessage—BrandMessageRotatorwraps itmaxLines=3— → cap now applies to short slogans only, flagship bounded1..160. Both test-side only; production correct: quiet hours verified live R15, flagship is committed design6d74c6a). theme-scan 🔴9/🟠8/🟡32 (the 9 CRITICAL = C-THEME-001..009 already filed). wiring-scan 🔴0/🟠20/🟡35 (🔴0 = Pass N DoD met). Done so far: rebuilt+installed both; smoke 5554 = 6/6 PASS, 0 blocked (launcher + 5 notif cold-starts open & stay), 5556 in progress. Theme triage — of the 9 filed C-THEME, 3 are NOT real shipped defects: C-THEME-003 =@Preview-only (WheelRevealPreview) → false positive; theme-scan now excludes @Preview composables. C-THEME-006/007 = dead unusedPlaceholderScreen.kt(replaced by the real dashboard; 0 source refs) → file deleted. The other 6 are real → FIXED (theme tokens): BucketList (badge→primaryContainer; AddItemDialog surface→surface + Cancel→secondaryContainer + Add→primary + CategoryChips→primary/surfaceVariant — also closes the Future.md "mixed dialog" note), DateMatch (heart→primaryContainer, count badge→error/onError), WheelHistory (lock→surfaceVariant/primary), QuestionThread (waiting banner→surfaceVariant). theme-scan CRITICAL 9→0; build + unit tests green. R16 RESULT: smoke 6/6 both (0 blocked); theme-scan CRITICAL 9→0 (3 reclassified, 6 fixed); C-THEME-001/002 + N-001 + N-002 verified LIVE (dark, 5554); units 205 + functions 24 green; deadPlaceholderScreen.ktdeleted; theme-scan now excludes@Preview. N-001/N-002 pruned. Open now: O-AGE-001 (P2 pre-ship) + 3 P3. NEXT (R17): live-confirm the 4 remaining C-THEME fixes (004/005/008/009) in both themes + the 6 in LIGHT on 5556; re-test M-001 quiet-hours (backgrounded-push) to prune it; then resume passes A–N+P — esp. a live both-theme sweep of the new dark-art batch (BRAND-DARK-COVERAGE) + the D/E cornerstones. Uncommitted (user commits): unit-test fixes, 4 theme-fixed screens + BucketList/DateMatch,scripts/theme-scan.sh, deletedPlaceholderScreen.kt, dark-art batch, QA docs. - R15 (2026-06-27) — gap-closing round (Passes L/M/N/P + regression smoke) — found & FIXED M-001 (P2). Build current (HEAD
c31eea2+ R15 working-tree changes rebuilt+installed both emulators); baseline both FREE, 0 active sessions. Smoke ✅ 6/6 GREEN both (launcher + 5 notif cold-starts). M (settings take-effect) — M-001 (P2) quiet hours didn't suppress backgrounded/killed partner pushes (local-only window; OS showsnotificationblock w/o app code). FIXED + verified live: client mirrors window+tz →users/{uid}; 4 partner-action senders suppress via fail-openrecipientInQuietHours(); rules allowlist extended. Live: QH ON → fn logis in quiet hours — suppressing, 0 delivery; QH OFF →notified partner. Per-type chat toggle re-confirmed server-enforced (toggle off → 0 delivery; field flips in Firestore). Theme/DataStore persistence across relaunch ✅. Biometric lock code-sound (no compose bypass; observation: re-locks on cold-start, not plain background→resume →Future.md). L (chat E2E) ✅ decrypt both dirs, attribution, Seen receipt, ❤️ reaction, ordering, day-sep, inbox noenc:leak, at-restenc:v1:. N ✅ daily-Q + reveal both-answered gate render (outcomes/bucket-list/date-builder carried — render-clean prior rounds). P (content/language) ✅ UI copy warm/inclusive, debug rowsBuildConfig.DEBUG-gated, friendly error fallbacks; question bank 6103 Qs: 0 empty/0 dupes/0 placeholders/complete answer configs/on-guide tone. D1 at-rest ✅ messages/preview/capsulesenc:v1:. 0 FATAL. Pass N driven (user "FIX"): N-001 (P1) Bucket List was fully non-functional (coupleId never set → all CRUD no-ops) → FIXED + verified live (addenc:v1:/ complete / delete / list render; client-only). N-002 (P2) "Plan a Date"/Date Builder "Create Plan" no-op (wrote to unread prefs collection;dateIdeaId/coupleIdnever wired) → FIXED + verified live (re-pointedDateBuilderViewModelto create a PLANNEDDatePlanviasavePlan+ resolve coupleId →date_planstatus=planned,enc:v1:; Home shows "Date coming up"). Outcomes/Your Progress code-correct (resolves coupleId); daily-Q/reveal render ✓. Uncommitted (user commits): client (BucketListViewModel,DateBuilderViewModel) — M-001's functions/rules/client were committed by the user mid-round (+ user dropped 3 dark-variant PNGs indrawable-night-nodpi/toward BRAND-DARK-COVERAGE). M-001 functions+rules DEPLOYED to prod; N-001/N-002 are client-only (debug APK installed both emulators). NEXT (R16): confirm M-001 + N-001 + N-002 hold → prune; 2 P3 brand backlogs; revisit Date Builder "both-partners-generate" vision if wanted. - R14 (2026-06-27) — full fresh A–J ClaudeQAPlan run (pure QA, no code changes) — FLAWLESS, 0 open P0–P3, 0 new findings. Baseline both FREE, 0 active sessions; R13 build on both emulators (5554 dark / 5556 light). A ✅ premium enforcement audited (6/6 features gated, not just badged; A-201 class closed) + free→Paywall (Date Match / Desire Sync / Question Packs) + couple-shared unlock (Sam prem→QA free unlocks Desire Sync + a premium pack; modal +
subscription_entitlement_changedpush delivered live to QA). B ✅ Desire Sync + How Well + Spin-the-Wheel full 2-device (True/False+Yes/No+multi-select+free-text answer types; skipped-answer reveal; first-finisherpartner_completed_partnudge confirmed in Sam's queue), Memory Lane create+seal (premium), Connection Challenges resume (Day 4 · 🔥2), Date Match deck; ToT carried R13. C ✅ broad both-theme + decoupled-theme-art mandate (system-light+app-Dark → dark UI + correctly-themed feathered Today hero); no nav dead-ends (back-from-Home exits = correct). D ✅ LIVE non-member 403 ×2 · self-grant 403 · member 200 · chat at-restenc:v1:(game/capsule at-rest carried R10/R12, crypto unchanged). E ✅ all triggers fired live, content-free copy to right partner (started/completed_part/finished + entitlement). F ✅ offline Today-from-cache +am killrecovery, 0 FATAL. I ✅ jank 5.25%. J ✅ J-OBS 48dp holds. 0 FATAL whole run. The 5 R13 fixes held → pruned. Uncommitted (user commits): R13's 16 modified +PremiumUnlockOverlay.kt+illustration_premium_unlock.png(R14 added no code). - R13 (2026-06-27) — backlog fix pass + full fresh A–J — FLAWLESS (0 open P0–P3). Took over the open Codex dark-mode backlog and shipped it all (working tree, both emulators rebuilt+installed; baseline both FREE, 0 active sessions): C-DARK-UI-001 (ToT dark redesign — theme-aware backdrop/options/chips/versus/progress/pills) · C-DARK-UI-002 (check-in label/value weight) · C-DARK-UI-003 (Play/Home/Paywall bottom clearance) · C-ART-EDGE-002 (8 opaque heroes routed through
BrandIllustrationfeather) · J-OBS (composer/voice/retry buttons → 48dp). Confirmed A-201 live → pruned. Shipped the Premium-unlock modal (ui/components/PremiumUnlockOverlay.kt, hosted inAppNavigation; driven offCouplePremiumChecker, one-time via a newpremiumUnlockCelebratedSettingsRepositoryflag) — verified live on BOTH purchaser (5554) and partner (5556) + one-time gate (dismiss→relaunch no re-show). A–J: A ✓ (Date Match + Desire Sync gates → Paywall) · B ✓ (ToT full both themes; Wheel launch clean) · C ✓ (extensive both-theme sweep) · D ✓ LIVE (non-member 403 ×2, self-grant 403, member 200, chat at-restenc:v1:) · E/F/G carried (diff is UI-only; no rules/functions/crypto/auth change) · H ✓ (ToT brand + modal + heroes) · I ✓ (jank 6.43%) · J ✓ (48dp). 0 FATAL both devices. Uncommitted (user commits): 16 modified +ui/components/PremiumUnlockOverlay.kt+res/drawable-nodpi/illustration_premium_unlock.png. - Ad hoc dark-mode UI/brand sweep (2026-06-27, Codex-owned emulator
emulator-5558): current debug APK installed, dark mode forced, fresh real paired users created through invite flow (Codex Dark+River Dark). Swept profile, invite, paired Home, Play, This-or-That, Settings, Notifications, Paywall, Messages, Today. 0 app FATAL/ANR/force-finish in logcat. Findings added below: C-DARK-UI-001 (P2), C-DARK-UI-002 (P3), C-DARK-UI-003 (P3). Screenshots captured at/tmp/closer-dark-04-after-permission.pngthrough/tmp/closer-dark-25-today.png.R12 (2026-06-27) FRESH FULL ClaudeQAPlan run STARTED (user: "start from the start") | Baseline verified clean: QA free, Sam free (premium revoked), 0 active sessions; build HEAD 2cd0af6 + 3 uncommitted art files installed both emulators (5554=Dark, 5556=Light) | A ✅ (A-201 P1 Date-Match premium bypass) | B ✅ (4 async games full 2-device end-to-end + first-finisher nudge + C-NAV-002 + Ready=Start re-verified live; CC/MemoryLane/DateMatch render-checked; MemoryLane title/preview run-on→Future) | C ✅ (regression-clean vs R10 sweep; Messages/Today/Subscription + organic A/B + R11 decoupled art; NEW C-ART-EDGE-002 P3 direct-call hero hard edges on dark) | D ✅ (LIVE: D1 game answers enc:v1: · D3 non-member 403×4 + member-scoped · D5 self-grant→403; D2/D4/D6/D7 carried R7/R10, rules unchanged) — cornerstone clean | E ✅ (smoke 6/6 + Pass B triggers) | F ✅ (concurrency+process-death+offline) | G ✅ (security half live) | H (finding C-ART-EDGE-002) | I ✅ (jank 4.10%) | J (J-OBS P3) | **FIX PHASE ✅ — A-201 (P1) FIXED+VERIFIED LIVE** (Date Match LOVE/MAYBE on premium idea → Paywall via CouplePremiumChecker; SKIP passes; 0 FATAL). C-DARKART-001+C-ART-EDGE-001 held → PRUNED. | **R12 COMPLETE — FLAWLESS: 0 open P0–P2.** Remaining: 2 non-blocking P3s (C-ART-EDGE-002 hero edges, J-OBS touch targets). | NEXT (R13): confirm A-201 holds → prune; optional P3 polish. Uncommitted (user commits): R11 art files +ui/dates/DateMatchViewModel.kt+ui/dates/DateMatchScreen.kt(A-201). Carryover from R11 (still valid): C-DARKART-001 (P2) + C-ART-EDGE-001 (P3) fixed pending 1 confirm; J-OBS (P3) open touch targets. - **(prior) R11 (2026-06-27) confirmation round COMPLETE — FLAWLESS | Fixed last open P2 C-DARKART-001 (in-app-theme art) + P3 C-ART-EDGE-001 (feathered edges) in shared BrandIllustration/EmptyState; verified live both decoupled theme directions, 0 FATAL | 5 R10 P2 fixes re-confirmed + PRUNED (C-HOME-001/C-NAV-002/C-NAV-003/C-PW-001/C-SEC-001) | entrypoint smoke green on fresh APK | 0 open P0–P2 | Baseline: both FREE, 0 active sessions; build (HEAD
2cd0af6+ 3 uncommitted art files) installed both emulators | NEXT (R12 = next session): confirm C-DARKART-001 + C-ART-EDGE-001 hold → prune; optional J-OBS (P3) touch targets; then declare program-complete for Android. Art fixes in working tree (user commits).` - Pass B progress (R12): 1. This or That ✅ — full end-to-end 2-device, NEW style Light×5 Quick (R10 was Deep×10): QA started → answered 5 (alt A/B) → first-finisher state; first-finisher nudge fired (
partFinishNotifiedAtset + Sam queuepartner_completed_part"QA finished their part — your turn to play!"); Sam joined via Play-hub active state (at Q1/5, no dup session) → answered all-A → session→completed (0 active);partner_finished_gameto BOTH; reveal 3/5 in sync symmetric + correct Match/Differ + You/QA attribution on both devices (QA dark / Sam light). 0 FATAL. 2. Spin the Wheel ✅ — Ready=Start session (R11 change) verified; spun→Stress→10Q; mixed answer types (free-text + 1–5 scale) render+accept; Sam joined active session via Play hub (Q1, no dup/new spin); both finished→completed (0 active); results "Here's how you each answered" with You/QA free-text + scale; C-NAV-002 RE-VERIFIED LIVE — results → BACK → wheel hub (Spin/History), NOT the finished session (the R11-deferred confirm). 0 FATAL. 3. How Well ✅ — QA subject 5·Quick (answered 5 about self), Sam joined as guesser ("Predict how QA answered…", asymmetric), guessed 5 → score 5/5 "Perfect read" + per-Q breakdown (✓ + answer, choice+scale) symmetric both devices; completed (0 active). 4. Desire Sync ✅ (premium, couple-shared via Sam-on) — free QA opened setup (no paywall); QA all-Yes, Sam joined + Y,Y,Y,N,N → reveal "3 shared desires · 2 kept private" (only mutual-Yes shown, 2 mismatches "Private") symmetric both devices; completed (0 active); Sam premium restored OFF. All 4 async session games verified end-to-end. - Uncommitted (user commits): R11 art fixes —
app/.../ui/theme/Theme.kt,app/.../ui/components/BrandIllustration.kt,app/.../ui/components/EmptyState.kt; + R12 A-201 fix —app/.../ui/dates/DateMatchViewModel.kt(CouplePremiumChecker gate +paywallRequiredevent) +app/.../ui/dates/DateMatchScreen.kt(navigates to paywall). Everything else committed in2cd0af6. Build installed both emulators. - Foreground "partner started a game" alert + bold Game Waiting card (R10, user-requested) — DONE+VERIFIED. When the app is OPEN and a partner starts a game, a prominent in-app top banner (" started " + Join) slides in (mirrors chat's in-app surface) instead of the easy-to-miss system notification; Join → joins the game. Verified live for all 4 session games: banner name correct (Spin the Wheel / This or That / How Well Do You Know Me / Desire Sync); Join → joins wheel (ToT shows graceful "QA is playing a Wheel game" when types mismatch); suppressed when already on that game's screen (added
ActiveGameSessionMonitor.enter/leavetoWheelSessionViewModel— the others already had it). Home "Game waiting" card redesigned as a bold purple-gradient hero (glyph + game name + "Join the game"), promoted to top of "Waiting for you", verified both themes → tap joins the specific game (not the Play-hub fallback). FCM transport on the emulators is flaky (FcmRetry); the banner was exercised via a data-only high-priority send to the partner token (faithful to the deployed payload). - Pass C progress (R10): Settings family ✅ (dark + Security on light): Settings list, Subscription, Security, Delete account, Notifications all render clean both-relevant-themes; 4 illustrations confirmed in-context (Security padlock, Delete-account doorway, Quiet-hours moon, + Subscription); back-stack OK (Security/Delete/Notif → BACK → Settings). Found C-SEC-001 (P2) — accepter's Recovery phrase disabled + wrong "invite your partner" copy (see Open issues). Wheel back-stack RE-CHECKED = not a trap: live wheel session → BACK → spin/setup → BACK → Play hub (2 backs, no dead-end); earlier "stuck" was automation cycling. Leaving mid-wheel leaves a resumable abandoned active session (normal; cleaned). Home ✅ both themes (stale game card gone).
- 6. Spin the Wheel ✅ — spun→Physical Intimacy→session; Sam joined at Q1; both answered 10 (A/B + 1–5 scale + free-text); per-Q You/QA breakdown renders; completed, 0 active. (Helper
wheel_drive.pyhandles mixed types; free-text Qs hide "Next" behind IME.) - 7. Date Match ✅ — swipe deck ("Swiping with Sam/QA"); QA+Sam mutual like → "It is a match!" modal live; new match persisted (date_matches 3→4); swipe action
enc:v1:at rest (only swipedAt clear). - Pass B = COMPLETE (R10): all 7 games played end-to-end 2-device, 0 bugs. 2 observations: CC day-counter desync (Future.md, by-design?) · WATCH — wheel back-stack: after finishing Spin-the-Wheel, system-BACK from the results re-enters the completed wheel-session screen (loop), needed an app relaunch to escape. Possibly automation artifact (missed taps) — recheck deliberately in Pass C nav fuzzing; file if reproducible (P2 back-stack).
- 5. Memory Lane ✅ — new capsule sealed (3-mo pick) with future open date; title+content
enc:v1:at rest (admin-verified); lists cross-session. Minor cosmetic: "Opens in 2 mo" shown for a 3-month selection (relative-time display nit; not filed). - 4. Connection Challenges ✅ — Gratitude Week (in-progress from R9): per-day step, "I did it today", "waiting for partner" both-gate, missed-day catch-up ("Pick it back up"), streak 🔥→2 synced both devices. UX note (Future.md): "Day N of 7" counter diverges between partners after asymmetric catch-up (QA D4/Sam D3) while streak stays synced — plausibly by-design, non-blocking.
- Pass B progress (R10): 1. This or That ✅ — Deep×10 (varied): QA started, Sam joined via Play-hub card (no duplicate, 1 session), both answered 10, results symmetric both devices ("8/10 in sync", per-Q Match labels correct), session→completed, 0 stale. 2. How Well ✅ — QA-subject 5·Quick: QA answered 5 about self, Sam joined as guesser (asymmetric join works), predicted 5, score+breakdown render correctly (1/5, ✓/✗ guess→actual incl. scale Q), completed, 0 stale.
- R10 scratchpad drivers (reuse):
r10_set_premium.js <QA|Sam> <on|off>·rv_gate.js/rv_markreveal.js(raw-API) ·hw_drive.py <serial> <rounds>(taps first option+Confirm per Q) ·rv_inspect.js/rv_sessions.js(admin reads). Game-option taps: use uiautomator bounds, NOT fixed coords (layouts shift per question; last Q button = "Done →" not "Confirm →"). - Admin writes: user authorized this session (2026-06-26) → premium toggle + baseline reset now working. Baseline reset done (0 active sessions; stale 06-24/06-25 answers cleared). Premium toggle:
scratchpad/r10_set_premium.js <QA|Sam> <on|off>. - Pass A ✅ (R10): neither-premium → Desire Sync shows 🔒 + opens paywall ("Go deeper together"); toggled Sam premium ON → QA(free) Play hub badge cleared live + Desire Sync opens setup (no paywall) = couple-shared unlock holds. Code audit: all gates use
CouplePremiumCheckerexceptSubscriptionScreen(by-design own-status) +DailyQuestionResolver(per-user premium-question fallback — verify in Pass B/E it doesn't desync the couple's daily Q). Other 7 features share the verified path (R9 enumerated each). - Build: HEAD
e6a8dee— clean working tree (reveal feature committed: couple-key encryption, read-gatedsecuresubdoc,onAnswerWrittenboth-answered copy,onAnswerRevealed). Rebuilt + installed on both emulators this session. - Daily-reveal QA (2026-06-26, live, both emulators 5554 dark / 5556 light): Gate (raw API): only-1-answered → partner reads metadata 200 but content 403, non-member 403/403; both-answered → partners read each other 200/200, non-member still 403/403. At-rest: answer doc content-free metadata only; content in gated
secure/payload(enc:v1:). Reveal: shows the partner's answer both directions (the fixed bug) — QA↔Sam. Pushes:onAnswerWrittenfires (both-answered "unlocked ✨" copy is in deployed code);onAnswerRevealedfired live (isRevealedflip → "notified partner that X opened"). 0 FATAL either device. Today's test answers wiped after; baseline clean. One low-sev robustness note →Future.md(revealisRevealedwrite isn't retried if it fails). Note: stale active wheel session + 06-24/06-25 unrevealed answers are pre-existing test pollution (confound the Home dashboard daily card; not the reveal feature). - Devices / accounts: emulator-5554 = QA (
Y05AKO2IlTPMa0JQW1BiNIM0uzK2) · emulator-5556 = Sam (imDjjO…) · paired, coupleIdXal3Kw3gjSdn0niERYKJ, both free (baseline restored). - Docs: Playbook
ClaudeQAPlan.md· CoverageClaudeQACoverage.md· IdeasFuture.md## QA· BrandingClaudeBrandingReview.md.
Severity board
| Severity | Open | Fixed (pending 1 confirm) |
|---|---|---|
| P0 | 0 | 0 |
| P1 | 0 | 0 |
| P2 | 2 (O-AGE-001 pre-ship, C-DARKART-002 decoupled dark-art) | 8 (6× C-THEME fixed, M-001 quiet hours, TEST-001 unit suite) |
| P3 | 3 (BRAND-DARK-COVERAGE, BRAND-ICON-CUSTOM, C-ORIENT-001) | 0 |
R16: ran the new cheap gates → found+fixed TEST-001 (unit suite was silently red, 5 failures) → 205 unit + 24
functions green. Entrypoint smoke 6/6 on BOTH emulators, 0 blocked. Theme triage: of the 9 filed C-THEME, 3 were
not real shipped defects (C-THEME-003 @Preview false positive [theme-scan now excludes @Preview]; C-THEME-006/007 dead
PlaceholderScreen [deleted]); 6 real → FIXED → theme-scan CRITICAL 9→0, build+units green; C-THEME-001/002
verified LIVE (dark). Confirmed + pruned R15 fixes N-001 (P1, live add/delete) + N-002 (P2, Home "Date coming
up"). M-001 carried (not re-tested this round). Remaining open: 1 P2 pre-ship (O-AGE-001) + 3 P3 (2 brand
backlogs + C-ORIENT-001). 4 C-THEME fixes (004/005/008/009) pending live confirm next round.
Issues — open (Pass C theme defects + brand-asset backlogs)
Surfaced by the 2026-06-27 brand standards audit (new Pass H/Pass C mandates) and the 2026-06-28 theme-scan run. Brand-quality defects (light-only art, generic icons) and Pass C theme defects (hardcoded surface/background colors) both live here; asset lists + prompts are in
ClaudeBrandingReview.md.R16 reclassified (NOT real shipped defects, removed from open): C-THEME-003 =
WheelCompleteScreen.kt:507is inside@Preview fun WheelRevealPreview()— design-time only, never shipped → false positive;scripts/theme-scan.shnow excludes@Previewcomposables so it won't re-file. C-THEME-006 / C-THEME-007 =PlaceholderScreen.kt(SignalChip/PreviewPanel) had 0 source references (replaced by the real dashboard perdocs/qa/private-mvp-checklist.md) → dead code, file deleted.
| ID | Sev | Area | Description | Suggested fix | Status |
|---|---|---|---|---|---|
| C-DARKART-002 | P2 | Visual / theme-variant art (Pass C/H) | Dark-variant art does NOT render in the decoupled in-app-Dark + system-light state (a common, supported config: user sets in-app Dark on a light/auto-system phone). Pack-art banners (QuestionPackLibraryScreen.kt:223 via packArtworkRes) + ~7 literal painterResource(R.drawable.illustration_/pack_art_) sites (SpinWheel, AnswerReveal, Home, PlayHub, + debug ArtPreview) load via raw painterResource, which resolves the -night qualifier off the system uiMode, not the in-app theme — so the new drawable-night-nodpi/ dark variants (BRAND-DARK-COVERAGE batch) only show when system=night. Verified live R17 (5554): in-app-Dark + system auto → light pack art on dark Question Packs; forcing system night=yes → correct dark aubergine art (variants themselves are correct + packaged). Recurrence of the C-DARKART-001 class for the direct-painterResource sites the R11 fix didn't cover. |
Route these sites through BrandIllustration (resolves -night off LocalAppInDarkTheme via createConfigurationContext — the R11 pattern; also gains edge feathering), or drive a config override from the in-app theme. Re-run the decoupled-state check both directions after. |
Open |
| C-THEME-001 | P2 | Dates / Bucket List | AddItemDialog used a hardcoded light surface (Surface(color = Color.White), BucketListScreen.kt) → light dialog on dark. |
→ MaterialTheme.colorScheme.surface; also themed the whole dialog (Cancel→secondaryContainer, Add→primary, CategoryChips→primary/surfaceVariant) — closes the Future.md "mixed dark/light dialog" note. |
Fixed — verified LIVE R16 (dark): dialog surface dark, fields/chips/buttons readable. Pending 1 confirm. |
| C-THEME-002 | P2 | Dates / Bucket List | CategoryBadge + category filter chips used hardcoded light colors (Color(0xFFF3E8FF); ternary Color(0xFFFFF8FC) — the latter evaded the scanner's color = Color( regex). |
→ badge primaryContainer/onPrimaryContainer; chips primary/surfaceVariant. |
Fixed — verified LIVE R16 (dark): item-card "Adventure" badge + All/Adventure filter chips readable. Pending 1 confirm. |
| C-THEME-004 | P2 | Questions / Discussion thread | WaitingPhase "Your answer is saved" banner used Color.White.copy(alpha=0.78f) (QuestionThreadScreen.kt). |
→ surfaceVariant.copy(alpha=0.78f) (children already use onSurface/onSurfaceVariant). |
Fixed R16 (theme-scan 0, build+units green) — pending live confirm. |
| C-THEME-005 | P2 | Wheel / History | History premium-lock icon used Color(0xFFF8F1FF) bg + Color(0xFFB98AF4) tint (WheelHistoryScreen.kt). |
→ bg surfaceVariant, tint primary. |
Fixed R16 (theme-scan 0, build+units green) — pending live confirm. |
| C-THEME-008 | P2 | Dates / Date Match | "View matches" heart button used Color(0xFFF3E8FF) bg + Color(0xFF56306F) tint (DateMatchScreen.kt). |
→ bg primaryContainer, tint onPrimaryContainer. |
Fixed R16 (theme-scan 0, build+units green) — pending live confirm. |
| C-THEME-009 | P2 | Dates / Date Match | Match-count badge used Color(0xFF8D2D35) + Color.White text (DateMatchScreen.kt). |
→ error/onError (semantic count badge, adapts both themes). |
Fixed R16 (theme-scan 0, build+units green) — pending live confirm. |
| O-AGE-001 | P2 | Release / store readiness (Pass O) | No age gate / age verification despite adult-intimacy content. Sign-up collects only email+password+confirm; Create Profile collects name+gender; domain/model/User.kt has no DOB/age field; the only "birthday" in-app is the partner's relationship special-date (SpecialDatesSection), not age. Yet the app ships sexual/intimacy content (Desire Sync). Google Play content-rating + sexual-content policy generally require an accurate maturity rating and may require an age gate. (Static finding — 2026-06-28 QA-plan gap review; confirm against current Play policy + intended content rating.) |
Add an 18+/age-appropriate gate where required + complete the Play content/maturity questionnaire to match actual content. Pre-ship gate (does not block per-round flawless). | Open (pre-ship) |
| C-ORIENT-001 | P3 | Visual / config (Pass C/O) | App not portrait-locked; landscape layout unverified. AndroidManifest.xml declares no screenOrientation, so MainActivity rotates to landscape — but no round has verified the landscape (or tablet/large-screen, minSdk 26 / targetSdk 35) layout renders correctly. (Static finding — 2026-06-28 QA-plan gap review.) |
Decide: lock portrait in the manifest if landscape isn't a supported experience, or certify the landscape layout (Pass C orientation check). | Open |
| M-001 | P2 | Settings / notifications | Quiet hours did not suppress backgrounded/killed partner pushes. "Quiet hours — 10 PM–8 AM, no notifications" was stored local-only (DataStore); partner pushes carry a notification block the OS shows directly when the recipient is backgrounded/killed, and the only client check (PartnerNotificationManager.isInQuietHours) runs foreground-only (AppMessagingService.onMessageReceived). So the "no notifications" promise was broken for the main case. Repro: Sam QH ON @22:28 CST, backgrounded → QA chat → "QA sent a message" posted to Sam's shade. |
Client mirrors window+tz to users/{uid}; Cloud Functions (onMessageWritten/onAnswerWritten/onAnswerRevealed/onGameSessionUpdate) suppress via fail-open notifications/quietHours.ts:recipientInQuietHours(); firestore.rules user-doc allowlist extended for quietHours*+timezone. |
Fixed — verified live R15 (fn log suppress vs notify; deployed prod). Pending 1 confirm. |
| TEST-001 | P2 | QA infra / unit tests | Unit suite was silently RED (5 failures) — the regression net was non-functional, undetected until R16 ran it for the first time (test-vs-code drift). (a) PartnerNotificationManagerTest (4×) stubbed quietHoursManager.isInQuietHours(any()), but the method's default now: Calendar = Calendar.getInstance() param made the stub pin the instant captured at stub time → never matched the SUT's call-time clock → MockKException. (b) CloserBrandCopyTest asserted every privacy message ≤64 chars, which predated the intentional 150-char flagship primaryMessage (commit 6d74c6a; BrandMessageRotator wraps it at maxLines=3). |
Test-side only (production correct: quiet hours verified live R15; flagship is committed design). Stub → isInQuietHours(any(), any()); brand test caps short slogans ≤64 + flagship 1..160. |
Fixed — verified R16 (./gradlew testDebugUnitTest 205 ✅, functions 24 ✅). Pending 1 confirm. |
| BRAND-DARK-COVERAGE | P3 | Art / theme | Most illustrations are light-only — only 12 of ~25 have a drawable-night-nodpi/ dark variant. All illustration_couple_* heroes (paywall/subscription/onboarding/invite/history), daily_question, partner_activation, tonight_partner_prompt, together_empty, and all 10 pack_art_* banners show the light/pink image on a dark screen (feathered edges don't change the image colors). |
Generate dark/aubergine-palette variants for each light-only asset → drawable-night-nodpi/ (identical filename); BrandIllustration auto-selects per in-app theme. Re-run the decoupled-theme check. List in ClaudeBrandingReview.md. |
Open (P3) |
| BRAND-ICON-CUSTOM | P3 | Icons / brand | ~60 distinct generic Material icons across ~201 call sites (generic hearts Favorite/FavoriteBorder, Person, Lock, Star, PlayArrow, ArrowBack, …) — these are placeholders, not the Closer brand. |
Replace each with a bespoke glyph_* in the house style (ImageVector.vectorResource + Icon(tint)), highest-traffic first; ship bar = 0 generic Material icons. Backlog table in ClaudeBrandingReview.md. |
Open (P3) |
Resolved & confirmed (archived — full detail in git history)
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 · N-001 · N-002 — all fixed and re-verified (R16 pruned N-001 [Bucket List non-functional → CRUD works; confirmed live add/delete] + N-002 [Date Builder no-op → Home "Date coming up"; confirmed live]) (R14 pruned the 5 R13 fixes — C-DARK-UI-001 ToT dark redesign · C-DARK-UI-002 check-in label/value · C-DARK-UI-003 bottom-inset clearance · C-ART-EDGE-002 8 opaque heroes feathered · J-OBS 48dp touch targets — held through R14's full A–J sweep; in working tree) (R13 pruned A-201 [Date-Match premium ideas ungated → now gated to Paywall via CouplePremiumChecker] — fixed R12, confirmed live R13; in working tree) (R12 pruned C-DARKART-001 [in-app-theme -night art] + C-ART-EDGE-001 [feathered edges] — fixed R11, held through R12 visual sweep; in working tree) (R11 pruned the 5 R10 P2 fixes — C-HOME-001 single Home card · C-NAV-002 popUpTo(WHEEL_SESSION){inclusive} present + R10-live · C-NAV-003 single app bar re-confirmed live · C-PW-001 dark paywall pills legible re-confirmed live · C-SEC-001 recovery row active for accepter re-confirmed live — all committed in 9c84c36; E-GAME-003 onGamePartFinished deployed + committed 2cd0af6) (E-GAME-002 confirmed live R10: startNotifiedAt set + partner_started_game queued to right partner + foreground banner + Join→joined active ToT at same Q1; commits 6e79cd9/38fdc6d) (commits in history; F-RACE-001 re-confirmed R8; I-001 query→whereIn(dayKeys) + I-002 Long-score→Number.toInt(), fixed ab29f6b, re-confirmed live R9: 0 outcomes denials/CCE). Pruned per the one-confirmation-round rule. (C-OBS / outcomes list / SubscriptionScreen per-user gate = investigated, not bugs.)
Security cornerstone — clean (Pass D, deep dive, Round 7)
- R17 re-verified (live, admin + raw-API): D1 at-rest — messages
text,lastMessagePreview, Memory Lane capsules (content+title), all 4 game answers (this_or_that/desire_sync/how_well/wheel), date_swipeaction→ allenc:v1:; only metadata clear. D3 negative access — minted non-member token → raw Firestore REST: couple doc / messages / capsules / desire_sync reads + premium self-grant all DENIED 403. Scripts:scratchpad/d1_atrest.js,d1_probe3.js,d3_negative.js. - D1 at-rest: chat text +
lastMessagePreview+ all 4 game-answer collections (ToT / How Well / Desire Sync / Wheel, both users) + Memory Lane capsules + date-swipe actions =enc:v1:. No plaintext content; only metadata in clear. - D2/D3 access: non-member denied all reads/writes (raw Firestore REST → 403); real premium write
users/{uid}/entitlements/premiumdenied (server-only → no self-grant); cross-couple denied. - D4 keys: couple key phrase-wrapped (argon2id); recovery phrase server-blind;
encryptedRecoveryPhrasewiped on acceptance; plaintextinviteCodenot exploitable (invite readable only by inviter; no code-encrypted secret persisted). - Robustness: malformed/abusive deep-link intents (unknown type, missing extras, injection/path-traversal) → 0 crash; killed-state cold-start chat deep-link → conversation loads.
⛔ "All notifications broken / app opens-and-closes" — ROOT CAUSE = splash crash (FIXED R10)
The actual cause was NOT routing — it was a crash in the splash-screen exit animation on notification cold-starts.
MainActivity.onCreate (added in 95cad84, 2026-06-25) set splashScreen.setOnExitAnimationListener { provider -> provider.iconView.animate()… }.
On a notification / PendingIntent cold-start the OS hands the splash view over without an icon (SplashScreenView: Icon: view: null),
and provider.iconView throws an internal NullPointerException (SplashScreenViewProvider$ViewImpl31.getIconView) →
onCreate crashes → "Force finishing activity" → the app opened and immediately closed on EVERY notification tap
(chat, game-start, results — all of them, because they share the cold-start path). This is why it looked like "all
notifications broke again." Normal launcher cold-starts were fine (icon present), which masked it.
- Why my earlier
am starttests missed it: shellam startuses a different splash transfer than the FCM PendingIntent handover (the SysUILaunch remote transition), so it didn't hit the null-icon handover. Alsoam force-stopcan't receive FCM at all (stopped-package broadcast exclusion) — must useam killto test killed-app push. - Fix (R10, working tree):
MainActivitywraps the icon scale inrunCatching(best-effort) and the view fade inrunCatching { … }.onFailure { provider.remove() }so the splash is always removed and onCreate never crashes. - Verified live: real FCM notification → killed (
am kill) Closer2 → tapped the OS notification → cold-start logsIcon: view: nullthenremove starting view, 0 FATAL, process stays alive, lands on Home (was the crash). Normal launcher cold-start still animates + works.
Notification deep-link routing — SINGLE mechanism (do NOT reintroduce a second one)
Invariant: an app-posted notification carries the resolved route in one place — the app_route extra —
and routing is MainActivity.deepLinkRouteFromIntent → pendingDeepLink → AppNavigation navigateRoute. Do not
also set an ACTION_VIEW + closer:// data Uri on the notification intent: for routes that have a navDeepLink
(conversation / answer_reveal / daily_question / question_thread / home / play) the NavController auto-handles that Uri
in addition to pendingDeepLink → a race/duplicate nav. That dual path is what kept re-breaking notifications.
- Why it broke "again" (root cause, traced via git):
aaab768/1b9d8cf/b9b1560built routing on thecloser://data Uri (NavController auto-handle) + apendingDeepLinkgated oncurrentRoute == HOME; then38fdc6dadded theapp_routeextra on top without removing the data Uri → two mechanisms for the same tap. The HOME-only gate also meant a warm tap from any non-Home tab setpendingDeepLinkbut never consumed it. - Fix (R10, working tree):
PartnerNotificationManager.showNotificationno longer setsACTION_VIEW/data Uri —app_routeextra only.AppNavigationpendingDeepLink gate broadened from== HOMEto!in entryRoutes(fires once past onboarding, on any main screen). Verified live (0 FATAL): killed-app tap → chat opens the conversation; all 4 game results pushes (partner_finished_game) load the real per-session results (wheel "Here's how you each answered" · This-or-That "5/5 in sync" · How Well "Perfect read 5/5" · Desire Sync "5 shared desires"); app_route-only path (no Uri) loads results; warm tap from Settings now routes (was the stuck case).
Round history (one line each)
- R14 (2026-06-27) — full fresh A–J run (pure QA, no code), FLAWLESS, 0 new findings. Confirmation round on the R13 build: A premium enforcement audit + couple-shared unlock + entitlement push live; B 3 async games full 2-device + first-finisher nudge + Memory Lane/CC/Date Match core; C decoupled-theme-art mandate; D cornerstone live (403s + enc:v1:); E triggers/copy live; F offline + process-death; I jank 5.25%; J 48dp holds. 0 FATAL both emulators. The 5 R13 fixes held → pruned to the archived line.
- R13 (2026-06-27) — open-backlog fix pass + full fresh A–J, FLAWLESS (0 open P0–P3). Fixed all 5 carried/open UI items (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, and shipped the branding Premium-unlock modal (one-time, both partners, couple-shared). A–J: D security cornerstone re-verified LIVE (non-member 403, self-grant 403, at-rest
enc:v1:); premium gates → Paywall; ToT both themes; jank 6.43%. Diff UI-only → E/F/G carried. 0 FATAL both emulators. App changes in working tree (user commits). - R12 (2026-06-27) — FRESH FULL A–J run + fix phase, FLAWLESS (0 open P0–P2). Found A-201 (P1): Date Match
premium ideas ungated (free users could like/match ★Premium ideas —
getDateIdeas()=all, no checker, badge only; escaped prior Pass A rounds) → fixed + verified live (gated LOVE/MAYBE viaCouplePremiumChecker→Paywall, SKIP passes). Pass B: all 4 async games full 2-device E2E (ToT/Wheel/HowWell/DesireSync) + first-finisher nudge + C-NAV-002- Ready=Start re-verified live. Pass D LIVE clean: non-member 403 (read+write), self-grant→403, game answers enc:v1:. Pass E smoke 6/6. Pass I jank 4.10% (art change perf-safe). New P3 C-ART-EDGE-002 (direct-call hero hard edges, deferred). C-DARKART-001+C-ART-EDGE-001 (R11) held → pruned. Retrospective added to Pass A (badge≠gate; try to USE premium content as a free user). Fixes in working tree (user commits).
- R11 (2026-06-27) — confirmation round, FLAWLESS (0 open P0–P2). Fixed the last open P2 C-DARKART-001 (dark-mode
art now follows the in-app theme:
LocalAppInDarkThemeCompositionLocal inCloserTheme→BrandIllustrationloads the-nightdrawable via acreateConfigurationContextwhoseUI_MODE_NIGHT_*comes from the app theme, not the system) and the open P3 C-ART-EDGE-001 (tiled art feathers its 4 edges to transparent viagraphicsLayer{Offscreen}+BlendMode.DstIngradients instead of hardclip+border;EmptyStatenow routes throughBrandIllustration). Verified live both decoupled theme directions (5554 system-light+app-Dark → dark aubergine art; 5556 system-dark+app-Light → light pastel art; both feathered), 0 FATAL, both apps alive. Re-confirmed + pruned the 5 R10 P2 fixes (C-HOME-001 single Home card · C-NAV-002 wheel-backpopUpTopresent · C-NAV-003 single app bar live · C-PW-001 dark paywall pills legible live · C-SEC-001 recovery row active for accepter live). Entrypoint launch-integrity smoke green on the fresh APK (launcher- notification cold-starts open & stay — splash-crash class clean). Art fixes in working tree; everything else committed
(
2cd0af6).
- notification cold-starts open & stay — splash-crash class clean). Art fixes in working tree; everything else committed
(
- E-GAME-003 (2026-06-27) — FIXED+VERIFIED+DEPLOYED: async-game first-finisher left the waiting partner un-notified.
Async games (this_or_that/wheel/how_well/desire_sync) write answers to
couples/{c}/{gameType}/{sessionId}and the session only flips tocompletedwhen BOTH answer — soonGameSessionUpdate(watches the session doc) never fired on a single finish, and the waiting partner got nothing ("Closer2 finished a game but the partner was never notified"). Fix = new Cloud FunctiononGamePartFinished(trigger on the answer doc; on exactly-1 answer, idempotently claimpartFinishNotifiedAton the session + sendpartner_completed_part"X finished their part — your turn to play!"). Verified live: QA finished ToT part → sessionpartFinishNotifiedAt=true, Sam queue got 1partner_completed_part, posted on Sam's device, tap → opened ToT, 0 FATAL. Deployed (onGamePartFinishedcreated,onGameSessionUpdateupdated). Funcs source uncommitted (user commits). - R10 (2026-06-26) — FULL ClaudeQAPlan run A–J + fix phase. Found 5 P2 in report-only passes, fixed + verified all live: C-HOME-001 (Home dup pending card), C-NAV-002 (wheel results→BACK re-entered finished session), C-NAV-003 (duplicate app bar on Wheel History/PartnerHome), C-PW-001 (dark paywall pills light-on-light), C-SEC-001 (Security read wrong recovery-phrase store → accepter couldn't view phrase; E2EE recovery itself sound). E-GAME-002 confirmed live (startNotifiedAt set + partner_started_game→right partner + foreground banner + Join→joined active ToT) → pruned. D1–D7 security clean (non-member denied all raw-API reads/writes, no self-grant, secure-subdoc gate correct, argon2id+AAD=coupleId). Concurrency double-start→1 session. Perf jank 5.53% / a11y font-2.0 reflows — no regression. Build OK, both emulators reinstalled, 0 FATAL, content still
enc:v1:. App fixes in working tree (user commits). - Notif→game fix + dark art + QA sweep (2026-06-26, uncommitted). E-GAME-001 (P1, FIXED+VERIFIED): game notifications "led nowhere" — backgrounded/warm taps landed on Home (MainActivity was standard launch mode →
onNewIntentnever delivered the tap's extras →pendingDeepLinkunset), and even when routed, the game screen showed setup instead of joining (one-shotgetActiveSessionForCoupleraced the post-push Firestore sync → returned stale-empty). Fixes:AndroidManifestMainActivity launchMode=singleTop+QuestionSessionRepositoryImpl.getActiveSessionForCouplenow SERVER-first (cache fallback). Verified live: Sam backgrounded → taps partner_started_game → lands IN the active This-or-That (1/10), joined, no duplicate session; back-stack sane (game→back→Home→back→exit, C-NAV-001 holds). Generic across game types (shared routing + getActiveSession). Dark-theme art: 12_darkvariants →drawable-night-nodpi/(light names) so dark mode auto-swaps; verified live (Security shows the aubergine variant on dark; light unchanged). QA sweep: tabs both themes, deep-link back-stack, all 12 illustrations both themes — 0 FATAL, baseline intact. - Brand art drop (2026-06-26) — wired + QA-swept, 0 issues. All 11 generated illustrations (A1–A12, source gitignored) wired into their screens via shared
EmptyState+ newBrandIllustrationhelper (commits077a408→5868d06). Complete both-theme sweep: in-context dark and light verified for Bucket List (A6), Quiet hours (A9), Security (A11), Delete account (A12) — all render as crisp rounded tiles, on-brand, no clipping/contrast issue; A1 (transparent), A3 (banner) + the empty-only states (A2/A4/A5/A8/A10, unreachable on the baseline couple) verified via the debug Art-preview gallery on both themes + the proven shared tile. 0 FATAL/ANR both devices; baseline intact (0 sessions/outcomes). Process catch: 5556 was on a stale build mid-sweep → reinstalled current, both now on768f511. Details inClaudeBrandingReview.md. - R9 — clean confirmation round (0 new findings): confirmed + pruned I-001/I-002 (0 outcomes denials/CCE on the fixed build); swept deferred Pass C deep/list screens (Answer History, Activity, Bucket List, Date Match/Matches — both themes) + Pass F network (offline cache render + clean reconnect). 0 open P0–P2.
- R8 — F-RACE-001 re-confirmed + pruned; Passes I (perf) + J (a11y) run; found+fixed+verified I-001 & I-002 (outcomes read: query rules-denied + Long/Int parse CCE → "Your Progress" was silently dead). 0 open P0–P2.
- R7 — multi-angle security/concurrency deep dive → cornerstone fully clean; F-RACE-001 found + fixed + verified. 0 new open.
- R6 — branding drop + Future.md backlog regression (white-keyhole icons/loader/splash, inclusive gender, copy, rate-limit split, results-push suppression, paywall retry/offline) → 0 new open.
- R5 — Cloud Functions deployed (E-OBS channel fix, E-003 results routing) + new Pass G (account creation / fake-account abuse) clean → 0 open.
- R1–R4 — baseline Passes A–F report-only; every P0–P2 found was fixed + verified (see archived IDs).
Operational constants
- Execution mode: autonomous run-to-completion — don't stop; fix blockers inline; cycle fix→re-QA until flawless. Don't hand back when context fills — re-read this run-state + coverage after any compaction. 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 without pausing. Only the macOS requirement for iOS (Parts 2/3) is a hard stop. - Hardening backlog → Future.md: App Check not enforced on Firestore. (Correction R15: the
users/{uid}update rule is NOT open — it enforces a field allowlist (firestore.rules~L198,hasOnly([...])); R15 extended it forquietHours*+timezone. Keep that list in sync withFirestoreUserDataSourcewhen adding a client-written field.)