Skip to content

feat: streak-loss reminder notification#168

Open
code418 wants to merge 1 commit into
masterfrom
claude/streak-loss-notifications-6oqt85
Open

feat: streak-loss reminder notification#168
code418 wants to merge 1 commit into
masterfrom
claude/streak-loss-notifications-6oqt85

Conversation

@code418

@code418 code418 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

What & why

Expands the notification system with the first gameplay push (the existing ones are all social). Once a day, at an unpredictable time between 5pm and 9pm UK time, we remind players who claimed yesterday but haven't claimed today that they'll lose their daily streak unless they claim before midnight.

How it works

New scheduled function streakReminder (functions/src/streakReminder.ts), mirroring the newDayScoreboard pattern (thin onSchedule wrapper + exported, unit-tested helpers):

  • Cloud Scheduler can't fire at a random time, so the function polls every 15 min (*/15 17-20 * * *, Europe/London) across a 16-slot window (17:00 … 20:45). On the day's first run it rolls a random target slot and fires once when that slot is reached — giving an unpredictable send time that's guaranteed to land inside the 5–9pm window. Firing on slot >= target (not ==) keeps it robust to a skipped scheduler run.
  • Dispatch state ({ day, targetSlot, sentDay }) lives in a server-only notificationState/streakReminder doc, read-and-written inside a transaction so two overlapping invocations can't double-send.
  • Recipients: a single indexed equality query users where lastClaimDate == yesterday — since lastClaimDate advances to today the moment a user claims, this is exactly "claimed yesterday, not yet today" (and implies streak >= 1).
  • Message is Postman-James-voiced and tailored to the streak length.

Supporting changes:

  • _notifications.ts: export the existing sendToUser FCM helper for reuse.
  • _dateUtils.ts: add previousDay and getLondonHourMinute helpers.
  • New opt-out pref streakReminder (default on): defaulted in onUserCreated, gated with the existing === false convention, and surfaced as a Switch in the Settings → Notifications section (lib/settings_screen.dart).
  • firestore.rules: explicit server-only block for notificationState (consistent with reportQuotas/trustScores).
  • CLAUDE.md: documented the new function, helpers, and pref.

Tests

Added unit tests (functions/src/test/test.index.ts) for the slot maths (slotForLondonTime), the pure fire-decision (decideFire — random roll, fires at/after target, never twice/day, drops stale sentDay), pref gating (shouldNotifyStreakReminder), the previousDay/getLondonHourMinute helpers, the recipient query (mock Firestore), and the fan-out send (opt-out + allSettled). The region-pinning loop auto-covers the new export (europe-west2). All 33 new/related assertions pass; npm run build and eslint are clean.

Heads-up (pre-existing, unrelated): master currently references functions/src/onUserDeleted.ts and functions/src/_accountDeletion.ts (from the GDPR account-deletion feature), but those two files are absent from the repo, so the functions project does not compile as-is. This PR does not touch that area; I verified this branch's build/tests against temporary local stubs for those two modules, which are not included in the diff. Worth fixing separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S9hCUG7puzA94Ed46oJASU


Generated by Claude Code

Add a scheduled gameplay notification that nudges players who are about
to break their daily claim streak.

- New scheduled function `streakReminder` (functions/src/streakReminder.ts):
  polls every 15 min from 17:00–20:45 Europe/London and fires once per day
  at a random slot within that window, so the reminder lands at an
  unpredictable time between 5pm and 9pm UK time. Recipients are users whose
  `lastClaimDate == yesterday` (claimed yesterday, not yet today), so they
  still have a streak to lose.
- Dispatch bookkeeping (random target slot + sent marker) is persisted in a
  server-only `notificationState/streakReminder` doc, gated in a transaction
  so overlapping invocations can't double-send.
- Export `sendToUser` from `_notifications.ts` for reuse.
- Add `previousDay` and `getLondonHourMinute` helpers to `_dateUtils.ts`.
- New opt-out pref `streakReminder` (default on): defaulted in
  `onUserCreated`, gated with the existing `=== false` convention, and
  surfaced as a Switch in the Settings notifications section.
- firestore.rules: explicit server-only block for `notificationState`.
- Unit tests for the slot maths, the pure fire-decision logic, pref gating,
  recipient query, and fan-out send.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9hCUG7puzA94Ed46oJASU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants