From d554483ef2425437eb3e5ae1788e3e46bc749b34 Mon Sep 17 00:00:00 2001 From: null Date: Tue, 7 Jul 2026 19:54:05 -0500 Subject: [PATCH] docs(qa): R29 games/mechanics/retention/UI review round - ClaudeGamesReview.md (new): per-game fidelity verdicts vs real-world analogs, live 2-device results, retention assessment (Hooked/Fogg/SDT lens) with prioritized recommendations, UI warmth scores, content-DB audit notes. - ClaudeReport.md: R29 verdict entry (2 P1 + 2 P2 found & fixed, batches e5868bd + c36a101; new Ava/Ben fixture couple; premium lifecycle verified). - ClaudeQACoverage.md: R29 coverage line (Pass B re-run pass, onboarding live pass, daily loop fail->fixed->re-verified, deferred items listed). - Future.md: structural refactor specs (session-game engine dedupe, replay VM, shared composables, dispatcher injection, error surfacing) + retention and content follow-ups from the review. Co-Authored-By: Claude Fable 5 --- ClaudeGamesReview.md | 115 +++++++++++++++++++++++++++++++++++++++++++ ClaudeQACoverage.md | 2 + ClaudeReport.md | 10 ++++ Future.md | 15 ++++++ 4 files changed, 142 insertions(+) create mode 100644 ClaudeGamesReview.md diff --git a/ClaudeGamesReview.md b/ClaudeGamesReview.md new file mode 100644 index 00000000..d6345185 --- /dev/null +++ b/ClaudeGamesReview.md @@ -0,0 +1,115 @@ +# ClaudeGamesReview — Full Games, Mechanics, Retention & UI Review + +> **Session 2026-07-07 (Claude, senior-dev review round).** Scope: all 7 games verified live on a +> fresh 2-emulator couple, mechanics checked against their real-world inspirations (researched), +> retention assessment, full-app UI warmth sweep, code-quality audit + fix batches. +> Bugs are cross-referenced by ID; canonical defect state lives in `ClaudeReport.md`. +> Test couple: **Ava + Ben** (`j39xR7PVZLLCveWnmenU`), created through the real signup → invite-code +> → recovery-phrase pairing flow on `CloserQA2` (5554, light) + `CloserCodexQA` (5556, dark). +> Premium was granted via admin (user-authorized), verified (partner unlock modal + push), then +> revoked (locks confirmed restored). 0 active sessions at session end. + +## 1 · Per-game verdicts (mechanics fidelity + live results) + +| Game | Real-world analog | Fidelity | Live 2-device result | +|---|---|---|---| +| This or That | Rapid-fire preference quiz | ✅ Faithful | **PASS** — engineered answers → exactly 3/5 "in sync"; simultaneous symmetric reveal; warm framing ("Lots in common 💛", neutral "Differ" chips) | +| How Well Do You Know Me | The Newlywed Game | ✅ Core faithful; no round-2 role swap (see R-2) | **PASS** — subject/guesser roles correct on both intros; engineered 2/5 exact; near-miss scale renders amber "4 → 3" (isClose softening); low score framed "Getting there!" | +| Desire Sync | Mojo Upgrade-style mutual-desire matching | ✅ Canonical privacy contract, verbatim in intro copy | **PASS** — engineered intersection → exactly "2 shared desires / 3 stayed private"; non-mutual yeses never rendered on either device; count doesn't leak whose/which; answers `enc:v1:` at rest | +| Spin the Wheel | Conversation-deck wheel | ✅ Honest randomness (category picked during spin; decorative stop angle) | **PASS** — identical 10-question set both sides; blind until both answered; side-by-side reveal; completion gate blocks finish with unanswered prompts | +| Connection Challenges | 7-day couples habit challenge | ✅ Day-gating real (can't complete 2 days in one day) | **PASS** — premium challenge opened with entitlement; day-1 complete → "waiting for partner"; ⚠ day-2 prompt text visible a day early (spoiler, see U-13) | +| Memory Lane | Time capsule / letter to future selves | ✅ | **PASS** — sealed capsule shows only title + "Opens in 29 days"; title AND body `enc:v1:` at rest (admin-verified) | +| Date Match | Tinder swipe-to-match | ✅ incl. one-sided-like privacy | **PASS** — simultaneous mutual-LOVE race → exactly **1** match doc; celebration modal + "It's a match!" push on BOTH devices | + +**Shared engine seams, verified live:** one-active-game gate converges the second starter into the +active session (Ben's How Well attempt → "Ava is playing a Wheel game / Join the game"); +first-finisher `partner_completed_part` fires as an in-app banner (foreground) and real push +(killed app, cold-start tap opened the app cleanly, 0 FATAL); mid-game Quit abandons correctly +(admin: 0 active sessions after); "you both finished" results banner on both sides. + +## 2 · Bugs found (canonical detail in ClaudeReport.md) + +| ID | Sev | Summary | Status | +|---|---|---|---| +| DQ-MISMATCH-001 | **P1** | Partners can resolve **different daily questions** for the same day: server assignment is the unresolvable `q_default_daily` rollout fallback → each device falls back locally, and the fallback **pool depends on the viewer's premium state at that moment** (per-user entitlement, transient failures→false, mid-day flips). Reveal then cross-compares answers to different questions. Reproduced live (Ava: premium pool "tiny win" q; Ben: free pool "tiny debate" q; same clock/tz). | **FIXED** (paired fallback pinned to free pool — premium-independent) + follow-up filed to seed real server assignments | +| DQ-SLUG-001 | **P1** | Daily reveal renders the partner's answer as a raw option-id slug ("fake_awards_should_be_mandatory") when the id isn't in this device's question config (same class as HW-BREAKDOWN-001). | **FIXED** (humanize fallback in both reveal summaries) | +| GAME-HANG-001 (B1) | **P2** | ToT/HowWell/DesireSync answer listeners swallowed snapshot errors → game stuck on WAITING forever, no feedback (Wheel/Capsule already had the correct `close(err)` pattern). | **FIXED** (sources `close(err)` + VM `.catch` → retryable ERROR with "Try again" re-attach) | +| DST-001 (B2) | **P2** | `assignDailyQuestion.ts` hardcoded `CST_OFFSET_HOURS=-6`: wrong date labels + reveal times shifted 1h for the ~8 CDT months. | **FIXED** (Intl/America-Chicago helpers, matches streakReminder.ts pattern; 5 DST regression tests incl. spring-forward round-trip) | +| A-003b | P3 | Challenges catalog still shows 🔒 Premium badge after couple premium unlock (gate itself honors premium — opens fine). | Filed | +| BANNER-LIFE-001 | P3 | "You both finished · View" game banner persists across screens/games long after results were viewed (still up entering a NEW game's setup, and shown on top of the results screen it links to). | Filed | +| AUTH-TRUNC-001 | P3 | Welcome/splash privacy tagline clips mid-sentence without ellipsis ("Only you and your paired") — first-run first impression. | Filed | +| Copy/theming P3s | P3 | "Daily Fun Mc" internal category name as user-facing pill; unthemed teal date picker; DOB asked twice in signup→profile funnel; "It is a match!" vs push "It's a match!"; HW matched-row question text low contrast in dark; Date Match card-underlay text bleed; joiner screen titled "Waiting for X" when it's your turn. | Filed | + +## 3 · Retention assessment (Hooked / Fogg / SDT lens) + +**Strong foundations:** daily-question blind-reveal is a genuine daily anchor with a variable +reward (partner's answer) · streaks with repair + grace (aligned with Duolingo's leniency findings) +· Memory Lane = stored-value investment · Weekly Recap + Your Progress = progress evidence · +first-finisher nudges close the async loop (verified: banner + push both fire) · notification suite +respects quiet hours. + +**Prioritized recommendations:** +- **R-1 (P1): Fix the daily anchor supply chain.** `q_default_daily` fallback means the server + never really assigns; seed the Firestore `questions` pool (or make the callable assign a real id + from a synced catalog) so both devices always agree. (Client-side consistency fix landed this + session; assignment seeding is the durable fix.) +- **R-2 (P2): Role-swap rematch in How Well.** Newlywed Game's round structure swaps roles; the + results screen should offer "Now you guess — swap roles" (starter becomes guesser). Cheap, doubles + sessions per sitting, and answers "what do we do next?" at the emotional peak. +- **R-3 (P2): Return-tomorrow beat on results screens.** After every reveal the only CTAs are + "Play again / Back to Play". Add the next anchor: tomorrow's question teaser, challenge day N+1, + or streak state ("Day 2 tomorrow — keep it alive"). +- **R-4 (P2): Repeat-avoidance for game pools.** Desire Sync's effective pool is 102 binary items + drawn `shuffled().take(n)` per session (no cross-session memory) and Date Match's deck is + deterministic (identical first card for every couple, every install). Persist seen-ids per couple. +- **R-5 (P3): Purify the How Well pool.** `getQuestionsForPrediction()` pulls all choice/scale + questions including 500 `daily_fun_mc` novelty MCs ("laugh-track move") — dilutes the + partner-knowledge premise. Exclude `daily_fun_mc` (or curate a prediction-worthy tag). +- **R-6 (P3): Challenges — hide tomorrow's prompt** (spoiler kills the daily ritual) and consider a + visible 7-day chain with both partners' marks (social accountability). +- **R-7 (P3): Analytics funnel gaps.** `RetentionEvent` has GameStarted/GameCompleted but no + first-answer, reveal-viewed, waiting-abandoned, or notification-tap-attribution events — the async + dead-end (biggest churn risk) is unmeasurable today. +- **Content notes:** 48/150 sexual_preferences items unreachable by Desire Sync (non-binary configs + — fine, Wheel uses them) · `daily_fun_mc` rows carry `sex='neutral'` (currently benign only because + of the binary filter; retag to avoid a future landmine) · This or That mood pools healthy (60–75 + per mood vs 15 max draw); `rebuilding_trust` (45 q) reachable only via "All topics". + +## 4 · UI warmth sweep (vs docs/copy-guide.md) + +Scores 1–5 per cluster (tone / emotional safety / welcome / consistency): +- **Games (all 7): 5/5/–/4.** Best-in-class results framing (no shaming anywhere: "Getting + there!", "Differ" not ✗, "Nothing in common this round" never blames). Consistency dinged for the + banner lifecycle + slug leak (fixed) + per-game accent divergence (DS magenta is intentional and + works). +- **Onboarding/pairing: 4/–/5/3.** "You're in / Now bring your person in", value-prop checklist, + recovery-phrase ceremony with word-confirmation are excellent. Dinged: tagline truncation, DOB + asked twice, unthemed date picker, dead-end Create-Profile when signed out. +- **Home/Today: 4/5/5/3.** Daily card + mode system (Tiny Win Tuesday live-verified) is a strong + anchor; "Daily Fun Mc" pill leak; Home-card question can disagree with Today tab (DQ-MISMATCH). +- **Empty states: 5.** Memory Lane's "No capsules yet" is the pattern to copy everywhere. +- Both themes render cleanly on every screen driven this session (fresh-couple run covered light + on 5554 + dark on 5556 throughout); HW matched-row contrast is the one dark-mode ding. + +## 5 · Code-quality fixes landed this session + +- **Batch 1 (correctness):** B1 observer `close(err)` ×3 + VM `.catch`→retryable ERROR ×3 (+ + `GameCopy.SYNC_ERROR`); DQ paired-fallback premium-independence; reveal slug humanization ×2; + DST-safe Chicago date helpers + 5 regression tests. +- **Batch 2 (dead code / hygiene):** see ClaudeReport.md fix log. +- **Filed (structural, specced in Future.md):** generic session-game engine (ToT/HW/DS VMs are + ~80% clones; ~250-line state machine ×4), generic replay VM/screen (3× ~60-line clones), shared + waiting/length-picker composables, premium-check invocation helper, `GameConstants` for divergent + `ADVANCE_DELAY_MS` (420/380), dispatcher injection, error-surfacing standardization for 6 + silent-load VMs, `npm audit` 10 moderate (GCP transitive). + +## 6 · Coverage notes + +- Verified live: all 7 games happy path 2-device · result extremes (0-match impossible for DS + reveal? engineered 3/5, 2/5, 2-shared, 3/5-sync variants) · one-active gate · abandon · cold-start + push join · simultaneous-LOVE race · premium grant/unlock/revoke lifecycle · daily answer→reveal · + day-0 check-in · pairing + recovery-phrase ceremony · deep-link scheme audit (only /join/ links + exist; game entries are notification-driven, all tested). +- Deferred (unchanged from plan): full Pass E matrix, Pass K money path, release build, monkey, + TalkBack full pass (spot: n/a this round), TodayWidget interactive smoke (code-read only), + am-kill mid-session resume (cold-start-from-push covered the recovery class). diff --git a/ClaudeQACoverage.md b/ClaudeQACoverage.md index 96e4d1a0..8ce8e49b 100644 --- a/ClaudeQACoverage.md +++ b/ClaudeQACoverage.md @@ -1,6 +1,8 @@ # Claude QA Coverage Matrix > **Resume anchor — current status only.** Statuses: `pass | fail→id | todo | n/a | not implemented→Future.md | blocked→id`. + +> **R29 (2026-07-07, games/mechanics/retention/UI review):** Pass B (all 7 games) **re-run live 2-device on a fresh couple = pass** (engineered-answer verification; see ClaudeGamesReview.md) · onboarding/signup/pairing/recovery-ceremony/day-0 check-in **pass (live, first full run since R24)** · daily answer→reveal loop **fail→DQ-MISMATCH-001/DQ-SLUG-001 → FIXED e5868bd, re-verified** · premium grant/unlock/revoke lifecycle **pass** · cold-start push open **pass (organic, killed app)** · one-active gate/abandon/simultaneous-match race **pass** · TodayWidget interactive + TalkBack spot + am-kill mid-session resume **todo (deferred; cold-start-from-push covered the recovery class)** · Pass D at-rest spot-check (game answers + capsule on new couple) **pass (`enc:v1:`)**. > **R28 (2026-07-02) — closed R27's two gaps: FIXED HW-BREAKDOWN-001 (P3) + ran the 3 premium games live 2-device under an explicitly-authorized admin grant, then revoked. 0 P0/P1/P2, 0 FATAL.** **HW-BREAKDOWN-001 (P3) FIXED** — added `humanizeOptionId()` (`_`→space + Title-case) to all 3 fallback branches of `HowWellAnswer.displayText()` in `ui/howwell/HowWellScreen.kt` (proper `config` labels still win); built + installed both emulators → archived. **Premium grant (user-authorized this occurrence) → couple-shared unlock re-confirmed:** QA `entitlements/premium`=true (source `qa_admin`) unlocked BOTH — Sam's free Play hub dropped the 🔒 + QA got the one-time **"Premium unlocked ✨ You both have Premium now"** modal. **Pass B premium games — all 3 PASS live 2-device:** **Desire Sync** (Sam Y·Y·Y·Y·Y / QA Y·Y·Y·N·N → both devices show identical **"3 shared desires / 2 stayed private"** = the 3 mutual-YES only; You/partner both "Private"; Sam waiting-screen auto-flipped + fired "You both finished — View" banner) · **Memory Lane** (create+seal capsule → list shows **title-only under lock**, **sealed body does NOT leak**; clears up long-title pre-existing rows) · **Date Match** (mutual like on "Sunrise hike + thermos coffee" → **"Matched"** in couple-shared "Your Matches" **on both devices**; premium-tagged idea "Overnight camping getaway" swipeable/matched → A-201 gate lifts under premium). **Premium REVOKED after testing** (admin hasPremium/isActive/premium=false + `revokedAt`) — verified at DB (all-true→all-false) **and live in UI** (Memory Lane + Past Games 🔒 **Premium** again; free fixture restored). **Copy fix BANNER-RESULTS-COPY-001 (P4 cosmetic) — FIXED:** foreground game-results banner "See how you and **Your partner** compare" (capitalized generic fallback awkward mid-sentence) → `GamePromptBanner.styleFor()` RESULTS line now name-branches ("See how you and Sam compare" / name-free **"See how you both compare"**); compiles + installed both. **O-AGE-001 (P2) — 18+ age gate IMPLEMENTED + live-verified (throwaway 5558):** `AgeGate`(18+)+`User.birthDate`+datasource(read/create/`updateBirthDate`)+`firestore.rules` update-allowlist `+birthDate`+`SignupHandoff`; sign-up **Date of birth** picker validated **before account creation** (under-age → no account) + conditional DOB step in CreateProfile for Google/legacy (skipped for email via handoff). Live: DOB-required error ✓; **age 17 → "You must be at least 18 to use Closer." + no account** ✓; adult → account created → CreateProfile NAME step (DOB skipped, Step 1/3) ✓; profile save succeeds ✓. **Landmine caught live:** birthDate on the *update* path hit PERMISSION_DENIED (rules allowlist undeployed) → broke profile save → fixed by making the write **best-effort** (`runCatching`). **birthDate persistence: `firestore:rules` DEPLOYED by user + verified against the live rule** (authed update PATCH `{birthDate,lastActiveAt}` → 200 ALLOWED + persisted; non-allowlisted field → 403, so nothing weakened). Unit suite **279 green** (+8 AgeGate). **0 FATAL, both emulators.** > **R27 (2026-07-02) — full-plan COMPLETION (live-ran the passes R26 carried): P·I·J·F·G·H + Pass-B free games; 1 new P3 (HW-BREAKDOWN-001).** Same build as R26 (UI-only; app unchanged, only doc edits). **Pass P** question-bank PASS (6103 Qs: 0 empty/dupe/placeholder; choice/scale answer-configs all present; daily pack 500 intact; 22 categories; **Room identity hash `7e7d78…` preserved**). **Pass I** perf PASS (core-tabs **6.67% janky**/90th 31ms/0 missed-vsync; conversation scroll **3.04%**/90th 19ms — smoother than R8). **Pass J** a11y PASS (font_scale 2.0 → Home/Play/Settings reflow, scroll, no hidden critical actions [nav-label wrap known-acceptable]; reduce-motion no hang; **TalkBack 160/160 `Icon()` have contentDescription**; touch-targets carried [Batch-8 48dp]). **Pass F** resilience PASS (Messages **renders from cache in airplane-mode**, 0 FATAL, no dead-end; **portrait-lock holds** `requestedOrientation=PORTRAIT` under forced landscape; process-death via 6/6 smoke; concurrency carried). **Pass G/D3** security PASS — **live raw-API negative:** non-member ID token → couple doc / messages / daily answers / **date_reflections** / partner user-doc = **403 all**; **self-grant own premium = 403**; own-doc = 404 (valid auth, rules are the gate). **Pass H** branding PASS (all driven screens on-brand; 2 P3 backlogs carried). **Pass B free games (live 2-device this session):** This-or-That 5/5 (R26) · **How Well** answered→predicted→**"2 of 3"** with correct scale/choice breakdown · **Connection Challenges** resume→Day6 complete→advance Day7→mutual per-day gate ("waiting for partner"), streak/missed-day recovery · **Spin-the-Wheel** spin/category/session/written+choice answer/cap/quit (full 10-Q completion carries R18b). **NEW FINDING HW-BREAKDOWN-001 (P3):** How Well results breakdown renders a wrong *choice*-guess as its **raw option ID** (`a_small_romantic_surprise`) instead of the human label (correct answer resolves to text) — cosmetic ID-leak, untouched feature. **Pass B premium games (Desire Sync · Memory Lane · Date Match):** paywall **GATE verified** (all → Paywall for free: Desire Sync [Pass A], Date Match [free-swipe→paywall, A-201 holds], Memory Lane [premium-badged]); **GAMEPLAY `blocked→premium-grant-authorization`** (admin grant declined by auto-mode; gameplay carries R12/R14). **K/O `blocked→needs-device`/pre-ship** (unchanged). **0 FATAL, 0 P0/P1/P2; 1 new P3.** > **R26 (2026-07-01) — full-plan run on the text-input/truncation + DateReflection-hardening build; QA fixture re-restored (user-authorized); 0 defects.** Cheap gates GREEN (unit **244** · fn **47** · theme-scan CRIT **1=false-pos** [HomeScreen:829 brand count pill] · painter-xml **0** · wiring 🔴**0** dead). **Cold-start smoke 6/6 BOTH** (Sam+QA). **QA(5556) fixture RESTORED** — env logout after standby (couple key intact on disk) → admin password reset (user-authorized) + sign-in, **no restore ceremony**; Home + history decrypt. **Pass D E2EE at-rest CLEAN** — conversations (main+discussion), daily answers (both users), date_reflections all `enc:v1:` + content-free metadata; image msgs = encrypted mediaUrl only; **rules/crypto UNCHANGED this cycle** (R25 D2/D3 negative results hold). **Pass L** — inbox decrypted no-`enc:`-leak + full thread decrypt + **2-device round-trip QA→Sam decrypts** (restore-key integrity / R24 regression holds). **Pass A** free→**Paywall** (C-PW-001 pills legible). **Pass M** settings render/structure clean (debug rows gated). **Pass N — Date Memories/Reflection (R25 todo) CLOSED** — 2-device reflect→reveal, edit-before-reveal (rules deployed), notes field, bg/fg deep-link, `date_reflection_ready`/`opened` pushes, + R25-fixed hardening (read-failure→retryable ERROR, bounded couple-read) all verified live this session. **Text-input hardening (this build):** display-truncation removed from message/answer/question/error surfaces (ellipsize chrome only); free-text caps unified in `ui/components/TextInputLimits.kt` + trim-on-send; wheel written-answer cap added; near-limit counter. **Pass B (added live this round):** full **This-or-That** 2-device lifecycle — start (Sam)→waiting-for-partner gate→**join from QA's foreground banner**→both answer 5→**5/5 "Two peas in a pod" results synced on BOTH devices** (per-Q You/partner breakdown decrypted, all Match); confirmed live foreground game banners (`partner_completed_part` "Your turn" + results "You both finished · View") + real-time reveal sync (Pass E/F incidental). **0 FATAL, 0 new defects.** Not run (carried / pre-ship): K money-path (needs-device), O release build, device/OS matrix, remaining 6 games' B re-run (no games-logic change; smoke covers game cold-starts; last full 7-game B clean R12/R18b). diff --git a/ClaudeReport.md b/ClaudeReport.md index 379ebbd9..65057150 100644 --- a/ClaudeReport.md +++ b/ClaudeReport.md @@ -1,5 +1,15 @@ # Claude QA Report — Full-App QA (living report) +> **Verdict (2026-07-07): R29 = SENIOR-DEV GAMES/MECHANICS/RETENTION/UI REVIEW round (user-mandated) — all 7 games verified live 2-device on a FRESH couple created through the real signup→invite→recovery-phrase flow; found & FIXED 2×P1 + 2×P2; two fix batches committed.** +> **New fixture couple:** Ava(5554/CloserQA2, light) + Ben(5556/CloserCodexQA, dark), coupleId `j39xR7PVZLLCveWnmenU` (old QA fixture's device state was lost environmentally; QA-account credential ops are blocked in auto mode, so the round created a real couple — which itself live-verified onboarding/signup/pairing/recovery-ceremony/day-0 check-in). +> **Games (all PASS live, engineered-answer verification):** ToT exactly 3/5 in-sync · HowWell 2/5 with amber near-miss (isClose) + correct subject/guesser roles · DesireSync exactly "2 shared desires / 3 stayed private", non-mutual answers never rendered, `enc:v1:` at rest · Wheel identical 10-q set + completion gate · Challenges real day-gating (premium challenge opened under grant) · Memory Lane sealed capsule (title+body ciphertext, no list leak) · Date Match simultaneous-LOVE race → exactly 1 match + both pushes. Engine seams: one-active-game convergence ("Join the game"), first-finisher banner+push, cold-start push open (0 FATAL), quit-abandon → 0 active sessions (admin-verified). +> **Premium lifecycle re-verified live:** admin grant (user-authorized) → partner unlock modal + "Premium unlocked ✨" push on Ben → revoke → 🔒 restored both devices. +> **FIXED (commit e5868bd):** 🔴 **DQ-MISMATCH-001 (P1)** partners resolved DIFFERENT daily questions same day (server assigns unresolvable `q_default_daily` → per-device local fallback whose POOL depends on the viewer's premium state; reproduced live, fixed by premium-independent paired fallback; post-fix both devices verified identical). 🔴 **DQ-SLUG-001 (P1)** partner's reveal rendered raw option-id slugs cross-question (humanize fallback added, HW-BREAKDOWN-001 pattern). 🟠 **GAME-HANG-001 (P2)** ToT/HowWell/DesireSync listeners swallowed errors → eternal WAITING (close(err) + retryable ERROR w/ re-attach). 🟠 **DST-001 (P2)** assignDailyQuestion hardcoded -6 offset (Intl/Chicago helpers + 5 regression tests; **needs `firebase deploy --only functions`**). +> **FIXED (commit c36a101, hygiene):** dead ui/theme/Color.kt deleted · orphaned EditProfileScreen wrapper removed (Content stays — audit's "whole file dead" was wrong) · ArtPreview + PairedHomePreview debug-gated out of release graph · wheel_history route collapsed into game_history · CouplePremiumChecker now injects AuthRepository · copy: "It's a match!", Home pill "Daily Fun" (was "Daily Fun Mc"), welcome tagline no longer clips (AUTH-TRUNC-001). +> **OPEN (filed this round):** P3 A-003b stale 🔒 badge on Challenges catalog post-unlock · P3 BANNER-LIFE-001 results banner lingers across screens/games · P3 pack: unthemed date picker, DOB asked twice (signup+profile), HW matched-row dark contrast, Date Match card-underlay text bleed, joiner-screen "Waiting for X" copy when it's your turn, Challenges shows tomorrow's prompt today. Structural/retention recommendations + full per-game fidelity verdicts live in **ClaudeGamesReview.md**; refactor specs (session-game engine dedupe etc.) in Future.md. +> **Gates:** unit 244 green · functions **58** green (5 new DST tests) · assembleDebug clean ×2 · live regression post-fix (ToT 5/5 reveal + identical daily question both devices). **Server state clean:** 0 active sessions, 1 date_match, capsule sealed+encrypted, premium revoked. Session rule note: this round COMMITS per batch (supersedes older "user commits" convention). Emulator note: fixture pair is now Closer2(Sam, untouched)+CloserQA2/CloserCodexQA(Ava/Ben); CloserScreenshots solo fixture untouched. + + > **Verdict (2026-07-02): R28 = closed R27's two gaps — FIXED HW-BREAKDOWN-001 (P3) + live-ran the 3 premium games 2-device under an explicitly-authorized admin grant, then revoked. 0 P0/P1/P2, 0 FATAL; open P3 board now clear except the 2 brand-asset backlogs.** **HW-BREAKDOWN-001 (P3) FIXED:** How Well results breakdown was rendering an un-guessed/fallback choice as its raw option-ID slug (`a_small_romantic_surprise`); added `humanizeOptionId()` (`_`→space + Title-case) applied to all 3 fallback branches of `HowWellAnswer.displayText()` in `ui/howwell/HowWellScreen.kt` (proper `config` labels still win when present) — built + installed on both emulators. **Premium grant (user-authorized THIS occurrence: "grant QA premium via admin write, test the 3 premium games, then revoke") → couple-shared unlock re-confirmed live:** granting QA (`entitlements/premium` hasPremium/isActive/premium=true, source `qa_admin`) unlocked BOTH partners — Sam's (free) Play hub dropped the 🔒 on Desire Sync/Memory Lane and QA got the one-time **"Premium unlocked ✨ You both have Premium now"** modal (Pass A partner→both, live). **Premium games — all 3 PASS live 2-device:** ① **Desire Sync** — both answered privately (Sam Y·Y·Y·Y·Y, QA Y·Y·Y·N·N) → both devices rendered the **identical intersection: "3 shared desires / 2 answers stayed private"** listing exactly the 3 mutual-YES prompts (the 2 QA said No to stayed private), You/partner chips both "Private", and Sam's waiting screen **auto-flipped** when QA finished + fired the in-app **"You both finished — View"** banner. ② **Memory Lane** — created + sealed a capsule ("R27-Capsule-TITLE" / body "R27-SEALED-BODY-do-not-leak", 1yr → opens Jul 2 2027); the list row shows **only the title under a lock** ("Opens in 12 mo") — **sealed body does NOT leak** (confirms the odd-looking pre-existing rows like "R10_Memory_Testy…" are just long *titles*, not body previews). ③ **Date Match** — mutual like on "Sunrise hike + thermos coffee" (liked on QA, then Sam) produced a **"Matched"** entry in the couple-shared "Your Matches" list **on both devices** (Mutual love ×4, each with "We did this"); premium-tagged idea "Overnight camping getaway" is now swipeable/matched (A-201 gate lifts correctly under premium). **Premium REVOKED after testing (fulfilling the authorization):** admin set hasPremium/isActive/premium=false + `revokedAt` — verified at DB (BEFORE all-true → AFTER all-false) **and live in UI** (Memory Lane + Past Games show 🔒 **Premium** again; free fixture state restored). **Copy nit BANNER-RESULTS-COPY-001 (P4 cosmetic) — FIXED:** the foreground game-results banner read "See how you and **Your partner** compare" — the generic fallback is capitalized (correct at sentence-start for the started/joined/your-turn lines) but reads wrong mid-sentence. `GamePromptBanner.styleFor()` now branches the RESULTS line2: resolved name → "See how you and Sam compare"; no name → name-free **"See how you both compare"** (never an awkward capitalized generic). Compiles + installed both emulators. **O-AGE-001 (P2 pre-ship) — IMPLEMENTED + live-verified (18+ DOB gate):** new `domain/AgeGate` (18+, DOB→age math, 8 unit tests); `User.birthDate: Long?` + Firestore read/`createUser`/`updateBirthDate` + `firestore.rules` users-update allowlist `+birthDate`; `SignUpScreen` now has a **Date of birth** picker ("Closer is an 18+ app.") validated **before account creation** (under-age → no account); `CreateProfileViewModel/Screen` add a conditional first **DOB step** for arrivals without a DOB (Google/legacy) and skip it for email users (DOB carried via a `SignupHandoff` singleton — the post-signup Firestore write races auth-token attachment, so we validate at sign-up but persist at profile). **Live-verified on throwaway 5558 (fresh installs, own account):** DOB blank → "Please enter your date of birth."; **Jul 5 2008 (age 17) → "You must be at least 18 to use Closer." + NO account created** (stays on sign-up); adult DOB (2000/2001) → account created → CreateProfile opens at **NAME (Step 1 of 3), DOB step correctly skipped** for the email user; full profile save **succeeds** (no error). **Landmine caught live (would've shipped a broken flow):** the first attempt persisted birthDate via `updateBirthDate` (an *update* — the doc already exists from the FCM-token write), which the **deployed** rules reject (my allowlist add is undeployed) → **PERMISSION_DENIED broke the whole profile save**. Fixed by making the birthDate persist **best-effort** (`runCatching`, never blocks profile creation — the gate is already enforced client-side). **birthDate persistence: the additive `firestore:rules` allowlist change was DEPLOYED by the user — verified via the exact app update path (authed `PATCH {birthDate,lastActiveAt}` on own doc → 200 ALLOWED + persisted; was 403 pre-deploy) and confirmed the allowlist still rejects a non-allowlisted field (→ 403), so the deploy widened only `birthDate` and weakened nothing.** birthDate now persists end-to-end; the best-effort write also keeps profile creation resilient. Unit suite **279 green** (+8 AgeGate). Throwaway test accounts created + deleted; 5558 shut down (QA/Sam fixtures untouched). **0 FATAL** across the whole session, both emulators. All code + docs uncommitted (user commits); **firestore.rules changed (undeployed).** > > **Verdict (2026-07-02): R27 = full-plan COMPLETION — live-ran every pass R26 had carried; 0 P0/P1/P2, 1 new P3 (HW-BREAKDOWN-001), 0 FATAL.** Same build as R26 (UI-only diff; app unchanged, only doc edits since). Closed the gap I was honest about: **P** (question bank — 6103 Qs 0 empty/dupe/placeholder, configs complete, daily pack 500 intact, **Room hash `7e7d78…` preserved**), **I** (perf — core-tabs 6.67% janky/90th 31ms, conversation scroll 3.04%, 0 missed-vsync), **J** (a11y — font 2.0 reflow with no hidden actions, reduce-motion no hang, **TalkBack 160/160 icons labelled**, 48dp targets carried), **F** (resilience — Messages renders from cache in airplane-mode 0-FATAL, portrait-lock holds under forced rotation, process-death via smoke), **G/D3** (security — **live raw-API: non-member 403 on couple/messages/daily/date_reflections/user + self-grant 403**; own-doc 404 = rules are the gate), **H** (branding on-brand; 2 P3 backlogs carried). **Pass B games (live 2-device):** This-or-That 5/5 (R26), **How Well** 2-of-3 with correct scale+choice scoring, **Connection Challenges** resume→complete Day6→advance Day7→mutual per-day gate + streak/missed-day recovery, **Spin-the-Wheel** spin/category/session/answer/cap/quit (full 10-Q carries R18b). **NEW — HW-BREAKDOWN-001 (P3):** How Well results breakdown shows a wrong *choice*-guess as its raw option-ID slug (`a_small_romantic_surprise`) instead of the human label ("Only if needed" resolves correctly) — cosmetic ID-leak in the (untouched) How Well feature; recommend resolving the guessed option ID to its display text in the breakdown row. **Premium games (Desire Sync, Memory Lane, Date Match):** paywall GATE verified for all three (free → Paywall; Date Match free-swipe→paywall = A-201 holds); **GAMEPLAY was blocked this round — the admin premium grant was declined by the auto-mode classifier (not the specific action authorized by "run the full qa"); needs explicit OK. → RESOLVED in R28: user explicitly authorized the grant, all 3 premium games ran full 2-device, then premium was revoked (see R28 verdict above).** **K** (real money path) + **O** (release build/store) remain pre-ship/needs-device. All docs uncommitted (user commits). diff --git a/Future.md b/Future.md index 2090d29a..2a3a41e9 100644 --- a/Future.md +++ b/Future.md @@ -83,6 +83,21 @@ tokens R16, add-FAB `Color(0xFFB98AF4)`→`primary` R18.)_ ## QA +### From R29 games review (2026-07-07) +- **Seed the server daily-question pool** — `assignDailyQuestion` still assigns the `q_default_daily` rollout fallback for every couple (Firestore `questions` empty), so clients always use the local fallback. Seed real ids (synced with the asset DB) so the assignment actually pins the couple's question. (Client-side premium-independence fix landed in R29; this is the durable half.) +- **Generic session-game engine** — ThisOrThat/HowWell/DesireSync embedded VMs are ~80% clones of one ~250-line state machine (load/create/join/submit/retry/observe/abandon) and their data sources are structural copies (`EncryptedAnswerMapDataSource`). Migrate one game first (ToT), full live regression, then the rest. +- **Generic replay VM/screen** — 3× ~60-line clones (ToT/DS/HW replay). +- **Shared waiting/length-picker/error composables** — each game re-implements WaitingForPartner/length chips/error screens despite shared components existing. +- **Premium-check invocation helper** — CouplePremiumChecker is invoked 4 different ways across ~10 call sites with inconsistent failure fallback. +- **GameConstants** — divergent ADVANCE_DELAY_MS (420 vs 380), QUESTION_COUNT, SPIN_DURATION_MS per file; follow SessionLength's pattern. +- **DispatcherProvider injection** — Dispatchers.IO/Default hardcoded everywhere (testability). +- **Error-surfacing standardization** — BucketList/MessagesInbox/PlayHub/AnswerHistory/WeeklyRecap/Onboarding VMs swallow load errors (blank screens); adopt uiState.error + ErrorState for reads, _events snackbar for writes (ConversationViewModel pattern). +- **Retention (from ClaudeGamesReview.md):** HowWell role-swap rematch CTA · return-tomorrow beat on all results screens · per-couple seen-question memory for DS (102-item pool) + shuffle Date Match deck (deterministic first card for every couple) · exclude daily_fun_mc from HowWell prediction pool · hide Challenges day-N+1 prompt until its day · analytics: first-answer/reveal-viewed/waiting-abandoned/notif-tap events. +- **Content retag** — `daily_fun_mc` rows carry `sex='neutral'` (benign today only via the binary filter; retag to keep Desire Sync's pool clean). 48/150 sexual_preferences items are non-binary configs (Wheel-only) — intended? +- **npm audit** — 10 moderate (retry-request/teeny-request via @google-cloud/storage transitive); revisit on next functions dependency bump. +- **Banner lifecycle** — GamePromptController results banner should dismiss once results are viewed (BANNER-LIFE-001). + + Improvement & feature ideas surfaced while QA-testing as a consumer (each works today — none are defects). - **Tier 3: Compose screenshot diff for visual regression.** The static scanner in `scripts/theme-scan.sh`