Commit Graph

91 Commits

Author SHA1 Message Date
null 3acba138a3 chore(functions): rebuild dist for deployed daily-question + game-copy changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 22:31:02 -05:00
null f2321d3536 fix(functions): server-authoritative, mode-aware, deterministic daily question
Replace pickRandomQuestionId (empty pool → unresolvable q_default_daily fallback)
with pickDailyQuestionId(date): computes today's weekday mode (mirrors the client
DailyModeResolver.DOW_DEFAULTS, FROZEN) and deterministically indexes the free
daily pool by epochDay % poolSize — the SAME question the free client would pick,
now assigned server-side.

Fixes two things at the root:
- The daily 'questions' pool was empty, so every couple got q_default_daily, which
  the client can't resolve → each device fell back to its own local selection
  (the DQ-MISMATCH-001 class; the client half was pinned to the free pool in C1).
- Partners in different time zones computed different device-local weekdays and got
  different questions; a single server-assigned id makes both devices identical.

Firestore 'questions' seeded with the 75 free daily_fun_mc weekday questions (ids
match the client asset DB so getQuestionById resolves them). Pool fetched once and
filtered in memory — no composite index needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:57:22 -05:00
null fda53175a9 feat(games): nudge + per-game copy + score a11y batch C4 from UX review
- Waiting-on-partner screen: 'Send a little nudge 💜' button (shown while the
  partner is still playing, not once it's your turn) reusing the generic
  thinking-of-you callable (10/day, quiet-hours-safe server-side); one-shot
  Toast on result incl. friendly rate-limit copy. Verified live: nudge →
  partner_activity push landed on the partner.
- Per-game banner copy: YOUR_TURN/RESULTS in-app banner now branches on gameType
  ('Your turn — guess their answers' for How Well, 'only mutual yeses ever show'
  for Desire Sync, etc.) instead of one generic line; mirrored in the Cloud
  Function's partner_completed_part push (yourTurnBody). Verified live.
- Accessibility: merged contentDescription on the This or That MatchScoreBadge
  ('You matched on N of M') and the How Well score ring Canvas ('You guessed N
  of M correctly') — both were split/Canvas visuals invisible to TalkBack.

Unit + functions typecheck green; assembleDebug clean. Server copy change needs
a functions deploy (bundled with the C1 finish-guard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:50:39 -05:00
null 4ab79d34c4 fix(ui,games): defect-polish batch C1 from P3/UX review
- Challenges catalog: hide the 🔒 Premium badge once the couple has premium
  (A-003b; matches the Play hub's showPremiumBadge pattern). Verified live
  both directions under an authorized grant/revoke cycle.
- Game banner lifecycle (BANNER-LIFE-001): entering a session's screen now
  consumes any banner pointing at it (GamePromptController.consumeForSession
  wired into ActiveGameSessionMonitor.enter), and activity from a DIFFERENT
  session may replace a stale persistent banner. Verified live: no banner on
  reveal; stale RESULTS banner replaced by a new session's prompt.
- Waiting/join screen: says 'Your turn — {name} already played their part'
  for the non-starter once a first part landed (new partHasFinished mapped
  from partFinishNotifiedAt; completedByUsers only fills at reveal). Session
  observe mapping now also carries completedByUsers/joinedByUsers.
- How Well results: matched-row colors are now a theme-aware container+content
  pair (dark mode was near-invisible: fixed pale-green container under
  onSurfaceVariant text).
- Date Match: top card fully opaque — next card's text no longer bleeds
  through (was alpha 0.96).
- functions: don't send 'X finished — see your results!' for abandoned/quit
  sessions (status flips to completed with empty completedByUsers; a real
  completion always has both uids). Found live when a quit triggered a false
  banner. Needs deploy (bundled with C4).

Unit + functions suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:03:22 -05:00
null e5868bd6b1 fix(games,daily): correctness batch from games review
- Game answer listeners (ToT/HowWell/DesireSync): close(err) instead of
  swallowing snapshot errors, + .catch in each VM's observeReveal surfacing a
  retryable ERROR (GameCopy.SYNC_ERROR + retrySync re-attach) — games no longer
  hang on WAITING forever on listener failure (GAME-HANG-001; matches the
  Wheel/Capsule sources' established pattern).
- Daily question: paired fallback pool is now premium-INDEPENDENT so both
  partners always resolve the same deterministic question; viewer-premium pools
  broke the couple contract when entitlement state differed or flipped mid-day
  (DQ-MISMATCH-001, reproduced live: partners answered different questions).
- Daily reveal: humanize raw option-id fallbacks so slugs never render
  (DQ-SLUG-001, e.g. 'fake_awards_should_be_mandatory').
- assignDailyQuestion.ts: replace hardcoded CST_OFFSET_HOURS=-6 with DST-safe
  Intl America/Chicago helpers (DST-001) + 5 regression tests (CDT/CST labeling,
  6PM reveal instant, spring-forward round-trip).

Verified live 2-device: identical daily question on both partners post-fix;
ToT full loop 5/5 reveal regression clean. Unit 244 + functions 58 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 19:41:41 -05:00
null 89cbd7cb55 refactor(functions): type callable payloads instead of `any`
Change the 5 https.onCall handlers from `data: any` to
`data: Record<string, unknown>`, so payload fields are `unknown` and must go
through the existing validators rather than being implicitly-typed. No behavior
change (every field was already validated); tsc + 53 function tests green.

Left as-is deliberately: `catch (err: unknown)` narrowing (churn, marginal) and
the untyped Tink handles in wrapReleaseKeyCallable (the crypto lib ships no types).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:06:45 -05:00
null eeac3c9383 build(functions): compile dist for outcome aggregation
Tracked dist output rebuilt to match src (aggregateOutcomeStats export +
aggregateOutcomes module), keeping the deployed bundle in sync with source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:10:02 -05:00
null 943507b72a feat(functions): privacy-safe outcome-stat aggregation
aggregateOutcomeStats (scheduled) rolls up per-couple check-in deltas
(couples/{id}/outcomes/day_30|60|90) into the "X% feel closer in N weeks" stat
WITHOUT reading any E2EE content — only the self-reported deltas.

Privacy: counts/percentages only (no couple id or individual scores);
minimum-cohort suppression (N<50 → window omitted, no percentages); EXPORT-ONLY
via the top-level aggregate_stats collection, now explicitly deny-all in
firestore.rules (owner reads via console).

Pure aggregate()/extractCoupleOutcome() unit-tested incl. below-threshold
suppression + a no-PII assertion (6 tests); rules deny client read/write of
aggregate_stats (2 tests). Full functions 53/53, rules 121/121.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:06:57 -05:00
null 95c3bc21fa feat(dates): Firestore-backed date catalog + sponsored ideas
- DateIdea gains sponsored/sponsorName/externalUrl (native, clearly-labeled ads
  — no tracking SDK).
- FirestoreDateIdeaDataSource reads the server-curated date_ideas collection
  (defensive: malformed docs skipped, https-only externalUrl); DateMatchRepo
  serves remote first, falling back to the shipped static seed when empty/failed
  so the feature always has content. Fallback verified live (Date Match renders
  the seed) + unit-tested (remote-present / empty / failure).
- Date card shows a "Sponsored · <name>" pill and a "Learn more" link (opened via
  the system browser, https-only, no click IDs).
- Local admin seeder (functions/scripts/seedDateIdeas.ts + date_ideas_seed.json,
  generated from the Kotlin seed + 1 sponsored example) — idempotent by id, has a
  --dry-run; NOT a deployed endpoint. Verified: 38 docs upserted to the emulator,
  1 sponsored, tsc builds.

Owner: run the seeder against prod (GOOGLE_APPLICATION_CREDENTIALS) to populate
date_ideas; add real sponsor content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:02:22 -05:00
null 896bf26b28 feat: date reflection reveal, UI upgrade plan, seed updates, ime-scan script 2026-07-01 04:12:58 -05:00
null d8408a2c44 chore(functions): rebuild dist with pruneDeadTokens wired into all 19 push sites 2026-06-30 23:45:42 -05:00
null afd1eab299 feat(users): wire pruneDeadTokens into onUserDelete 2026-06-30 23:45:36 -05:00
null 2488e18790 feat(notifications): wire pruneDeadTokens into streakReminder 2026-06-30 23:45:31 -05:00
null 40e4f48131 feat(notifications): wire pruneDeadTokens into dailyQuestionReminder 2026-06-30 23:45:26 -05:00
null 355edd0887 feat(couples): wire pruneDeadTokens into acceptInviteCallable 2026-06-30 23:45:21 -05:00
null f55c49010d feat(notifications): wire pruneDeadTokens into sendThinkingOfYouCallable 2026-06-30 23:36:09 -05:00
null 5306d40c72 feat(notifications): wire pruneDeadTokens into sendGentleReminderCallable 2026-06-30 23:35:59 -05:00
null e35b8151c6 feat(notifications): wire pruneDeadTokens into reengagement 2026-06-30 23:35:49 -05:00
null 35eca7d08d feat(notifications): wire pruneDeadTokens into gameRetention 2026-06-30 23:35:42 -05:00
null 7b5790da43 feat(billing): wire pruneDeadTokens into onEntitlementChanged 2026-06-30 23:35:38 -05:00
null c7181e276f feat(questions): wire pruneDeadTokens into onMessageWritten 2026-06-30 23:35:29 -05:00
null 8b3356e60d feat(questions): wire pruneDeadTokens into onAnswerWritten 2026-06-30 23:35:24 -05:00
null 9ca9f19c51 feat(questions): wire pruneDeadTokens into onAnswerRevealed 2026-06-30 23:35:20 -05:00
null 13c0769efb feat(games): wire pruneDeadTokens into onGameSessionUpdate 2026-06-30 23:35:15 -05:00
null ffa038ca56 feat(dates): wire pruneDeadTokens into onDateReflectionWritten 2026-06-30 23:35:07 -05:00
null 2aa08efd39 feat(dates): wire pruneDeadTokens into onDateHistoryCreated 2026-06-30 23:35:00 -05:00
null f8240e01a9 feat(dates): wire pruneDeadTokens into createDateMatch 2026-06-30 23:34:56 -05:00
null 342ca37e91 feat(couples): wire pruneDeadTokens into scheduledOutcomesReminder 2026-06-30 23:34:39 -05:00
null c32a5ad8cc feat(couples): wire pruneDeadTokens into onCoupleLeave 2026-06-30 23:34:33 -05:00
null 6fda1574ea feat(backup): wire pruneDeadTokens into onRestoreRequested 2026-06-30 23:34:26 -05:00
null b564d1da42 feat(notifications): add pruneDeadTokens utility for FCM dead-token cleanup 2026-06-30 23:34:22 -05:00
null 37815af781 chore: rebuild functions dist (add onRestoreRequested + onRestoreFulfilled exports) 2026-06-30 20:43:58 -05:00
null 4b4f79361f feat(backup): add onRestoreRequested and onRestoreFulfilled Cloud Functions (partner push + owner self-alert) 2026-06-30 20:43:22 -05:00
null 4dd60a6a4d chore(date-memories): add compiled onDateReflectionWritten 2026-06-30 18:14:54 -05:00
null 2c47c222de chore(date-memories): add compiled onDateHistoryCreated 2026-06-30 18:14:52 -05:00
null d4cedaf304 chore(date-memories): update compiled function index 2026-06-30 18:14:50 -05:00
null b7ad62054b feat(date-memories): export onDateHistoryCreated and onDateReflectionWritten from index 2026-06-30 18:14:48 -05:00
null 7a5f4e9bbd feat(date-memories): add onDateReflectionWritten Cloud Function 2026-06-30 18:14:46 -05:00
null 2eb21f42f9 feat(date-memories): add onDateHistoryCreated Cloud Function 2026-06-30 18:14:44 -05:00
null 6dddac4615 Revert "feat(date-memories): add onDateHistoryCreated + onDateReflectionWritten Cloud Functions (batch 5/8)"
This reverts commit 8829cffa7e.
2026-06-30 18:13:00 -05:00
null 8829cffa7e feat(date-memories): add onDateHistoryCreated + onDateReflectionWritten Cloud Functions (batch 5/8) 2026-06-30 16:51:59 -05:00
null d87603211a feat(activity): ActivityScreen timeline, HomeScreen activity feed, HomeViewModel wiring, PartnerNotificationManager, sendThinkingOfYouCallable Cloud Function 2026-06-30 03:54:01 -05:00
null e74b6f59af feat(cloud-functions): onEntitlementChanged, acceptInviteCallable, onGameSessionUpdate, onAnswerRevealed, onMessageWritten — FirestoreUserDataSource E2EE, AppMessagingService, EditProfileScreen, iOS plan 2026-06-30 02:38:31 -05:00
null 2a5c40508e feat(notifications): QuietHoursManager + NotificationSettingsScreen rewrite, Cloud Functions (streakReminder, quietHours, reengagement, gameRetention), UserRepository E2EE wiring, SettingsDataStore, firestore rules, wiring-scan 2026-06-30 00:38:06 -05:00
null f51a55743c feat(games): partner game-session push orchestration — in-app notification banner, Firestore rules, Cloud Function, QA docs 2026-06-28 22:24:46 -05:00
null fa8005f25f feat(functions): wrapReleaseKeyCallable — server-side Tink wrap for iOS→Android release keys (closes keybox Path A interop gap) 2026-06-28 17:19:06 -05:00
null 37ed7cebec feat: quiet hours notifications, settings UI, game session updates, docs 2026-06-28 10:00:25 -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 2cd0af65a8 chore: working tree changes — QA docs, app tweaks, Cloud Functions updates 2026-06-27 13:31:09 -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