diff --git a/ClaudeReport.md b/ClaudeReport.md index faca9f72..b2d0ba71 100644 --- a/ClaudeReport.md +++ b/ClaudeReport.md @@ -37,11 +37,32 @@ _(Prior games/notifications QA from 2026-06-24 was completed + verified; superse **Launch/crash sweep (QA, free):** This or That ✅ (mood/length select), How Well Do You Know Me ✅ (intro), Connection Challenges ✅, Spin the Wheel ✅ — all render, **no FATAL**. Desire Sync + Memory Lane are premium-gated (covered in Pass A; gameplay needs premium toggle). Date Match: todo. Full two-device start→finish + results not exhaustively re-run this round (the prior round verified `onGameSessionUpdate` start/finish end-to-end). -## Pass C — Visual (light + dark) -_not started_ +## Pass C — Visual (light + dark) (main screens verified; deep/stateful screens pending) +**Method:** 5554=Dark, 5556=Light; readable dark|light pair montages + a code scan for non-adapting colors. +**Verified clean (both themes, readable, no clipping):** Home, Today, Play, Messages inbox, Settings. `closerBackgroundBrush()` is theme-aware (adapts). No FATAL on these. +| ID | Area | Screen | Severity | Description | Status | +|---|---|---|---|---|---| +| C-OBS | Theming | ~20 screens (AnswerRevealScreen 15, WheelSessionScreen 14, DateMatchScreen 10, PaywallScreen 9, BucketListScreen 9, SettingsScreen 7, HomeScreen 5, …) | observe | Use hardcoded `Color(0x…)` literals (195 total) that don't adapt to theme — a dark-mode contrast risk to verify per-screen. Main screens checked look fine; deep/stateful screens (reveal, wheel session, dates, bucket list) still need visual verification in both themes. | Open (verify in continuation) | -## Pass D — Security & Encryption -_not started_ +_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 D — Security & Encryption ✅ clean (no P0/P1 found) +- **D1 at-rest:** all private content is ciphertext — message `text` + `lastMessagePreview` + thread messages = `enc:v1:`; daily answers `encryptedPayload` = `sealed:v1:`. Metadata (dates, types, commitmentHash, ids) plaintext as expected. Chat media bytes = Tink ciphertext (verified prior round + unchanged code path). **No plaintext content leak.** +- **D2 rules:** no catch-all `match /{document=**}`, no blanket `if true`; **`hasPremium` server-only** (client create/update blocked, rules L172/174); entitlements `write:false`; conversations/messages/typing/reactions + entitlement partner-read scoped to members. +- **D4 key exchange:** pairing uses a **wrapped couple key** (`wrappedCoupleKey` + `kdfSalt`/`kdfParams` + `encryptedRecoveryPhrase`); invite code is the KDF seed, never stored raw; strict E2EE (invites without a wrapped key rejected) — confirmed in `acceptInviteCallable`. +- **D5 App Check/secrets:** App Check enforced (`SecurityModule`, `PlayIntegrityChecker`, `FirebaseInitializer`); both service-account JSONs gitignored **and untracked**; `allowBackup=false`. +- **D6 leak vectors:** analytics events carry only metadata (no message/answer content); `allowBackup=false`. + +_Follow-ups (not blockers): live **non-member negative test** (D3) needs a fresh 3rd account (rule logic verified member-scoped); a fresh Storage-bytes spot-check of chat media._ ## Pass E — Notifications -_not started_ +- **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. +- **Foundations** (prior round, code present): FCM token registration on sign-in, POST_NOTIFICATIONS, channels. + +| ID | Area | Severity | Description | Status | +|---|---|---|---|---| +| E-001 | Notification routing | **P2** | Type-string mismatch: functions send `daily_question` + `challenge_day_ready`, but client mapped only `daily_question_reminder` + `challenge_waiting` → tapping those did NOT deep-link to Today / Connection Challenges. | **FIXED** `` — 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._ diff --git a/app/src/main/java/app/closer/notifications/PartnerNotificationManager.kt b/app/src/main/java/app/closer/notifications/PartnerNotificationManager.kt index cb6d6161..2105102b 100644 --- a/app/src/main/java/app/closer/notifications/PartnerNotificationManager.kt +++ b/app/src/main/java/app/closer/notifications/PartnerNotificationManager.kt @@ -287,10 +287,12 @@ enum class PartnerNotificationType( // is ready, so this maps to the results-ready copy (not "open yours when ready"). "partner_finished_game" -> GAME_RESULTS_READY "game_results_ready" -> GAME_RESULTS_READY - "challenge_waiting" -> CHALLENGE_WAITING + // Server emits 'challenge_day_ready'; keep the legacy 'challenge_waiting' alias too. + "challenge_day_ready", "challenge_waiting" -> CHALLENGE_WAITING "memory_capsule_unlocked" -> CAPSULE_UNLOCKED "gentle_reminder" -> GENTLE_REMINDER - "daily_question_reminder" -> DAILY_QUESTION_REMINDER + // Server emits both 'daily_question' (assignment) and 'daily_question_reminder' — both open Today. + "daily_question", "daily_question_reminder" -> DAILY_QUESTION_REMINDER "chat_message" -> CHAT_MESSAGE "outcome_reminder" -> OUTCOME_REMINDER "partner_joined" -> PARTNER_JOINED