feat(reminders): season-reminder opt-in/opt-out + sticky header + deterministic skill wrappers#27
Merged
Merged
Conversation
…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>
|
No blocking issues. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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+/qaskill mechanics into deterministic scripts.Reminders
{EVENT_ROOT}/reminders/{sha256(email)}={email, status}— hash-keyed (idempotent), carried across the yearly rollover, never archived.past players ∪ opt-ins − opt-outs(getSeasonStartRecipients); identical to today when there are no reminder records.ended.html→POST /remind/(CSRF + Turnstile) → APIPOST /reminders/(not season-gated, works while closed).GET /unsubscribe/shows a confirm page;POSTperforms it (CSRF + HMAC-API_KEYtoken re-verified).UI
position: stickyso the title + hamburger menu stay reachable while scrolling (open sidebar lifted above it).Tooling
scripts/test.sh+scripts/qa.shwrap/testand/qadeterministically (bake inIMAGE_TAG, theEVENT_ROOTsandbox, email/gift-card env).qa statusnow prints the reminders + blast list.Testing
VERSIONis build-stamped.🤖 Generated with Claude Code