Commit Graph

354 Commits

Author SHA1 Message Date
null 21b078a180 fix(notif): set FCM channel on remaining reminder senders (E-OBS)
Add android.notification.channelId to the 4 senders not covered by the earlier batch:
scheduledOutcomesReminder + dailyQuestionReminder + reengagement -> 'reminders';
sendGentleReminderCallable -> 'partner_activity'. Completes E-OBS so backgrounded pushes land on
their proper channels (importance/sound + per-category toggle) instead of fcm_fallback. tsc green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:39:59 -05:00
null aaab768cb0 fix(notif): deep-link results-ready pushes to per-session results/replay screen (E-003) 2026-06-25 12:35:49 -05:00
null 3de4178fd3 qa(plan): add varied gameplay styles, exhaustive nav fuzzing, Pass G account-creation/fake-account
- Pass B: vary style of play (lengths/moods/answer types, result patterns, turn orders, exit/resume
  styles, edge inputs) to hit different code paths.
- Pass C: 'take every avenue' exhaustive nav fuzzing — tap every element, every order, rapid/repeated
  input, interrupt mid-nav, hunt dead-ends/traps.
- Pass G (new): account creation happy path + validation + duplicate/conflict + fake/malicious
  creation attempts (live D3 non-member denial, invite-code abuse, App Check, self-premium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null ee19ef3f59 qa(r4): fix phase + re-QA complete — E-003/B-004/A-OBS fixed; 0 open P0-P2
Round 4: the 2 new P2 (E-003 game-push deep-link, B-004 WaitingForPartner Join escape) + 1 new P3
(A-OBS paywall copy) from R3 are fixed, verified live, committed. Regression smoke clean (launch,
This-or-That end-to-end + B-001 auto-close, chat enc:v1 at rest, C-NAV-001 back->launcher).
Only E-OBS (P3) open — bg push channel, needs server change + user-gated functions deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null d99fa6c6ea fix(paywall): show friendly plan-load error, not raw SDK message (A-OBS)
The paywall ErrorState rendered uiState.error verbatim, surfacing developer-facing billing/RC SDK
text ('There was a credentials issue. Check the underlying error for more details.') to users.
Now always shows friendly copy. Verified live: free user -> paywall -> 'We couldn't load
subscription options right now. Check your connection and tap to try again.' (no raw error).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null f1549c642c fix(games): add 'Join the game' escape to WaitingForPartner screen (B-004)
The generic WaitingForPartner screen only exited when the session became null, so a partner who
landed there for an async game they could actually play (every current game is async — both play
on their own device) was stuck waiting forever, recoverable only via Back to Games. Now the screen
resolves the active session's game route and offers a primary 'Join the game' action that drops the
user into the game (which auto-joins the session). Deterministic repro: QA starts How Well, Sam
opens a different game -> one-game lock routes Sam to WaitingForPartner -> 'Join the game' -> How
Well guess intro. Verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null 1b9d8cf8dc fix(notif): game pushes deep-link into the waiting game, not the Play hub (E-003)
partner_started_game / partner_completed_part now route to the specific game route
(gameRouteForType(payload.gameType)) so the game screen auto-joins the couple's active
session — fulfilling the 'Tap to join!' promise. Server already sends game_type in the FCM
data; client now parses it (AppMessagingService + MainActivity) into PartnerNotificationPayload
and routeFor maps it. game_results_ready stays on the hub pending a server change to also send
game_session_id (completed sessions aren't returned by getActiveSession, so the plain game route
would show setup). Verified live: backgrounded partner tapped the start-game push -> opened This
or That at 1/5 (joined), not the hub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null a2b38485b1 docs(seed): align type names with repo schema — single_choice, this_or_that, answer config 2026-06-25 12:35:49 -05:00
null d1026c7312 docs(seed): split question guide into three documents — content guide, schema, rewrite plan 2026-06-25 12:35:49 -05:00
null 0c8586fa9e qa(r3): Round 3 full re-QA (A-F) COMPLETE — 12 fixes hold; 5 new issues logged
Pass F: offline (cache renders, no crash), rotation (state preserved), process-death (~6 clean
restarts), concurrency (simultaneous game play synced) all OK.
RESULT: all 12 prior fixes re-verified holding live; no P0/P1, no security/encryption findings.
New: 2xP2 (B-004 intermittent guesser-stuck; E-003 game notif -> Play hub not the game),
2xP3 (A-OBS paywall copy [env]; E-OBS bg fallback channel); C-OBS resolved (debug menu is gated).
Not yet flawless (2 open P2) -> fix phase + Round 4 re-QA. Baseline restored (both free, 0 active).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null 682a9d8ea7 qa(r3): Pass E live notif tests — chat_message full chain OK; log E-003 + E-OBS
chat_message: deliver (bg) + content-free + tap deep-links to exact conversation with content loaded.
partner_started_game: deliver + content-free OK; tap -> generic Play hub (not the game).
E-003 (P2): game pushes routeFor->AppRoute.PLAY (not the specific game/results) despite 'Tap to join!';
  extend B-002's game-route resolver to notifications.
E-OBS (P3): backgrounded pushes use fcm_fallback_notification_channel, bypassing code-defined
  CHANNEL_GAMES/chat channels (server sends notification not data-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null fa6d80602a docs(brand): add asset-system.md, cross-link from visual-identity.md 2026-06-25 12:35:49 -05:00
null afd81e8120 qa(r3): Pass C visual sweep + Pass D security re-audit clean
Pass C: ~14 screen-types in dark (Home, Play, all 7 games, paywall, Settings+Subscription+Appearance,
Today, Messages inbox, Conversation) render clean, no FATAL, no new contrast issues, 0 enc:v1 leaked
to UI. C-DS-001 holds. C-OBS: debug menu entries (verify BuildConfig.DEBUG-gated). Remaining standard
list/detail screens deferred (nav-drift).
Pass D: deployed rules re-audited (B-001 + D-001 fixes present, hasPremium/entitlements server-only,
ciphertext enforced, no catch-all); at-rest chat text + preview = enc:v1. D3 live deferred (3rd acct).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null c7140b1e10 qa(r3): Pass A + Pass B fully re-verified live
Pass A: neither->paywall, partner->couple-shared unlock, self->unlock, A-003 badges both
directions. New A-OBS (P3): paywall plan-load shows raw 'credentials issue' (env: no RC sandbox).
Pass B: all 7 game areas played; B-001 holds across 4 async types (auto-complete); B-002 clean
case works; B-003 + C-DS-001 hold; Date Match deck + Wheel + How Well + Desire Sync + ToT all PASS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null ddecfabcd9 qa(r3): How Well verified (5/5, B-001 holds); log B-004 intermittent guesser-stuck on WaitingForPartner
How Well two-device playthrough PASS x2 (subject+guesser, reveal correct, session auto-closes).
B-002 clean case works (Play now -> guess INTRO when subject done).
NEW B-004 (P2, intermittent): guesser can land on generic WaitingForPartnerScreen for How Well
during a rapid game-to-game transition and get stuck (screen only exits on session end).
Not reproduced in clean case; escalate to P1 if deterministic. Report-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null 185d27b921 qa(r3): re-QA round 3 — nav + premium + Desire Sync/This-or-That fixes re-verified live
Round 3 full re-QA in progress. Re-verified LIVE on both emulators (build ce7fc2e):
C-NAV-001 (back->launcher), C-CC-001 (single header), back-stack clean,
A-001 couple-shared (Sam free unlocks Desire Sync+Memory Lane), A-003 (0 badges),
D-001 (capsules load, no PERMISSION_DENIED), B-001 (2 consecutive games auto-close),
B-002 (Home Play-now resumes exact game), B-003 (coherent counts), C-DS-001 (dark contrast).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:35:49 -05:00
null 6eeb86320b qa(fix-phase): all P0-P3 FIXED (P3x4: A-003/B-003/E-002/F-OBS); severity board all clear 2026-06-25 10:14:18 -05:00
null 8e08823e83 fix(memorylane): propagate snapshot-listener errors so the screen doesn't hang (F-OBS P3)
observeCapsules swallowed listener errors (return@), so on PERMISSION_DENIED the flow never
emitted or closed and Memory Lane hung on its loading heart forever. Now close(err)s the
flow -> the ViewModel's existing onFailure -> ERROR state with Retry. (Root cause that
masked D-001.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:14:17 -05:00
null 46010508a9 fix(notifications): route partner_left/partner_deleted_account to Home (E-002 P3)
Added PARTNER_UNPAIRED type for the two real 'you are unpaired' pushes -> Home, where the
now-solo user gets the Invite CTA (matches body 'Tap to create a new invite'). Documented
that invite_created (server audit log, read:true) and spki (a crypto key-format string in
the RevenueCat webhook, not a notification) are false positives needing no routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:14:16 -05:00
null f63188d97a fix(desiresync): clearer privacy counts on reveal (B-003 P3)
Per-person tiles showed '$total private' (e.g. '5 private'), contradicting the caption's
'N kept private' (e.g. '2 kept private'). Tiles now read just 'Private' (your individual
answers always stay private); the caption keeps the real shared/kept breakdown. Verified:
'You: Private / Sam: Private' + 'N shared, M kept private', no contradiction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:14:15 -05:00
null f8ae15a41b fix(play): hide Premium badge on Desire Sync/Memory Lane cards when couple has premium (A-003 P3)
Threaded showPremiumBadge=!hasPremium into DesireSyncCard/MemoryLaneCard and gated the
lock badge behind it. The feature was already accessible (A-001) — only the static badge
was misleading. Verified: with couple premium the Play hub shows no Premium badge on them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:14:14 -05:00
null 0c67c505be qa(fix-phase): mark B-002/C-CC-001/C-DS-001 (P2) FIXED+verified; P0-P2 all clear, P3x4 remain 2026-06-25 09:58:27 -05:00
null a94f44d3ec fix(home): 'Play now' resumes the waiting game, not the generic hub (B-002 P2)
Resolve the active session's gameType to its resume route (gameRouteFor) and carry it on
HomeAction.gameRoute / HomeUiState.waitingGameRoute; HomeActionTarget.Game now navigates
there (fallback Play hub). Each game screen auto-joins the couple's active session on open,
so the Home 'Play now' CTA drops the user straight into the actual waiting game.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:58:26 -05:00
null 1fe4dea9c1 fix(desiresync): theme-aware reveal text for dark mode (C-DS-001 P2)
DesireMatchCard used a hardcoded dark plum (Color(0xFF3D1F2E)) for the shared-desire
text -> readable on the light card in light mode, but dim/low-contrast on the dark-tinted
card in dark mode. Switched to MaterialTheme.colorScheme.onSurface so it adapts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:58:25 -05:00
null edc00d2a5f fix(nav): drop Connection Challenges from shellBackRoutes (C-CC-001 P2)
The screen renders its own header (title + 'Pick a series…' subtitle + back) for both
the pick and active views, so the nav-scaffold app bar drew a SECOND identical header +
back arrow on top. Removed it from shellBackRoutes -> single header, single back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:58:24 -05:00
null 1108a57c4a qa(fix-phase): mark B-001 + C-NAV-001 (both P1) FIXED+verified; severity P1 0 open/4 fixed 2026-06-25 09:37:38 -05:00
null a82c43ad90 fix(rules): allow completedByUsers on session update so finished games close (B-001 P1)
The sessions allow-update rule required affectedKeys().hasOnly(['status','completedAt']),
but the async-game completion path (markUserComplete) always writes completedByUsers, so
every 'I reached results' write was denied and the session stayed active forever -> the
couple was locked out of starting any new game (only the destructive 'End their game'
worked, since abandonSession only diffs status/completedAt). Rule now permits
['status','completedAt','completedByUsers'], lets any couple member record completion
progress, keeps startedByUserId immutable and status monotonic (active->completed).
Deployed + verified live: both finish a game -> session auto-completes (completedByUsers
=[both]) -> next game starts immediately (no 'Waiting for partner' block).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:37:37 -05:00
null ebd3b2ed1f fix(nav): clear onboarding/auth back stack on entry->Home (C-NAV-001 P1)
Navigating to Home from any entry route (onboarding/profile/pair/login/signup/forgot)
now resets the stack (popUpTo(0) inclusive) so Home is the back-stack root. Previously
the graph start (ONBOARDING) lingered under Home, so system Back from Home walked
backward into the onboarding carousel -> welcome/login, making a signed-in user look
logged out. Verified: Back from Home now exits the app to the launcher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:37:36 -05:00
null 4e49b92be2 qa(round2-B2): Date Match PASS (3 mutual matches + live It's-a-match push + Matches view); PASS B COMPLETE all 7 games; Sam reverted to free 2026-06-24 23:31:18 -05:00
null 01feee8321 qa(round2-B2): Spin the Wheel PASS (free partner enters; spin->Date Night->both answer 10->reveal matches both, no crash) 2026-06-24 23:22:25 -05:00
null f79b38c07c qa(round2-B2): Memory Lane PASS (capsule create+seal, encrypted at rest enc:v1:, cross-device sealed view, no crash); D1 capsule ciphertext verified live 2026-06-24 23:15:23 -05:00
null e76a84f5da qa(round2-B2): C-NAV-001 P1 CONFIRMED — back from Home resurfaces onboarding/auth (back stack not popped after login); clean cold-start repro 2026-06-24 23:07:28 -05:00
null 1303597d4a qa(round2-B2): Connection Challenges PASS (day-cycle synced both, streak ok, no crash); C-CC-001 P2 (duplicate header + double back) 2026-06-24 23:03:15 -05:00
null c71d858283 qa(round2-B2): Desire Sync PASS (free partner enters via A-001; 3 mutual desires revealed correctly, mismatches hidden, results match); B-003 P3 (confusing counts), C-DS-001 P2 (dark contrast) 2026-06-24 22:57:50 -05:00
null 3c9037d8e4 qa(round2-B2): How Well PASS (user-nav, predicted 4/5 w/ deliberate miss + scale, results match both, no crash); B-001 re-corroborated (Back to Play leaves session active) 2026-06-24 22:50:59 -05:00
null f8dc8119cb qa(round2-B2): This or That PASS (user-nav, 5/5, results match); B-001 escalated P3->P1 (Back to Play doesn't close finished session -> blocks next game); B-002 P2 (Play now lands on hub) 2026-06-24 22:42:18 -05:00
null 8fa922fb70 qa(round2): RESTART Pass B from game #1 (play-as-user) — coverage reset, build reinstalled both devices 2026-06-24 22:30:09 -05:00
null 21504098c2 qa(plan): Pass B — play-as-the-user mindset; report-first-then-workaround on any broken flow 2026-06-24 22:27:40 -05:00
null f9c6e42d92 qa(round2): Pass B — How Well full two-device playthrough PASS (5/5 predict, results match both) 2026-06-24 22:22:48 -05:00
null 60a6ce1dbf docs(qa): continue across auto-compaction without the user (file-state is authoritative)
Don't hand back when context fills: harness auto-summarizes + you continue from the committed
run-state + coverage. Can't self-invoke /compact and don't need to. Commit before interruptible
work; session-start ritual recovers stuck sessions. Only true blockers (denied gated action /
macOS) stop the run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 22:13:48 -05:00
null 693ecd28ef qa(round2): Pass B — This or That full two-device playthrough PASS (5/5, results match both, no crash) 2026-06-24 22:10:15 -05:00
null e7073fc5f8 qa(round2): R2-1 done — A-001 re-verified all features + free-gate; D-001 fixed. Pass B next 2026-06-24 22:04:54 -05:00
null b05a72605e fix(rules): add capsules + challenges member rules (D-001 P1) — Memory Lane/Challenges were broken
couples/{id}/capsules and /challenges had NO rules -> default-deny -> Memory Lane hung on
loader, Connection Challenges couldn't load (live PERMISSION_DENIED). Added member-read +
ciphertext-enforcing capsules rule (title/content/promptUsed = enc:v1:) and a challenges
rule (catalog-referenced progress). Deployed + verified live: both features load, 0 perm
errors. Found during Round-2 re-verify of A-001 (Memory Lane couple-shared also confirmed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 22:02:40 -05:00
null efe0ddbf29 qa: record standing authorization (deploy firestore rules + admin access)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:51:45 -05:00
null bbd7ef0806 docs(qa): define per-pass chunk granularity (sub-batch to one context window)
Round-1 calibration: A & D fit as single batches; B/C/E overflowed and got deferred.
Add a batch-sizing table: B=1 game/chunk, C=1 screen-group/chunk, D=~4 sub-areas,
E=3-5 types/chunk, F=1 dimension/chunk. Chunk = largest unit that finishes+commits in one
window; commit + run-state update per chunk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:47:04 -05:00
null 84dd5f1152 docs(qa): senior-QA review additions — Pass F, env/matrix, migration, iOS-native dims
- Pass F (cross-cutting): concurrency/realtime races, lifecycle/process-death, network
  resilience, idempotency/rapid-input, time-dependent (daily rollover/streaks/capsules),
  account/couple lifecycle, crash reporting.
- Methodology: prefer Firebase emulator/staging over prod; device/OS matrix; automate the
  smoke; test-data hygiene.
- Pass D7: encryptionVersion 0->1->2 migration. Reporting/re-QA now A-F.
- iOS: iOS-native QA dims (Dynamic Type/VoiceOver/safe-area/edge-swipe-back/sizes),
  real-device/sandbox needs (App Attest/APNs/StoreKit), crypto golden vectors.
- Logged D-OBS: PERMISSION_DENIED on outcomes/challenges/capsules to investigate in Round 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:44:02 -05:00
null e907453f3f docs(plan): add Part 2 (build iOS to parity) + Part 3 (iOS QA) — ClaudeiOSPlan.md
Program now Part1 Android QA -> Part2 iOS build -> Part3 iOS QA + cross-platform.
iOS = native SwiftUI (iphone/ scaffold, audit stale at v0.2.0). Decisions: full
Tink-compatible E2EE (Android<->iOS decrypt), working-parity build (no App Store).
Hard constraint: iOS build/run/QA needs macOS (not this Linux box) — Linux = author
Swift + refresh audit only; compile/run/QA deferred to a Mac.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:37:26 -05:00
null 16ba464752 docs(qa): autonomous run-to-completion mode — never stop; unblock by fixing; finish to flawless
Adds Execution-mode directive: run all passes -> fixes -> re-QA continuously to a flawless
round without checking in; fix anything that BLOCKS progress inline (stale data, crash, build
break, broken nav) to keep going; context limits = checkpoint not stop. Only a denied gated
action (prod deploy / admin write / entitlement toggle) may be surfaced, after all other work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:28:45 -05:00
null 99f0ae0c49 docs(qa): Pass C also checks navigation from every entry point + back-stack/double-back
UI review now verifies each screen opens correctly from ALL its entry points (inbox/Discuss/
notification, Play/notification, paywall from each gate) and that back (system + in-app)
returns correctly with no dead-ends, exit-app surprises, or two-back/duplicate-stack issues.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:26:23 -05:00
null f121eab67f docs(qa): require a full one-time playthrough of each game (not just launch)
Pass B now mandates playing each game end-to-end on both devices (start -> every step ->
finish/reveal/results); launch-only = partial. Reflected in playbook, report run-state,
and coverage (full playthroughs owed in Round 2).

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