Skip to content

feat(scheduled-tasks): expose Google Calendar-style recurrence options#5146

Merged
waleedlatif1 merged 3 commits into
stagingfrom
feature/scheduled-tasks-recurrence-options
Jun 20, 2026
Merged

feat(scheduled-tasks): expose Google Calendar-style recurrence options#5146
waleedlatif1 merged 3 commits into
stagingfrom
feature/scheduled-tasks-recurrence-options

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Add a per-day weekly toggle (repeat on any combination of weekdays) to the scheduled-task modal — previously only a single launch day or the Mon–Fri preset
  • Add monthly nth-/last-weekday anchoring ("on the third Tuesday" / "on the last Tuesday") alongside the existing "on day N"
  • Add a Yearly frequency ("Yearly on Jun 15")
  • Closes the gap with a calendar app's recurrence picker. The only unsupported option remains true "every N weeks/months" intervals (not cron-expressible — deliberately deferred)

Implementation notes

  • Front-end only: the recurrence UI compiles to a cron string, and croner already speaks the nth/last-weekday (# / #L) syntax — no schema, contract, route, or backend scheduling changes
  • New UI uses canonical ChipModalField rows + Chip toggles (active state), so spacing/typography match the rest of the modal
  • Monthly modes are derived from the launch date and all three are always offered, so changing the launch date can't dangle the dropdown value
  • parseCronToHumanReadable normalizes croner's #L to cronstrue's L for display only (stored cron keeps croner syntax) so labels read "last Monday" instead of "null Monday"

Type of Change

  • New feature

Testing

  • Added unit tests for the new recurrence↔cron conversions (weekly multi-day, monthly nth/last-weekday, yearly) and an end-to-end expandOccurrences test proving croner materializes 1#3 as the third Monday
  • Added human-readable tests for the #/#L patterns
  • 90 tests passing; changed files type-clean and biome-clean

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

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".
@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 20, 2026 5:57am

Request Review

@cursor

cursor Bot commented Jun 20, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes how user-selected recurrence compiles to cron (including monthly #/#L and yearly), which directly affects when tasks run; scope is localized and covered by new unit tests, with no backend contract changes.

Overview
Expands the scheduled-task recurrence picker with calendar-style options while still compiling to the same stored cron strings (no API or backend scheduler changes).

UI (RecurrenceSection): Adds a Yearly frequency, per-day Chip toggles for weekly repeat (any weekday mix, with at least one day kept), and a monthly “On” dropdown for day-of-month vs nth-weekday vs last-weekday anchors derived from the launch date. Frequency labels are simplified (e.g. “Weekly” / “Monthly” instead of embedding the launch day in the label).

Recurrence model (recurrence.ts): Introduces MonthlyMode and optional monthlyMode on Recurrence, plus yearly in RecurrenceFrequency. recurrenceToCron emits croner # / #L for monthly weekday rules (clamping a 5th-occurrence nth choice to #L), and fixed month/day for yearly. cronToRecurrence round-trips those patterns (including Sunday as 7), treats #5 as custom to avoid rewriting month-skipping crons, and recognizes yearly crons.

Display: parseCronToHumanReadable normalizes croner's #L to cronstrue's L for labels only.

Tests cover cron build/parse, occurrence expansion for 1#3, and human-readable output for nth/last weekday.

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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This 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.

  • New weekly UI: replaces the fixed "Weekly on Mon" label with a row of toggleable day chips; at least one day is always kept selected.
  • New monthly modes: nth-weekday (e.g. "third Tuesday") and last-weekday alongside the existing day-of-month option; the ordinal anchor is only offered for the 1st–4th occurrence to avoid months being silently skipped, with a belt-and-suspenders clamp in recurrenceToCron for dates that drift to a 5th occurrence via the date picker.
  • New yearly frequency: emits a 5-field cron with month and day-of-month fixed; parseCronToHumanReadable normalizes croner's #L to cronstrue's L so "last Monday" displays correctly.

Confidence Score: 5/5

Safe 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 #L display normalization, and the round-trip for alternate Sunday digit (7) are all verified by dedicated tests. The one asymmetry — the weekly comma-separated parser still uses [0-6] while the new # parsers use [0-7] — only affects externally-authored Sunday crons in the weekly path and doesn't corrupt any stored data.

recurrence.ts — the weekly cronToRecurrence branch regex.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts Adds MonthlyMode type, yearly frequency, nth/last-weekday cron generation and parsing; minor inconsistency — weekly parser still rejects Sunday digit 7 while the new #-pattern parsers accept it.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/task-modal/recurrence-section.tsx New weekly day-toggle chips, monthly anchor dropdown, and yearly frequency option added cleanly; fallback logic for 5th-week launch date is correct.
apps/sim/lib/workflows/schedules/utils.ts Adds pre-processing step to normalize croner's #L to cronstrue's L for display; change is scoped to the display path and does not touch the stored cron.
apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.test.ts Comprehensive new tests for nth/last-weekday, yearly, 5th-occurrence clamp, alternate Sunday digit acceptance, and an end-to-end expandOccurrences verification.
apps/sim/lib/workflows/schedules/utils.test.ts Adds tests verifying the nth-weekday human-readable label and that #L displays as "last Monday" without a "null" ordinal.

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
Loading
%%{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
Loading

Reviews (3): Last reviewed commit: "fix(scheduled-tasks): fold 5th-occurrenc..." | Re-trigger Greptile

Comment thread apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts Outdated
…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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@waleedlatif1 waleedlatif1 merged commit ce283fa into staging Jun 20, 2026
16 checks passed
@waleedlatif1 waleedlatif1 deleted the feature/scheduled-tasks-recurrence-options branch June 20, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant