Skip to content

feat(reminders): season-reminder opt-in/opt-out + sticky header + deterministic skill wrappers#27

Merged
cgoulart35 merged 1 commit into
masterfrom
feat/season-reminders-and-ui
Jun 15, 2026
Merged

feat(reminders): season-reminder opt-in/opt-out + sticky header + deterministic skill wrappers#27
cgoulart35 merged 1 commit into
masterfrom
feat/season-reminders-and-ui

Conversation

@cgoulart35

Copy link
Copy Markdown
Owner

What & why

Today the once-a-year "a new season has begun" blast goes to every past player unconditionally — non-players can't ask for a reminder, and nobody can opt out. This adds a persistent season-reminder list (opt-in + opt-out), pins the header, and moves the /test + /qa skill mechanics into deterministic scripts.

Reminders

  • Storage: {EVENT_ROOT}/reminders/{sha256(email)} = {email, status} — hash-keyed (idempotent), carried across the yearly rollover, never archived.
  • Recipients: past players ∪ opt-ins − opt-outs (getSeasonStartRecipients); identical to today when there are no reminder records.
  • Opt-in: off-season "Remind Me" form on ended.htmlPOST /remind/ (CSRF + Turnstile) → API POST /reminders/ (not season-gated, works while closed).
  • Opt-out: two-step unsubscribe so an email-client link prefetch can't opt anyone out — GET /unsubscribe/ shows a confirm page; POST performs it (CSRF + HMAC-API_KEY token re-verified).
  • Re-enlist: signing up to play clears any prior opt-out.
  • Scope: opt-out affects only the promotional blast; welcome/results emails always send (copy says so on every touchpoint).

UI

  • Header is now position: sticky so the title + hamburger menu stay reachable while scrolling (open sidebar lifted above it).

Tooling

  • scripts/test.sh + scripts/qa.sh wrap /test and /qa deterministically (bake in IMAGE_TAG, the EVENT_ROOT sandbox, email/gift-card env). qa status now prints the reminders + blast list.

Testing

  • 86 unit tests (token round-trips/tamper, reminder set/get, recipient math, rollover preserves reminders, re-enlist on signup, blast carries an unsubscribe link).
  • Browser QA (sandbox): sticky header (mobile + laptop), signup→re-enlist, two-step unsubscribe + confirm page, opt-out suppression (off the blast list, verified twice).
  • No new env vars or dependencies; VERSION is build-stamped.

🤖 Generated with Claude Code

…ministic skill wrappers

Make the once-a-year "a new season has begun" blast opt-in/opt-out instead of mailing
every past player unconditionally.

Reminders (src/common):
- Persistent list at {EVENT_ROOT}/reminders/{sha256(email)} = {email, status}; hash-keyed
  (idempotent), carried across the yearly rollover, never archived.
- Blast recipients = past players union opt-ins minus opt-outs (getSeasonStartRecipients);
  identical to prior behavior when there are no reminder records.
- Off-season "Remind Me" opt-in form on ended.html -> POST /remind/ (CSRF + Turnstile)
  -> API POST /reminders/ (not season-gated, so it works while closed).
- Two-step unsubscribe so an email-client link prefetch can't opt anyone out: GET
  /unsubscribe/ renders a confirm page; POST performs it (CSRF + HMAC token re-verified).
  Token is HMAC-API_KEY over the normalized email (stateless, in security.py).
- Signing up to play re-enlists you (clears any prior opt-out).
- Opt-out affects only the promotional blast; welcome/results emails always send. Copy on
  every touchpoint says so explicitly.

UI:
- Pin the header (position: sticky) so the title + hamburger menu stay reachable while
  scrolling; lift the open sidebar above it.

Tooling:
- scripts/test.sh and scripts/qa.sh wrap the /test and /qa flows deterministically (bake in
  IMAGE_TAG, the EVENT_ROOT sandbox, and the email/gift-card env); test/qa SKILL.md updated.
- qa status now prints the reminders + blast list for observability.

Tests (86 total): unsubscribe token round-trips/tamper, reminder set/get, recipient math
(opt-in/opt-out/dedup), rollover preserves reminders, re-enlist on signup, and the blast body
carries a per-recipient unsubscribe link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@claude

claude Bot commented Jun 15, 2026

Copy link
Copy Markdown

No blocking issues.

@cgoulart35
cgoulart35 merged commit 006616d into master Jun 15, 2026
5 checks passed
@cgoulart35
cgoulart35 deleted the feat/season-reminders-and-ui branch June 15, 2026 04:18
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.

1 participant