> **RUN-STATE: Round 2 | Pass B RESTARTED from game #1 (R2-B2, play-as-the-user) | Build reinstalled both devices == HEAD source. NEXT ACTION: redo all 7 games via the real user nav path, in order — 1.This or That → 2.How Well → 3.Desire Sync → 4.Connection Challenges → 5.Memory Lane → 6.Spin the Wheel → 7.Date Match. Couple currently HAS premium (Sam premium ON) so premium games are unlocked; revert Sam to free after the premium games. Exit each 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.
> **Pass-B MINDSET (user, 2026-06-24): PLAY AS THE USER** — navigate only via the real in-app path a person would tap (no deep-links/admin pokes/shortcuts); expect what a user expects. When the natural path fails, **REPORT FIRST** (log issue + severity + the user action that failed & what was expected), **THEN** a minimal workaround to proceed — never silently engineer around breakage; a flow needing a workaround is broken and must be filed.
> 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).
> **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.**
| 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) |
**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.
| B-001 | Games / sessions | couples/{id}/sessions | **P1** (was P3→P2→P1) | **A finished game NEVER closes its session — there is no normal-user path to complete it — so every game leaves a dangling `status=active` session that blocks ALL other games.** Definitively proven on the R2-B2 restart: played This or That fully through on BOTH devices → both reached the results screen → **BOTH tapped the intended "Back to Play" button** → both navigated back to the Play hub, **but the session stayed `active`** (re-checked at +0s and +12s; no cloud-function cleanup; `completedAt` never set). So neither "Back to Play" nor leaving to Home completes a finished session — the ONLY thing that does is the **destructive "End their game"** (which the next game offers as "Sam is playing a … game", misleading copy since nobody is actually playing). Net: a couple **cannot cleanly play two games in a row** — after every game, the next one is blocked until one partner kills the (already-finished) session. This breaks the core game loop for every session → **P1**. **Fix:** mark the session `completed` when both players reach results (or on "Back to Play"); don't let a completed/stale session block new games; fix the misleading "is playing" / "End their game" copy. | Play This or That to results on both → both tap "Back to Play" → Firestore `couples/{id}/sessions` still has that this_or_that doc `status=active` (12s later) → open any other game → "Waiting for Sam — Sam is playing a … game", only "End their game" unblocks. | Open |
| B-002 | Home → Play nav (play-as-user) | HomeScreen "Your partner is waiting to play" card → "Play now" | **P2** | The Home card explicitly promises resuming the specific waiting game — "**Your partner is waiting to play. A game is ready for the two of you. Jump back in and keep the ritual going.**" → **"Play now"** — but tapping it just lands on the **generic Play hub** (the game list). It does NOT open/resume the waiting game, and the Play hub shows **no indication of which game is waiting** nor any "resume" affordance. A user told to "jump back in" cannot tell what to tap or how to rejoin. (Also: BOTH partners' Home cards say "**your** partner is waiting to play" for the same session, so each thinks the other is mid-game.) **Fix:** "Play now" should deep-link into the active session (its play/results screen), or the Play hub should surface a "Resume — How Well" entry; the Home copy should reflect whose turn it actually is. | Cold start → Home → tap "Play now" → lands on Play hub, no waiting-game indicator. | Open |
| B-003 | Desire Sync results (copy/clarity) | DesireSyncScreen results | **P3** | The results stats are internally **inconsistent/confusing**. Header: "**3 shared desires — 2 answers stayed private.**" Per-person row: "**You 5 private / Sam 5 private**". Progress bar caption: "**3 shared, 2 kept private.**" So the same screen says both "2 kept private" (total) AND "5 private" (each person) — a user can't tell whether 3 are shared or all 5 stayed private. (Mechanically "5 private" likely means "all 5 of each person's raw answers stay private, 3 happened to overlap", but that framing isn't clear and contradicts the "2 kept private" line.) **Fix:** make the three counters consistent (e.g., drop or relabel the per-person "5 private", or clarify "your individual answers are always private"). | Play Desire Sync to results → read the three differing private/shared counts. | 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).
**R2-B2 Desire Sync playthrough (couple-shared premium):** QA (free) entered with NO paywall (A-001 holds live). Both played full 5 Yes/No; QA T,T,T,T,F + Sam T,T,F,T,F → results show **exactly 3 shared desires** (the mutual-yes Q1/Q2/Q4) with Q3 (mismatch) and Q5 (both no) correctly **hidden** — reveal/privacy logic CORRECT; results match on both devices; no crash. Findings: B-003 (P3 copy), C-DS-001 (P2 dark contrast on revealed list).
| C-CC-001 | Nav / layout — duplicate header + double back | ConnectionChallengesScreen (series list) | **P2** | The screen shows **TWO stacked "Connection Challenges" titles, each with its own back arrow** — a nav-scaffold app bar (title "Connection Challenges" + back) AND an in-content header ("Connection Challenges / Pick a series to build a habit together." + a second back arrow right below it). Verified it's a **redundant duplicate header, not a double-pushed route**: tapping the inner back arrow pops straight to the Play hub (same as the app-bar back). No dead-end, but two identical titles + two back buttons is confusing ("which back do I press?") and looks broken — exactly the "double back" case to flag. **Fix:** drop the in-content TopAppBar/back (let the nav scaffold own the title+back), or remove the scaffold bar for this route. | Play hub → Connection Challenges → two "Connection Challenges" headers + two back arrows stacked at top. | Open |
| C-DS-001 | Theming / readability (dark) | DesireSyncScreen results — "You both said yes to" list | **P2** | In **dark mode**, the revealed shared-desire list items render as **dim, low-contrast muted-pink text on a dark pink-tinted card** — legible but well below the crisp high-contrast black text the same items show in **light mode** (verified side-by-side: QA dark vs Sam light). Given the user's "text must be readable" bar — and that this is the intimate payoff content the user most wants to read — the dark-mode contrast is too low. (May be intentional "muted" styling; if so it needs a dark-mode-specific brighter token.) | 5554=Dark: play Desire Sync to results → the 3 shared-desire rows are dim/hard to read vs the same rows on 5556=Light. | Open |
_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).
| 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`. | **FIXED + DEPLOYED** — added member-read + ciphertext-enforcing `capsules` rule (title/content/promptUsed must be `enc:v1:`) and a `challenges` rule (catalog-referenced, progress-only). Re-verified live: Memory Lane shows empty state, Connection Challenges shows the series list, **0 permission errors**. |
| 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). | Open (Pass F) |
| (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. | — | Closed (by-design) |
- **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` (`fromRemoteType` → `routeFor`); chat opens the exact conversation, reveal→answerReveal(questionId), games→Play, capsule→Memory Lane, etc.
| 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._