Per the user's ask: drop the friendly RRULE-based shortcuts (Daily /
Weekly / Custom… etc.) — every selectable preset is now a cron
expression. Schedules are stored in `reminders.rrule` with the
`CRON:` sentinel and dispatched via the existing cron-aware
`nextOccurrence` helper.
Picker
- "Don't repeat" stays at the top (one-off, no cron).
- 11 cron-flavoured presets, each with its underlying cron expression
shown as the hint:
Every minute * * * * *
Every 5 minutes */5 * * * *
Every 15 minutes */15 * * * *
Every 30 minutes */30 * * * *
Every hour at :MM MM * * * *
Every day at HH:MM MM HH * * *
Every weekday at HH:MM MM HH * * 1-5
Every weekend at HH:MM MM HH * * 0,6
Every <DOW> at HH:MM MM HH * * <cron-dow>
Every month on day D at HH:MM MM HH D * *
Every year on Mon D at HH:MM MM HH D M *
- Labels are first-fire-aware: changing the time picker re-derives
every "at HH:MM" label and the preset's canonical cron string.
- "Custom cron expression…" reveals a free-form text input for
anything not covered by the presets.
- Removed: the old "Custom" RRULE detail panel (frequency dropdown,
weekday picker, monthday input, end-condition picker).
Storage
- `presetToSpec("none")` → kind:"none". Every other preset →
kind:"cron" with its canonical cron string.
- `matchPreset` compares the spec's cron expression against each
preset's canonical cron for the current first-fire — falls back to
"cron" (custom textbox) for anything else, including legacy RRULE
reminders that haven't been re-saved yet. Existing RRULE reminders
keep firing on the bot side (nextOccurrence still dispatches both).
- `presetCron(id, firstFire)` is a small pure helper; ISO weekday
(1=Mon..7=Sun) maps to cron weekday (0=Sun..6=Sat).
Tests (+8 in recurrence.test.ts; 137 web + 26 bot + 17 shared = 180)
- presetToSpec emits the right cron for every recurring preset
including Sunday → cron weekday 0.
- matchPreset round-trips through presetToSpec for every preset.
- matchPreset returns "cron" for arbitrary (non-preset) cron strings.
- presetDescriptors lists exactly the cron-only items in order with
first-fire-aware labels ("Every weekday at 09:00", "Every Wed at
09:00", "Every year on May 13 at 09:00", "Custom cron expression…").
- buildRrule produces CRON: prefixed strings for cron presets and
null for "none".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>