> **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`/`EmptyState` helpers, 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`](docs/Engineering_Reference_Manual.md). Most fixed-and-pruned IDs above are documented in its [Known landmines and recent fixes](docs/Engineering_Reference_Manual.md#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.]**
- **(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** (`partFinishNotifiedAt` set + Sam queue `partner_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_game` to 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.**
- **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** ("<partner> started <Game>" + 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/leave` to `WheelSessionViewModel` — 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.py` handles 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 `CouplePremiumChecker` except `SubscriptionScreen` (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-gated `secure` subdoc, `onAnswerWritten` both-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:**`onAnswerWritten` fires (both-answered "unlocked ✨" copy is in deployed code); `onAnswerRevealed` fired live (`isRevealed` flip → "notified partner that X opened"). 0 FATAL either device. Today's test answers wiped after; baseline clean. One low-sev robustness note → `Future.md` (reveal `isRevealed` write 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).
| A-201 | P1 | Premium / Date Match bypass (Pass A, R12) | **Premium date ideas are NOT gated — free users can view, swipe, like and match them with no paywall.**`DateIdea.isPremium` is documented as "requires an active premium entitlement," but `DateMatchRepositoryImpl.getDateIdeas()` returns `DateIdeaSeed.all` (premium ideas included, **no entitlement filter**), `DateMatchViewModel` loads them with no `CouplePremiumChecker`, and `DateMatchScreen` only renders a cosmetic `PremiumBadge()` — no lock overlay, no paywall on like/super-like. So the premium tier for Date Match is unenforced. **Escaped prior Pass A rounds** (which asserted "all gates use CouplePremiumChecker" — Date Match has NO gate). (Plan buckets "premium bypass" as P0; filed P1 as it's a content-tier subset, no security/data impact.) | 5554 (QA **free**): Play → Date Match → reject through deck → reach a **★ Premium** idea ("night camping getaway") → Like (heart) → **no paywall**, swipe accepted, deck advanced to the next premium idea ("Zipline canopy tour"); 0 FATAL, no `PERMISSION_DENIED`. | Gate premium date ideas through `CouplePremiumChecker`: either filter `isPremium` ideas out of a free couple's deck, OR intercept like/super-like on a premium idea → route to Paywall when neither partner is premium (couple-shared). Mirror the Desire Sync / Question-pack gate. | **Fixed + verified live R12 (working tree; user commits)** — `DateMatchViewModel` injects `CouplePremiumChecker`; `swipeCurrent` intercepts LOVE/MAYBE on a premium idea when neither partner is premium → emits `paywallRequired` → `DateMatchScreen` navigates to Paywall; SKIP still passes; deck stays on the card. **Verified:** free QA Love on ★Premium "night camping" → Paywall ("Go deeper together"), no swipe; SKIP → advances, no paywall; 0 FATAL. (Server already blocked real self-grant per D5 — so no entitlement was ever exposed.) |
| C-ART-EDGE-002 | P3 | Art / hard edges on direct-call heroes (Pass C, R12) | **Hero illustrations rendered via direct `painterResource` (not the shared `BrandIllustration`) still show hard edges on dark theme** — the R11 C-ART-EDGE-001 feather fix only covered `BrandIllustration`/`EmptyState`. The Today "Weekend Side Quest" daily-question hero (light/pink art) renders as a **bright rounded-rect block with a hard bottom edge on the dark screen**. These direct-call heroes have **no `-night` variant** either. Likely same class: paywall couple art, onboarding, Home tonight-prompt/partner-activation, spin-wheel hero, pack art (all direct `painterResource(illustration_*)`). Matches the user's "all images should fade into the screen" request (only partially satisfied by C-ART-EDGE-001). | 5554 (dark): Today tab → daily question → "Weekend Side Quest" hero shows a hard bright edge against the dark card. | Route these heroes through `BrandIllustration` (gains feather + theme-variant), OR apply the same `featherEdges()` treatment at each call site; consider `tile`/`hero` variants. Verify each direct `painterResource(R.drawable.illustration_*)` site listed in the R12 grep. | **Open (P3)** |
- **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.
- **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 → `onNewIntent` never delivered the tap's extras → `pendingDeepLink` unset), and even when routed, the game screen showed *setup* instead of joining (one-shot `getActiveSessionForCouple` raced the post-push Firestore sync → returned stale-empty). Fixes: `AndroidManifest``MainActivity launchMode=singleTop` + `QuestionSessionRepositoryImpl.getActiveSessionForCouple` now 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 `_dark` variants → `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` + new `BrandIllustration` helper (commits `077a408`→`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 on `768f511`. Details in `ClaudeBrandingReview.md`.
- **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; `users/{uid}` update rule allows arbitrary non-`hasPremium` fields (tighten to a field allowlist).