Commit Graph

416 Commits

Author SHA1 Message Date
null d19f0f8eb8 docs(manual): Batch 4 — update Cloud Functions list, module responsibilities, webhook ack order, and schedule timezones 2026-06-28 11:07:24 -05:00
null 08368b3e01 docs(manual): Batch 3 — correct E2EE and Firestore data model (user fields, couple fields, date plans, preferences, bucket list, secure subdoc) 2026-06-28 11:06:20 -05:00
null a6aa23eee2 docs(manual): Batch 2 — correct auth providers and couples model (no anonymous, no migration field, Google SDK not Credential Manager) 2026-06-28 11:04:15 -05:00
null be710a0c9d docs(manual): Batch 1 — correct repository layout paths (core/feature, QuestionDao, QuestionJsonParser) 2026-06-28 11:02:45 -05:00
null 3a6ae5c69a feat: theme-scan.sh summary counts, BrandIllustration exclusion, QAPlan scanner-first wiring 2026-06-28 10:50:27 -05:00
null 9949200f47 feat: wire theme-scan.sh into QA docs, fix script Tier 2, file 9 C-THEME P2 defects 2026-06-28 10:34:55 -05:00
null 4deed13845 feat: theme-scan.sh + improved Pass C scanner methodology (tier 1A-1G, tier 2, tier 3 roadmap) 2026-06-28 10:28:17 -05:00
null fe3ea7715c feat: add automated theme-mismatch scanner to Pass C methodology (Tier 1-3) 2026-06-28 10:20:41 -05:00
null 37ed7cebec feat: quiet hours notifications, settings UI, game session updates, docs 2026-06-28 10:00:25 -05:00
null c31eea2549 feat(premium): one-time PremiumUnlockOverlay + theme/art fixes (R13)
- New PremiumUnlockOverlay.kt — one-time 'Premium unlocked' celebration for both partners on couple-shared Premium activation. Driven off CouplePremiumChecker (not the push) so it surfaces for both wherever they are. Gated by persisted premiumUnlockCelebrated flag, auto-reset on lapse.
- New illustration_premium_unlock.png asset for the overlay.
- AppNavigation hosts the overlay at root alongside MessageBubbleOverlay.
- SettingsDataStore: new premiumUnlockCelebrated flag + setter.
- ThisOrThatScreen: theme-token fixes for A/B options, mood chips, versus badge, progress, ChoicePromptBackdrop — all read from MaterialTheme.colorScheme. Bumps dark-mode legibility.
- ConversationScreen: bump PendingMediaChip retry/dismiss IconButtons to 48dp touch targets.
- PlayHubScreen / ActivityScreen / HomeScreen / SubscriptionScreen / OnboardingScreen / PairPromptScreen / PaywallScreen / LocalQuestionContent / OutcomeCheckInDialog / ChatComponents: assorted R13 polish.
- firestore.rules (n/a this batch), SettingsRepository, manual: doc + flag wiring.
- Manual: new C-DARK-UI-001 + C-ART-EDGE-002 landmines, Premium-unlock-modal pattern note.
2026-06-27 21:01:16 -05:00
null 4eed0a8115 feat(premium): couple-shared unlock notification + reveal retry + users update allowlist + brand glyphs
- New Cloud Function: onEntitlementChanged (Firestore onWrite on entitlements/premium) — edge-triggered inactive→active, notifies the OTHER partner so couple-shared unlock isn't silent
- New notification type SUBSCRIPTION_CHANGED → routes to SUBSCRIPTION
- AnswerRevealViewModel: re-issue markRevealed if best-effort failed (offline/transient) so partner_opened_answer push eventually fires
- firestore.rules: harden users/{uid} update allowlist (defense-in-depth; no live hole)
- 18 new brand glyph vector drawables (drawable-nodpi/)
- SettingsScreen / PlayHubScreen / WaitingForPartnerScreen: swap Material icons for new brand glyphs
- ClaudeQA docs + Future.md updated
2026-06-27 16:35:41 -05:00
null 9f09ebbc67 chore: R12 working tree — QA docs, brand illustration updates, date-match paywall routing, theme tweaks 2026-06-27 15:34:38 -05:00
null 361eff18e3 docs(manual): review pass 3 — routing route names, notification_queue reality, /questions server-only, conversations/messages data model, premium sharing, entitlement fields, daily reminder skip conditions, iOS tree fix 2026-06-27 15:27:14 -05:00
null c167211323 docs(manual): review pass 2 — iOS pairing actually broken, notification routes corrected, repo tree fixes, schedules updated, reveal-flow read-path gotcha 2026-06-27 15:14:09 -05:00
null 439ae7ce51 docs(manual): review fixes — secure subdoc reveal flow, encryption version accuracy, anchor slug corrections, ToC/how-to updates, helper function list, gitignore case-sensitivity note 2026-06-27 15:00:47 -05:00
null 9eee3951e9 docs(qa): point Report + Coverage at Engineering Reference Manual (landmines section) 2026-06-27 14:51:48 -05:00
null 58d09ac8d3 docs(qa): cross-reference Engineering Reference Manual by Pass with anchor links 2026-06-27 14:51:23 -05:00
null 3924d63c7b docs(manual): R10 architecture updates — game push semantics, foreground banner, deep-link routes, premium gate pattern, landmines section 2026-06-27 14:50:23 -05:00
null 2cd0af65a8 chore: working tree changes — QA docs, app tweaks, Cloud Functions updates 2026-06-27 13:31:09 -05:00
null 9c84c36443 fix(qa): R10 fix phase — 5 P2 bugs fixed (C-HOME-001, C-NAV-002, C-NAV-003, C-PW-001, C-SEC-001) 2026-06-27 10:34:26 -05:00
null 32b5b560a2 docs(qa): update report with R10 results — E-GAME-002 fix, foreground banner, C-SEC-001 2026-06-26 20:04:20 -05:00
null 38fdc6d2cc feat(notif): foreground game-start banner + bold Home waiting hero — join specific game from both 2026-06-26 20:04:11 -05:00
null 6e79cd9704 fix(notif): replace status-diff with idempotent flag-claim for game start/finish pushes (F-RACE-001) 2026-06-26 20:04:05 -05:00
null e6a8deef67 docs(qa): update report with couple-key encryption, onAnswerRevealed, both-answered unlock 2026-06-26 12:41:22 -05:00
null b9b15604ef fix(games): notification deep-link lands in active game — singleTop + server-first read (E-GAME-001) 2026-06-26 12:41:17 -05:00
null e5c9c43317 feat(rules): add read-gated secure subdoc for couple-key encrypted answers (schemaVersion 2) 2026-06-26 12:41:06 -05:00
null f7418df700 feat(notif): add PARTNER_OPENED_ANSWER notification type + deep-link routing 2026-06-26 12:40:54 -05:00
null df32229f3b feat(answers): replace sealed-key exchange with couple-key encryption (schemaVersion 2) — reveal on both-answered, no key handshake 2026-06-26 12:40:49 -05:00
null 47867b5663 feat(notif): onAnswerWritten detects both-answered — send 'unlocked' notification instead of 'go answer' 2026-06-26 12:40:41 -05:00
null 8b4c5ba005 feat(notif): add onAnswerRevealed cloud function — notify partner when answers are opened 2026-06-26 12:40:36 -05:00
null fe104b4a41 brand(art): add 12 dark-theme illustration variants for night mode 2026-06-26 11:27:36 -05:00
null f924af9445 qa(brand): complete both-theme sweep of the art drop — 0 issues, 0 FATAL
In-context dark+light verified (Bucket List, Quiet hours, Security, Delete account);
A1/A3 + empty-only states via the debug gallery both themes. Caught + fixed a stale
build on 5556. Baseline intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:11:42 -05:00
null 768f511870 docs(brand): mark all 11 generated illustrations wired into Android (A1-A12)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:01:40 -05:00
null 63699c09da docs(qa): note the brand art drop landed + Pass C re-verify owed on touched screens
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:01:17 -05:00
null 5868d06421 brand(art): wire Delete account calm-goodbye illustration (A12)
DeleteAccountScreen gains illustration_account_deletion_goodbye centered above the copy —
a soft box releasing hearts (no alarm imagery), making the goodbye respectful and on-brand.
Verified live on dark; 0 FATAL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:59:53 -05:00
null 9b1e946ed8 brand(art): pairing-success hero -> A1 celebration; Security header -> A11 privacy-lock
PairingSuccessScreen replaces the white-keyhole app-icon chip joining the two partner
avatars with the illustration_pairing_success celebration (transparent, tile=false,
keeps the spring + pulse) so the "you're connected" beat shows the mark resolving with a
burst of hearts. SecurityScreen gains the illustration_privacy_recovery scene at the top.
Verified live: Security on dark (warm privacy-lock, not cold vault); A1 confirmed in the
debug gallery (transparent floats cleanly). 0 FATAL. Pairing-success needs a fresh pairing
to see in situ; A1 render proven via gallery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:57:26 -05:00
null 86679752b0 brand(art): wire Connection Challenges header (A3 banner) + Quiet hours (A9)
ConnectionChallenges series-list gains the illustration_connection_challenges_header
banner (16:9, BrandIllustration) under the title. Notification settings Quiet-hours
section gains the illustration_quiet_hours scene centered above the toggle. Verified live:
Quiet hours on dark (night-window scene reads beautifully); A3 banner + A1 (transparent,
tile=false) + A2 confirmed in the debug gallery — all crisp + on-brand. 0 FATAL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:53:28 -05:00
null fb4620559b brand(art): wire Date Match A5 (empty + it's-a-match) + Memory Lane A4; add all new art to debug gallery
DateMatches empty -> illustration_date_match_empty; the "It is a match!" modal replaces
the heart-icon circle with illustration_date_match_success (celebration). Memory Lane
empty replaces the 📦 emoji with illustration_memory_lane_capsule. ArtPreviewScreen
(debug) now shows all 12 new illustrations via BrandIllustration so they're verifiable on
both themes without needing empty/match data. Verified live (gallery, dark): A10/A11/A12
tiles render crisp + on-brand; 0 FATAL. (Empty/match states need data not present on the
baseline couple; render path proven via the shared tile.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:48:13 -05:00
null 5d74858679 brand(art): wire Answer History (A2) + Past Games (A10) empty illustrations
AnswerHistory primary empty swapped from the generic illustration_couple_history to the
purpose-made illustration_answer_history_empty (A2). WheelHistory (the "Past Games"
history screen) empty gains illustration_past_games_empty (A10). Both via the shared
EmptyState (rounded-tile, both-theme verified in Run 2). Empty states need empty data so
not reachable live on the baseline couple; render path proven via the shared component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:40:08 -05:00
null 4aec224f0d brand(art): wire Messages-empty (A8) + Bucket List-empty (A6); add BrandIllustration helper
EmptyState already supports illustrationResId (rounded-tile clip), so Bucket List just
passes illustration_bucket_list_empty. Messages inbox gained a proper empty state
("Your private conversation starts here") with illustration_messages_empty. Added
BrandIllustration() helper (theme-safe rounded tile / tile=false for transparent art)
for the upcoming header/hero placements. Verified live both themes: rounded illustration
tile reads cleanly on dark (card) and light (white card on blush); 0 FATAL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:36:53 -05:00
null 077a408785 brand(art): add 12 generated illustrations to drawable-nodpi; gitignore brand source art
Phase 0 of wiring the generated brand art (full-res per request). Adds A1 pairing,
A2 answer-history, A3 challenges header, A4 memory-lane capsule, A5 date-match
empty+success, A6 bucket-list, A8 messages, A9 quiet-hours, A10 past-games,
A11 privacy-recovery, A12 account-deletion to res/drawable-nodpi. Source working art
(docs/brand/{generated-art,sources,exports}) gitignored — only app copies committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:29:42 -05:00
null 5ba5b4a8ec qa(R9): clean confirmation round — deferred Pass C + Pass F network swept, 0 new findings
Confirmed + pruned I-001/I-002 (0 outcomes denials/CCE on fixed build). Deferred Pass C
deep/list screens swept (Answer History, Together/Activity, Bucket List, Date Match,
Date Matches in dark; Home/Privacy&Terms light parity) — clean, no clipping/contrast/
FATAL. Pass F network: airplane-mode -> cache render no crash; reconnect -> recovers.
Baseline restored (0 sessions, 0 outcomes). FLAWLESS bar: 0 open P0-P2 (1 P3 J-OBS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:42:03 -05:00
null 9505defd29 qa(R9): confirm + prune I-001/I-002 (0 outcomes denials/CCE on fixed build)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:33:46 -05:00
null dbf8a6f18e qa(R8): wrap — Pass E new-type status (not implemented), couple-premium-unlock idea to Future.md
Code check: the speculative Pass E types added by the merged playbook (join_game,
partner_joined_game, game_ended, date_plan_update, subscription_entitlement_changed,
...) are not implemented (0 files) -> marked not implemented -> Future.md. Logged the
worthwhile ones to Future.md ## QA (notify free partner on couple premium unlock; join/
end pushes). Round 8 at the flawless bar: 0 open P0-P2 (1 P3 J-OBS); I-001/I-002 fixed+
verified pending Round 9 confirm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:00:31 -05:00
null ab29f6b12f fix(outcomes): restore Your Progress read — scope query to allowed dayKeys + coerce Long scores (I-001, I-002)
I-001: getOutcomes() did a bare collection list .get() on couples/{cid}/outcomes,
which firestore.rules denies (reads allowed only for dayKey in day_0/30/60/90) ->
always PERMISSION_DENIED, swallowed to emptyList(). Now scopes the query with
whereIn(FieldPath.documentId(), OUTCOME_DAY_KEYS) so it satisfies the rule.

I-002 (found while fixing I-001): toOutcomeScores() cast values to Map<String,Int>,
but Firestore returns integer fields as Long on Android -> ClassCastException ->
scores dropped (same shape submitOutcomeCallable writes, so the real path was broken
too). Now coerces (value as? Number)?.toInt().

Verified live: 0 outcomes PERMISSION_DENIED after relaunch; seeded a day_0 baseline
(int64) -> "Your Progress" shows "Baseline recorded" (was "No baseline yet"). Seed
removed, couple baseline restored (0 outcomes, 0 active sessions). Both pending one
re-QA confirmation round before pruning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:58:37 -05:00
null 35d36e6851 qa(R8): Pass J accessibility — font scale 2.0 usable, semantics clean, reduce-motion honored
font_scale 2.0: Home/Paywall/Settings reflow+scroll, no clipped/hidden buttons (only
ellipsis + nav-label wrap). Semantics: 0 unlabeled Icon(), 111 decorative null'd,
meaningful labels on key controls, loader has "Loading…". Reduce-motion honored in 7
surfaces, no hang/crash. Touch targets mostly 48dp. Finding J-OBS (P3): a few
conversation icon-buttons ~42-45dp wide. No P0/P1/P2 a11y blockers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:47:27 -05:00
null f740b1d9a1 qa(R8): Pass I performance — route smoke checklist + I-001 (P1) outcomes read denied
Cold start 1253ms; core tabs 6.3% janky; conversation/Play-hub scroll ~36ms 90th;
no window/Activity/listener leak (meminfo stable over open/close x6); lazy-load (17),
Coil (11), Room caching all in place. Found I-001 (P1): FirestoreOutcomeDataSource
.getOutcomes() does a bare collection list .get() that firestore.rules:658 denies
(reads allowed only for dayKey in day_0/30/60/90) -> always PERMISSION_DENIED, swallowed
to emptyList() -> "Your Progress" never shows recorded outcomes + re-prompts done days.
Fix (fix phase): whereIn(documentId, [day_0..90]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:41:08 -05:00
null 11208c6fb5 qa(R8): re-confirm F-RACE-001 live (race -> 1 session, loser joins same set) + prune
Round 8 chunk 1. Simultaneous mood-tap on both emulators -> exactly 1 active session
(was 2 pre-fix); race-loser hit WaitingForPartner -> "Join the game" -> joined the
winner's session at the same Q1 (shared reveal preserved). Regression smoke clean:
no FATAL, game opens both devices, inbox loads, messages + date_swipes ciphertext at
rest. F-RACE-001 pruned to the archived-ID line per report hygiene; 0 open P0-P3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:32:31 -05:00
null 96987bf29a docs(qa): merge notification-suite playbook, add report hygiene + finding-routing, clean report/coverage
- ClaudeQAPlan.md: fold the deep notification + join-game suite into Pass E (both-client
  matrix, 6 assertions, expanded inventory, game/join-game suites, payload security,
  malformed/stale tests); add Pass B join-paths + Pass C routes-into-games; add missing
  batch rows G/H; add Report-hygiene (one-confirmation-round prune) + coverage-matrix
  hygiene + easy-to-read mandate; add "Where every finding goes" routing table.
- ClaudeReport.md: collapse stacked R1-R7 run-states + fixed tables to current-state
  (0 open P0-P3; F-RACE-001 pending one confirm; older fixed IDs archived).
- ClaudeQACoverage.md: current-status matrix (flip stale fail->A-001 to pass, drop
  contradictory Pass B footer, add status-at-a-glance, surface todo/deferred).
- removed stray seed/questions/Claude_QA_Playbook_Full_App_QA_Notifications_Merged.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:23:30 -05:00
null 23dd6a75e8 fix(games): atomic session start to prevent duplicate sessions on concurrent start (F-RACE-001)
Simultaneous game start by both partners created two divergent active sessions (TOCTOU: a
non-transactional check-then-create in GameSessionManager.startGameWithCouple). Each partner
ended up in a separate session with different questions → no shared reveal.

Fix: QuestionSessionRepository.startSessionAtomically runs a Firestore transaction on a
per-couple pointer doc (couples/{cid}/sessions/_active). It reads the pointer (+ the pointed
session) and either returns AlreadyActive (caller joins the existing session) or atomically
creates the new session and re-points the lock. Concurrent starts contend on the one pointer,
so the loser's transaction retries, sees the now-set pointer, and joins instead of duplicating.
The pointer self-heals (checks the pointed session's status) so no clear-on-finish is needed,
and it carries no status/completedAt so it's invisible to the active/history queries.
GameSessionManager routes all 7 games through it. firestore.rules adds member-write for
sessions/_active (deployed).

Verified live on both emulators: atomic create → 1 session + pointer; sequential 2nd start →
joins (1 session); literal parallel-tap race → 1 session (was 2); 0 FATAL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:43:06 -05:00