Commit Graph

321 Commits

Author SHA1 Message Date
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
null 6ca65ce7e9 qa(round1): close out — all P0-P2 fixed; P3 + deferred coverage tracked for round 2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:08:22 -05:00
null ce12abb1a6 fix(notifications): route daily_question + challenge_day_ready taps (E-001 P2)
Client fromRemoteType mapped only daily_question_reminder/challenge_waiting; functions
send daily_question/challenge_day_ready too. Tapping those now deep-links to Today /
Connection Challenges. Also records Pass C (main screens clean) + Pass D (security clean).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:07:32 -05:00
null 58208fd443 qa(round1): A-001 fixed+verified (couple-shared premium); A-003 P3 logged (static badge)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:53:39 -05:00
null e8892a9669 fix(premium): couple-shared premium everywhere (A-001)
Route all feature gates (Play hub, Desire Sync, Memory Lane, Connection Challenges,
Question Packs, wheel category/spin/history) through CouplePremiumChecker instead of
per-user EntitlementChecker. CouplePremiumChecker now exposes isPremium()/hasPremium()
that resolve the partner internally (self OR partner premium). Verified live: Sam premium →
QA enters Desire Sync; both free → QA → paywall.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:52:22 -05:00
null c54ceb16c3 qa(round1): Pass B games launch sweep — no crashes; stale session recovery verified (B-001 P3)
This or That / How Well / Connection Challenges / Spin the Wheel launch clean (no FATAL).
A stale active wheel session blocked all games; in-app 'End their game' recovery works.
Full two-device lifecycle partial this round. Report-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:04:54 -05:00
null 452aaf787a qa(round1): Pass A complete — couple-shared premium gap (A-001, P1)
Only chat uses CouplePremiumChecker; all other gates are per-user → a free partner of a
premium user stays locked. Confirmed live (Sam premium, QA still locked on Desire Sync +
Memory Lane). Report-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:59:04 -05:00
null 64f0a7e6c8 docs(qa): save full-app QA playbook (5 passes: premium, games, visual, security, notifications)
Reusable QA → fix → re-QA plan. Report-only passes with severity labels, then fix
one-at-a-time by severity, then re-QA until flawless. State/resume lives in ClaudeReport.md
+ ClaudeQACoverage.md. Not yet executed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:43:19 -05:00
null 6580299f05 feat(packs): route pack Discuss to the unified conversation
Replace the orphaned in-thread QuestionDiscussionThread (which wrote to question_threads
and sent no notifications) with a 'Discuss this together' button that opens the conversation
(q_<questionId>), same as the daily flow — so pack discussions are notified and appear in
the Messages inbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:55:40 -05:00
null 7a9ff31ae6 feat(chat): couple-shared premium gating for sending media + reactions
CouplePremiumChecker ORs self.isPremium with a live read of the partner's entitlement
doc (reactive). Composer photo/camera/voice buttons + keyboard GIF/sticker insert + the
reaction action gate on canSendMedia: locked buttons show a lock badge and route to the
existing PaywallScreen (with a chat_media paywall analytics event). Text/viewing/receiving
stay free. Rules: paired partner may read the entitlement doc. Verification pending deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:52:50 -05:00
null f29d4699ca feat(chat): typing indicator
Debounced typing flag (typing:{uid:ts}) on the conversation doc, cleared on stop/send/
leave; partner sees 'typing…' with a ~6s TTL safety net (ticker-driven auto-hide). Rules
allow members to write the typing field. Live verification pending the Phase B deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:47:39 -05:00
null 5b9596e042 feat(chat): message reactions + delete (unsend) via long-press menu
Long-press a message for a reaction bar (heart/laugh/thumb/wow/sad/fire), Copy (text),
and Delete (author). Reactions stored as a reactions:{uid:emoji} map; delete sets a
'deleted' tombstone ('This message was deleted') and updates the inbox preview if it was
last. Rules: any member may change only reactions; author may set only deleted. Live
verification pending the Phase B rules deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:44:13 -05:00
null 7f1b938aa5 feat(chat): read receipts (Seen) via the conversation reads map
Observe the partner's last-read timestamp on the conversation doc; show 'Seen · time'
under the last own message once the partner has read past it. No rules change (reuses reads).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:38:42 -05:00
null 3aa182a466 feat(chat): voice playback progress bar + 2-min recording cap
Voice bubbles show a determinate progress bar + elapsed time that advance during
playback (polling MediaPlayer position); recording auto-stops and sends at 2 minutes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:35:32 -05:00
null cfea8f0d41 feat(chat): send/upload feedback + message pagination
- Pagination: observeMessages(limit) uses limitToLast(N); a single live window grows
  by a page when scrolled to the top (keeps just-sent messages in view, no merge needed).
