Blocked by #200 and #201.
Lower priority than the core catalog, higher value than most of the polish. Do it after #201 lands.
Why this and not more countdown polish
Students do not miss competitions because they never heard of them. They miss them because the date passed while they were doing something else. A countdown only helps someone already looking at the page; a reminder reaches someone who is not.
This is the one feature in the vertical that turns a visit into a return visit, and the saves table (#199) already holds exactly the data it needs.
Two delivery paths, in this order
1. Calendar subscription (do this first). An .ics feed at /api/competitions/saved.ics?token=… that a student subscribes to once in Google Calendar or Apple Calendar. No email infrastructure, no sending, no deliverability, no unsubscribe flow, and the reminder lands in the app they already check. Generating iCalendar text is a pure function over the dates.
The token is a per-user opaque value, not the user id and not a JWT. Add a calendar_token column to user_profiles (#203) or a small table. The URL is a bearer credential, so treat it as one: rotatable from the account page, and never logged.
2. Email digest (only if the feed proves wanted). Weekly, listing saved competitions with a deadline in the next 14 days. Needs a sending provider, a scheduled job, unsubscribe handling and a bounce path. That is a real chunk of work and it is not worth starting before anyone asks for it.
Do not build browser push notifications. The PWA service worker exists, but push needs permission prompts, a push service and a subscription store, and it is the most easily ignored of the three.
Acceptance criteria
Blocked by #200 and #201.
Lower priority than the core catalog, higher value than most of the polish. Do it after #201 lands.
Why this and not more countdown polish
Students do not miss competitions because they never heard of them. They miss them because the date passed while they were doing something else. A countdown only helps someone already looking at the page; a reminder reaches someone who is not.
This is the one feature in the vertical that turns a visit into a return visit, and the saves table (#199) already holds exactly the data it needs.
Two delivery paths, in this order
1. Calendar subscription (do this first). An
.icsfeed at/api/competitions/saved.ics?token=…that a student subscribes to once in Google Calendar or Apple Calendar. No email infrastructure, no sending, no deliverability, no unsubscribe flow, and the reminder lands in the app they already check. Generating iCalendar text is a pure function over the dates.The token is a per-user opaque value, not the user id and not a JWT. Add a
calendar_tokencolumn touser_profiles(#203) or a small table. The URL is a bearer credential, so treat it as one: rotatable from the account page, and never logged.2. Email digest (only if the feed proves wanted). Weekly, listing saved competitions with a deadline in the next 14 days. Needs a sending provider, a scheduled job, unsubscribe handling and a bounce path. That is a real chunk of work and it is not worth starting before anyone asks for it.
Do not build browser push notifications. The PWA service worker exists, but push needs permission prompts, a push service and a subscription store, and it is the most easily ignored of the three.
Acceptance criteria
.icsfeed validates and imports into Google Calendar and Apple Calendar/competitions/saved