- 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>
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>
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>
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>
- 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>
- 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
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>