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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
- 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
- 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
- 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
- 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
- PartnerHomeViewModel: loads partner photoUrl alongside name
- PartnerIdentityCard: shows AsyncImage when photoUrl available, fallback to initial letter
- 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
- 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
- Firestore rules: partner can read user doc (name/photo), sender can read own release key
- QuestionThread: status stored UPPERCASE to match rules (lowercase broke discussion)
- GameSessionManager: propagate auto-generated session id (empty id crashed game start)
- AnswerReveal: decrypt partner's selectedOptionTexts from option IDs (showed raw ids)
- FirestoreAnswerDataSource: tolerate Timestamp/Date in updatedAt (serverTimestamp crash)
- FirestoreReleaseKeyDataSource: tolerate PERMISSION_DENIED on existence check (sender can't read)
- QuestionThreadRepository: runCatching status update (legacy lowercase status blocked submit)
- PartnerNotificationManager: suppress notification for active thread, deep link to thread
- ActiveThreadMonitor: new class tracks which thread user is reading (suppresses own notifs)
- DesireSync/HowWell/ThisOrThat: re-open guard skips INTRO if already answered; blank sessionId guard
- AppNavigation: deep link pattern for chat notification