feat(scheduled-tasks): expose Google Calendar-style recurrence options#5146
Conversation
Add a per-day weekly toggle (repeat on arbitrary weekdays), monthly nth-/last-weekday anchoring, and a yearly frequency to the scheduled task modal, closing the gap with a calendar app's recurrence picker. The recurrence UI compiles to cron, so this is front-end only: croner already speaks the nth/last-weekday (#/#L) syntax, and the display path normalizes #L to cronstrue's L so labels read "last Monday" not "null".
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview UI ( Recurrence model ( Display: Tests cover cron build/parse, occurrence expansion for Reviewed by Cursor Bugbot for commit 9859dfa. Configure here. |
Selecting Monthly from the frequency dropdown hard-reset monthlyMode to day-of-month, silently dropping a previously chosen nth-/last-weekday anchor when switching cadence away and back. Preserve the existing mode on reselect, mirroring how the last recurring cadence is restored across the recurring toggle.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d4f5bc0. Configure here.
Greptile SummaryThis PR extends the scheduled-task recurrence modal to match a Google Calendar-style recurrence picker: weekly multi-day selection via day-toggle chips, monthly nth/last-weekday anchoring, and a new yearly frequency. All changes are front-end only — the UI compiles to a cron string that croner already understands, so no backend schema or API changes are needed.
Confidence Score: 5/5Safe to merge — all changes are front-end only, compile to cron strings the existing backend already understands, and are well-covered by new unit and integration tests. The recurrence logic is self-contained: it generates and parses cron strings without touching any schema, API route, or scheduling engine. The 5th-occurrence clamp, the recurrence.ts — the weekly Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User opens Recurrence Section] --> B{Frequency dropdown}
B -->|daily| C[emit: MIN HOUR * * *]
B -->|weekdays| D[emit: MIN HOUR * * 1,2,3,4,5]
B -->|weekly| E[Day-toggle chips]
E --> F[emit: MIN HOUR * * days]
B -->|monthly| G[Monthly mode dropdown]
G -->|day-of-month| H[emit: MIN HOUR DOM * *]
G -->|nth-weekday ordinal <= 4| I{5th occurrence?}
I -->|No| J[emit: MIN HOUR * * WD#N]
I -->|Yes - clamp| K[emit: MIN HOUR * * WD#L]
G -->|last-weekday| K
B -->|yearly| L[emit: MIN HOUR DOM MON *]
B -->|custom| M[pass through raw cron]
N[parseCronToHumanReadable] -->|normalize WD#L to WDL| O[cronstrue display]
J --> N
K --> N
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[User opens Recurrence Section] --> B{Frequency dropdown}
B -->|daily| C[emit: MIN HOUR * * *]
B -->|weekdays| D[emit: MIN HOUR * * 1,2,3,4,5]
B -->|weekly| E[Day-toggle chips]
E --> F[emit: MIN HOUR * * days]
B -->|monthly| G[Monthly mode dropdown]
G -->|day-of-month| H[emit: MIN HOUR DOM * *]
G -->|nth-weekday ordinal <= 4| I{5th occurrence?}
I -->|No| J[emit: MIN HOUR * * WD#N]
I -->|Yes - clamp| K[emit: MIN HOUR * * WD#L]
G -->|last-weekday| K
B -->|yearly| L[emit: MIN HOUR DOM MON *]
B -->|custom| M[pass through raw cron]
N[parseCronToHumanReadable] -->|normalize WD#L to WDL| O[cronstrue display]
J --> N
K --> N
Reviews (3): Last reviewed commit: "fix(scheduled-tasks): fold 5th-occurrenc..." | Re-trigger Greptile |
…align weekday-digit parsing Address review edge cases in the monthly recurrence anchors: - The picker no longer offers a fifth weekday (a 5th occurrence is always the month's last), and recurrenceToCron clamps any nth-weekday that resolves to a 5th occurrence to #L — so a launch date drifting to day 29-31 can never emit #5 and silently skip months without one. - cronToRecurrence accepts croner's alternate Sunday digit (7) for the #/#L monthly anchors, matching parseCronToHumanReadable's normalizer; externally-authored 7#L crons now round-trip (canonicalized to 0#L). - #5 crons are left as custom pass-through so their month-skipping behavior is preserved verbatim rather than rewritten.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9859dfa. Configure here.
Summary
Implementation notes
#/#L) syntax — no schema, contract, route, or backend scheduling changesChipModalFieldrows +Chiptoggles (active state), so spacing/typography match the rest of the modalparseCronToHumanReadablenormalizes croner's#Lto cronstrue'sLfor display only (stored cron keeps croner syntax) so labels read "last Monday" instead of "null Monday"Type of Change
Testing
expandOccurrencestest proving croner materializes1#3as the third Monday#/#LpatternsChecklist