- Send feedback: 'Sending photo/voice…' chip above the composer with retry + dismiss on
  failure, plus a snackbar; media uploads fail fast when offline (connectivity pre-check +
  30s Storage retry cap) instead of a stuck spinner.
- Auto-scroll to bottom only on new messages when near the bottom (never on load-older).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:32:01 -05:00
null 3544b7a84a feat(chat): message timestamps + day separators
Show a muted clock time under the last bubble of each sender-run (side-aligned),
and a centered Today/Yesterday/date pill between messages from different days.
Falls back to now for a just-sent message whose server timestamp is unresolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:14:05 -05:00
null 8a68ae3107 feat(chat): compress gallery/camera photos before encryption (EXIF-safe, skip GIFs)
Downscale to 1600px + JPEG 80% on the gallery/camera send path only; keyboard
GIFs/stickers/Bitmoji stay untouched to preserve animation/transparency. Applies
EXIF rotation and falls back to the original bytes on any failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:10:24 -05:00
null 11a4c7deda feat(chat): RichContentTextField for keyboard GIF/sticker/Bitmoji insertion, tap-to-enlarge images
- RichContentTextField: AppCompatEditText wrapper with OnReceiveContentListener for image/* MIME types, enables Gboard GIF/sticker/Bitmoji tabs
- ChatComponents: replace OutlinedTextField with RichContentTextField, remove ExperimentalFoundationApi opt-in, add tap-to-enlarge full-screen image viewer with aspect-ratio sizing
2026-06-24 17:37:13 -05:00
null dbf7ae662b chore(config): coil-gif dependency, RECORD_AUDIO permission, firestore rules allow voice type
- build.gradle.kts: add coil-gif 2.7.0 for animated GIF/WebP support
- AndroidManifest: RECORD_AUDIO permission + microphone feature (optional)
- firestore.rules: messages create allows 'voice' type alongside 'image', accepts durationMs field
2026-06-24 16:34:53 -05:00
null 3ad725ca8a feat(voice+media): voice recording/playback UI, GIF/sticker support, keyboard content receiver
- ChatComponents: voice recording bar (mic → record → send/cancel), voice playback with MediaPlayer, GIF/WebP via coil-gif + ImageDecoderDecoder, keyboard content receiver for stickers/Bitmoji
- ConversationViewModel: sendVoice wired through
- ConversationScreen: onSendVoice passed to ChatComposer
2026-06-24 16:34:48 -05:00
null c20745e82a feat(voice): data layer — voice message model, sendVoiceMessage, durationMs field
- QuestionMessage: add durationMs, isVoice, type supports 'voice'
- ConversationRepository: sendVoiceMessage(audioBytes, durationMs)
- FirestoreConversationDataSource: sendVoiceMessage encrypts + uploads audio, decrypts durationMs
- ConversationRepositoryImpl: delegates sendVoiceMessage
2026-06-24 16:34:38 -05:00
null 29beff1702 build(functions): rebuild dist from source — revenueCatWebhook ack-after-process, acceptInvite strict E2EE, onMessageWritten conversations path, onGameSessionUpdate both-partner notify 2026-06-24 16:15:30 -05:00
null 060ef69ca5 feat(rules+trigger): conversations Firestore rules, onMessageWritten listens on conversations path, gitignore
- firestore.rules: conversations doc read/write rules with ciphertext validation, messages subcollection create rules (image or ciphertext text)
- onMessageWritten: trigger path changed from question_threads to conversations, passes conversation_id in FCM data, removed questionId resolution (no longer needed)
- .gitignore: deduplicate ClaudeReport.md entry
2026-06-24 16:14:18 -05:00
null e4cdbac7b1 refactor(notifications): questionId → conversationId across bubble, deep link routing, FCM handling
- ActiveThreadMonitor: activeQuestionId → activeConversationId, enter/leave use conversationId
- MessageBubbleController: IncomingMessageBubble uses conversationId, show/dismissFor updated
- PartnerNotificationManager: CHAT_MESSAGE routes to conversation, payload includes conversationId
- MessageBubbleOverlay: onOpen passes conversationId, pointerInput keys on conversationId
- AppMessagingService: chat_message type reads conversation_id, bubble show uses conversationId
- MainActivity: deepLinkRouteFromIntent reads conversation_id from FCM extras
2026-06-24 16:14:07 -05:00
null c85e55a790 feat(nav): Messages replaces Question Packs in bottom bar, conversation route with deep link, Play Hub hosts Packs
- AppNavigation: MESSAGES + CONVERSATION composable routes, bottom bar swaps Packs for Messages
- AppRoute: MESSAGES + CONVERSATION route constants, conversation() helper, bottom nav list updated
- PlayHubScreen: Question Packs card added (moved from bottom bar)
- AnswerRevealScreen: discuss button navigates to conversation route
- DailyQuestionScreen: discuss button navigates to conversation route
2026-06-24 16:13:58 -05:00
null 33baf220e4 feat(conversations): UI — Messages inbox, conversation screen, view models, chat components
- MessagesInboxScreen: list of conversations with last-message preview, tap to open
- MessagesInboxViewModel: observes conversations from Firestore
- ConversationScreen: E2E-encrypted messaging with send/receive, image support
- ConversationViewModel: send message, observe messages, active conversation tracking
- ChatComponents: reusable message bubble, input bar, encrypted image rendering
2026-06-24 16:13:52 -05:00
null db5b8a5f8a feat(conversations): data layer — domain model, Firestore data source, repository, DI bindings
- Conversation model with id, type (couple_chat/question_discussion), questionId, lastMessage fields
- FirestoreConversationDataSource: create, sendMessage, observeConversations, observeMessages
- ConversationRepositoryImpl: wraps data source
- RepositoryModule: bind ConversationRepository
- FirestoreCollections: CONVERSATIONS subcollection constants
2026-06-24 16:13:45 -05:00
null 4e2c3fdf0d fix: rate limiter bump (20/day, 100/week), firestore rules for image messages, storage rules for chat_media, gitignore ClaudeReport
- NotificationRateLimiter: 20 partner/day, 100/week (was 2/4 — too tight for game activity)
- firestore.rules: messages create allows type=image with mediaUrl or type=text with ciphertext
- storage.rules: chat_media path with 15MB cap
- .gitignore: ClaudeReport.md, docs/img
2026-06-24 15:20:44 -05:00
null 608ddcfc5b feat(bubble): drag-to-dismiss zone, no auto-timeout, dismiss on conversation enter
- MessageBubbleOverlay: drag-to-dismiss target at bottom-center, no 12s auto-timeout (persists until read)
- MessageBubbleController: dismissFor clears bubble when its conversation is opened
- ActiveThreadMonitor: calls dismissFor on enter, clearing the bubble for that thread
2026-06-24 15:20:38 -05:00
null adb61715fe feat(home): partner photoUrl loaded and displayed in identity card
- PartnerHomeViewModel: loads partner photoUrl alongside name
- PartnerIdentityCard: shows AsyncImage when photoUrl available, fallback to initial letter
2026-06-24 15:20:32 -05:00
null a8fbbaa286 feat(notifications): deep link routing from FCM data extras, onMessageWritten includes author name + photo
- MainActivity: deepLinkRouteFromIntent resolves FCM data extras to navigation routes; pendingDeepLink state for onNewIntent
- AppNavigation: LaunchedEffect waits for HOME route before navigating deep link (fixes race with onboarding)
- onMessageWritten: includes author displayName + photoUrl in notification payload
2026-06-24 15:20:24 -05:00
null 06e4d609f2 feat(chat): image picker (gallery + camera), encrypted image rendering, messenger-style avatars on consecutive bubbles
- QuestionDiscussionThread: gallery picker via PickVisualMedia, camera capture via FileProvider, EncryptedChatImage composable decrypts + renders, MessageAvatar with partner photo
- QuestionThreadViewModel: sendImage, loadDecryptedMedia, dailyRevealed skip for already-revealed daily questions, partner photo loading
- QuestionThreadScreen: pass loadDecryptedMedia to discussion thread
- LocalQuestionContent: pass partnerPhotoUrl to discussion thread
- file_paths.xml: cache-path for camera capture
2026-06-24 15:20:18 -05:00
null c9aa5f1e12 feat(chat): encrypted image messages — Storage upload/download, Firestore send/load, domain model with type+mediaUrl 2026-06-24 15:20:11 -05:00
null 609ced4095 feat(notifications): FCM token registration on auth, Android 13 permission request, in-app chat bubble overlay, game results notification copy
- MainActivity: request POST_NOTIFICATIONS on TIRAMISU+, register FCM token when user signs in
- AppMessagingService: foreground chat messages show draggable bubble instead of OS notification
- MessageBubbleController/Overlay: new in-app chat-head that drifts over all screens, tap to open
- PartnerNotificationManager: GAME_RESULTS_READY type with proper copy, partner_finished_game maps to it
- onGameSessionUpdate: notify BOTH partners on completion (not just the non-starter), fix starter name in notification
2026-06-24 11:47:49 -05:00