Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Groundwork for the v1→v2 Cloud Functions migration; no function is migrated yet
(all 35 still load as v1, verified via emulator discovery).
- options.ts: setGlobalOptions({ region: 'us-central1', maxInstances: 20 }), imported
first in index.ts so it applies before any v2 function is defined. Region pin is
load-bearing — the Android client uses the default region.
- notifications/push.ts: single canonical getUserTokens() + sendPushToUser() that
batches via messaging.sendEachForMulticast() and prunes dead tokens, to replace the
~10 copied token readers and ~19 copied send/prune blocks in later batches.
- log.ts: firebase-functions/logger re-export + redactToken() (FCM tokens are secrets).
- push.test.ts: 9 unit tests (token merge/dedupe, BatchResponse→dead-token mapping,
send/prune/no-op/whole-batch-failure paths). 67 tests green.
firebase-functions stays at v5.1.1 for the migration (supports both the root v1 API and
the /v2 subpaths); bump to v6 is deferred to the final batch once nothing references the
root namespace, so the build stays green at every step.
Co-Authored-By: Claude Opus 4.8 <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>
- 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