Repository navigation
feat(site): Send the daily reminder by browser notification - #17
Merged
Merged
Conversation
Each morning of the week, October 1 to 8, a participant who turns on
reminders gets one short message that asks them to leave the car at
home and share that day's trip on My week, with one idea to try. The
eight messages now live in one place, src/data/reminders.json: each
day's notification title and body, and the text message a future
text channel sends, with {link} where the person's own link goes.
Every build and every dev server start checks the list and stops with
the message's date and the broken rule when an edit breaks one: eight
messages in date order, titles and texts that name their day, the
mail-in line on days 1 and 8, and texts that fit in one standard text
with the 45-character link, in plain characters only.
The tests hold the list to the campaign's wording word for word, put
a random personal link into each text to confirm it stays one GSM
text, and feed the check each kind of broken list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Day 8's reminder points participants to "Keep going after the week" on My week. /keep-going is one short address that reaches it from anywhere: on a phone signed in to My week, a small script opens My week at the card (now anchored as #keep-going); on any other phone it opens LVBT's join page. Both replace the page, so Back doesn't bounce into the redirect again. Without JavaScript, the page shows the card's heading and line with both links, and it stays out of search engines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Daily reminders reach phones as web push notifications, which need two pieces of standard cryptography. Both are written with Web Crypto alone, so they run on Workers with no new dependency. - Encryption (RFC 8291, aes128gcm): each message gets a new P-256 key pair and salt, and only the browser that subscribed can read it. - VAPID (RFC 8292): each push carries a 12-hour ES256 token for its push service, signed with VAPID_PRIVATE_KEY. The Worker works out the public key from the private key, so the two can't disagree and setup only has to generate one secret. The tests reproduce RFC 8291's worked example byte for byte, derive the RFC's sender public key from its private key, read messages back with a stand-in browser's key, and verify the token's signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
My week's "Remind me to share my trip" section now turns reminders on and off for real. It opens with the section's heading and line, then the "Browser notifications" part: one tap on "Turn on notifications" asks the browser's own question, subscribes with the Worker's public key and saves the subscription for the person signed in; "Stop reminders" cancels it in the browser first, so it works offline, then deletes the record. iPhones outside the Home Screen get the steps to add it, and blocked or unsupported browsers get a plain line. From 8:00 am on October 8 the section shows only its closing line. The old yes-or-no choices for notifications, texts and email never sent anything and nobody had saved one, so their table and route are gone. Subscriptions live in the new push_subscriptions table. A Cron Trigger on the odd minutes from 8:01 to 8:59 am Las Vegas time, October 1 to 8, sends that date's message to the next 40 browsers that turned reminders on before 8:00 am, six at a time, encrypted for each. Each browser gets a day's message once; one a push service says is gone is deleted, and anything else is tried again on the next run. Signing out leaves a phone's reminders on, as before. The service worker shows each reminder, one at a time, and a tap opens My week. The Worker picks each Cron Trigger's job, and reminder channels share one schedule, so a text message channel can be added beside browser notifications on the even minutes. Volunteers get a "Send test reminder" button in /admin, and the API preview sends its pinned day's reminder to each new browser every five minutes. VAPID_PRIVATE_KEY is a generated secret in platform.json; the Privacy page now says what reminders keep and who delivers them, and every subscription is deleted on November 30 with the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The production guide now lists what the reminders need and where each value lives: VAPID_PRIVATE_KEY, which pnpm bootstrap --production generates once and never replaces on a later run; the public key, which the Worker works out from it; VAPID_SUBJECT and the send times, already in wrangler.jsonc; and the subscriptions table, which the same command's migrations add. It says the owner does nothing by hand, how to replace a leaked key on purpose, and how to test a phone before October 1 with "Send test reminder". The README points to the message list and the preview's reminder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WillieCubed
force-pushed
the
feat/daily-reminders
branch
from
September 24, 2026 02:43
11fec3d to
cc9c3a4
Compare
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.
TL;DR
Participants can turn on one browser notification each morning of Week Without Driving, October 1 to
8, from My week, and stop it with one tap. Each notification asks them to leave the car at home and
share that day's trip, and tapping it opens My week. The Worker sends them itself, encrypted and
signed with Web Crypto, with no new dependency, and the production guide says exactly what a
maintainer needs to run to turn it on.
Overview of Changes
The reminder message list
Each morning, a participant who turns on reminders gets one short message asking them to leave the
car at home and share that day's trip on My week, with one idea to try. The eight messages live in
one place,
apps/site/src/data/reminders.json, word for word as the campaign wrote them: each day'snotification title and body, and the text message a future text channel will send, with
{link}where the person's own "Open my week" link goes. Every build and every
pnpm devchecks the list andstops with the message's date and the broken rule when an edit breaks one — eight messages in date
order, titles and texts that name their day, the mail-in line on days 1 and 8, and texts that fit in
one standard text message with the 45-character link, in plain characters only.
Turning reminders on and off
My week's "Remind me to share my trip" section opens with its heading and a line saying what the
reminder is, then "Browser notifications." Tapping "Turn on notifications" answers the browser's own
question, subscribes with the Worker's public key and saves the subscription for the signed-in
person; "Stop reminders" cancels the subscription in the browser first, so it works offline, then
deletes the record. An iPhone outside the Home Screen gets the existing "Add to your Home Screen"
steps; a browser that can't show notifications gets a plain line instead of a button. From 8:00 am on
October 8 the section shows only its closing line. Signing out leaves a phone's reminders on, as
designed. The old yes-or-no choices for notifications, texts and email never sent anything and
nobody had saved one, so their table and route are gone.
How the morning send works
Subscriptions live in the new
push_subscriptionstable. A Cron Trigger fires on the odd minutesfrom 8:01 to 8:59 am Las Vegas time, October 1 to 8, and each run sends that date's message to the
next 40 browsers that turned reminders on before 8:00 am, six at a time, encrypted for each. A
browser gets a day's message once; one its push service reports gone is deleted, and anything else is
tried again on the next run. The Worker's scheduled handler now picks the job by its Cron Trigger, so
the daily cleanup and the reminders never run each other's work, and a text channel can be added
later beside browser notifications on the even minutes. Volunteers get a "Send test reminder" button
in
/admin, and the API preview sends its pinned day's reminder to each new browser every fiveminutes.
Encryption and keys
Each notification is encrypted for its browser alone (RFC 8291, aes128gcm) and signed with a 12-hour
VAPID token for its push service (RFC 8292, ES256), both written with Web Crypto alone in
apps/site/worker/push/, so nothing new is added to run on Workers.platform.jsondeclares one newsecret,
VAPID_PRIVATE_KEY, generated once bypnpm bootstrap --production; the Worker works thepublic key out from the private key and serves it at
GET /api/push/key, so the two can neverdisagree. The tests reproduce RFC 8291's worked example byte for byte, derive the RFC's sender public
key from its private key, read messages back with a stand-in browser's key, and verify the token's
signature.
What production needs
A maintainer runs
pnpm bootstrap --productiononce after this merges; it applies migration0006and generates
VAPID_PRIVATE_KEYwithout anything to type or copy. The production guide now listswhat reminders need and where each value lives:
VAPID_PRIVATE_KEY(generated once, never replacedon a later run), the public key the Worker derives from it,
VAPID_SUBJECTand the send timesalready in
wrangler.jsonc, and the subscriptions table the same command's migrations add. It saysthe owner does nothing by hand, how to replace a leaked key on purpose, and how to test a phone
before October 1 with "Send test reminder." The Privacy page says what turning on reminders keeps,
who delivers them, and that the record is deleted when you stop or on November 30, 2026.
lvwwd.org/keep-going
Day 8's reminder points to "Keep going after the week" on My week.
/keep-goingreaches it fromanywhere: on a phone signed in to My week, a small script opens My week at the card; on any other
phone it opens LVBT's join page. Both replace the page, so Back doesn't bounce into the redirect
again. Without JavaScript, the page shows the card's heading and line with both links, and it asks
search engines not to index it.
How it was tested
Vitest against a local D1 covers the RFC 8291 vector and VAPID tokens; subscribing and the 401, 400
and 410 replies; the schedule (40 a run then the rest, the 8:00 am cutoff, nothing outside the week
or on another Cron Trigger); gone and failing push services; sign-out leaving reminders on; and
deletion on November 30. The service worker tests show and tap each of the eight reminders. Playwright
covers the section's states, turning on and off,
/keep-goingsigned in and out and with JavaScriptoff. On the API preview, Chrome signed up, turned reminders on, and received the pinned day's message
a few minutes later through Google's push service.
pnpm checkpasses.Follow-ups
reminder_opt_inanalytics event once LVBT's analytics allow lvwwd.org's events.🤖 Generated with Claude Code