Skip to content

feat: rrule support for automations#2756

Open
janburzinski wants to merge 9 commits into
mainfrom
emdash/rrule-automation-5iu5k
Open

feat: rrule support for automations#2756
janburzinski wants to merge 9 commits into
mainfrom
emdash/rrule-automation-5iu5k

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • adds RRULE automation schedules

  • keeps existing cron schedules compatible

  • tracks cron vs rrule in telemtry

  • this enables more flexible automation schedules

Screenshot/Recording (if applicable)

https://cap.link/vs9dpc0qjqffy1b

Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds RRULE scheduling as an alternative to cron expressions for automations. It introduces a new rrule kind on TriggerConfig, a shared validation.ts layer for normalization, next-run computation, and validation, and a "Custom" slot in CronPicker that renders a free-text RRULE input. Telemetry is updated to track trigger kind, and existing cron automations remain fully backward-compatible via the kind ?? 'cron' default.

  • validation.ts is the core addition: normalizeTriggerConfig injects DTSTART when absent, getNextTriggerRunAt handles both cron (via croner) and rrule (via the rrule library with floating-date timezone arithmetic), and assertValidTrigger dispatches to the appropriate validator.
  • rrule-form-utils.ts provides buildRRuleTriggerExpr / getEditableRRuleExpr to round-trip the stored expression through the UI input field while preserving the DTSTART anchor.
  • useAutomationSettingsAutoSave.ts exposes saveCurrentTrigger and saveTriggerKind to wire RRULE blur-saves and kind-switching into the auto-save flow; a validationError state surfaces rrule parse failures inline.

Confidence Score: 4/5

Safe to merge with one fix: saveTriggerKind silently overwrites the stored schedule time when the user toggles the trigger-kind picker back to rrule.

The blur-save path correctly preserves DTSTART through buildRRuleTriggerExpr, but the kind-switch path in saveTriggerKind bypasses that logic and re-anchors the schedule to the current wall-clock time. A user who accidentally clicks away from Custom and back will silently shift their automation firing time.

useAutomationSettingsAutoSave.ts — saveTriggerKind needs to use the stored DTSTART from automation.triggerConfig when switching back to rrule mode.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/renderer/features/automations/useAutomationSettingsAutoSave.ts saveTriggerKind uses raw rruleExpr instead of buildRRuleTriggerExpr, silently re-anchoring the stored DTSTART (schedule time-of-day) to the current moment whenever the user toggles the trigger-kind picker back to rrule.
apps/emdash-desktop/src/shared/core/automations/validation.ts Core of the feature: adds rrule parsing, normalization (DTSTART injection), next-run computation, and validation. DST two-pass offset averaging and year zero-padding were addressed from previous threads; cron fallback of formatTriggerScheduleLabel returns raw pattern instead of human-readable label.
apps/emdash-desktop/src/renderer/features/automations/rrule-form-utils.ts New utility: getStoredDtstartLine, getEditableRRuleExpr, and buildRRuleTriggerExpr correctly preserve stored DTSTART when serializing user edits back to the canonical rrule string.
apps/emdash-desktop/src/renderer/features/automations/useAutomationFormState.ts Adds triggerKind and rruleExpr state; triggerConfig computed value correctly goes through buildRRuleTriggerExpr to preserve DTSTART on blur-saves from the settings panel.
apps/emdash-desktop/src/main/core/automations/repo.ts Replaces Cron-based getNextRunAt and assertValidCronTrigger with the unified getNextTriggerRunAt and assertValidTrigger; adds normalizeTriggerConfig on create/update paths as a server-side safety net.
apps/emdash-desktop/src/renderer/lib/CronPicker/CronPicker.tsx Extends CronPicker with an optional custom slot (selected state + content node); hides period-specific controls and parse-error banner when custom is active; clean extension with no breaking changes to existing callers.
apps/emdash-desktop/src/renderer/features/automations/components/AutomationSettingsFields.tsx Injects the 'Custom' rrule option into CronPicker via the new custom prop; wires setTriggerKind, onTriggerKindChange, and onRRuleExprBlur correctly to the parent handlers.
apps/emdash-desktop/src/shared/core/automations/validation.test.ts New test file covering normalization, next-run computation, DST timezone handling, expired finite schedules, label formatting, and invalid rrule rejection — good coverage of the happy and failure paths.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as AutomationSettingsFields
    participant FS as useAutomationFormState
    participant AS as useAutomationSettingsAutoSave
    participant V as validation.ts
    participant Repo as repo.ts (main)

    Note over UI,Repo: User edits RRULE expression and blurs field
    UI->>AS: "onRRuleExprBlur -> saveCurrentTrigger()"
    AS->>FS: read triggerConfig via buildRRuleTriggerExpr (preserves stored DTSTART)
    AS->>V: normalizeTriggerConfig(triggerConfig)
    AS->>V: assertValidTrigger(activeTrigger)
    AS->>Repo: "updateSettings({ triggerConfig })"
    Repo->>V: normalizeTriggerConfig - no-op if DTSTART present
    Repo-->>AS: saved Automation

    Note over UI,Repo: User switches trigger kind to RRULE via dropdown
    UI->>FS: setTriggerKind('rrule') [state update queued]
    UI->>AS: "onTriggerKindChange('rrule') -> saveTriggerKind('rrule')"
    AS->>FS: read raw rruleExpr (no DTSTART)
    AS->>V: "normalizeTriggerConfig injects DTSTART=now()"
    AS->>Repo: "updateSettings({ triggerConfig })"
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"}}}%%
sequenceDiagram
    participant UI as AutomationSettingsFields
    participant FS as useAutomationFormState
    participant AS as useAutomationSettingsAutoSave
    participant V as validation.ts
    participant Repo as repo.ts (main)

    Note over UI,Repo: User edits RRULE expression and blurs field
    UI->>AS: "onRRuleExprBlur -> saveCurrentTrigger()"
    AS->>FS: read triggerConfig via buildRRuleTriggerExpr (preserves stored DTSTART)
    AS->>V: normalizeTriggerConfig(triggerConfig)
    AS->>V: assertValidTrigger(activeTrigger)
    AS->>Repo: "updateSettings({ triggerConfig })"
    Repo->>V: normalizeTriggerConfig - no-op if DTSTART present
    Repo-->>AS: saved Automation

    Note over UI,Repo: User switches trigger kind to RRULE via dropdown
    UI->>FS: setTriggerKind('rrule') [state update queued]
    UI->>AS: "onTriggerKindChange('rrule') -> saveTriggerKind('rrule')"
    AS->>FS: read raw rruleExpr (no DTSTART)
    AS->>V: "normalizeTriggerConfig injects DTSTART=now()"
    AS->>Repo: "updateSettings({ triggerConfig })"
Loading

Reviews (3): Last reviewed commit: "fix(automations): preserve RRULE DTSTART" | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/renderer/features/automations/useAutomationFormState.ts Outdated
Comment thread apps/emdash-desktop/src/shared/core/automations/validation.ts
Comment thread apps/emdash-desktop/src/shared/core/automations/validation.ts Outdated
@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

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