Skip to content

feat: add hour range mode for calendar schedule#2

Merged
azu merged 3 commits intomainfrom
feature/calendar-hour-range
Mar 14, 2026
Merged

feat: add hour range mode for calendar schedule#2
azu merged 3 commits intomainfrom
feature/calendar-hour-range

Conversation

@azu
Copy link
Copy Markdown
Owner

@azu azu commented Mar 14, 2026

Summary

Adds an "hour range" scheduling mode to the calendar schedule configuration, allowing users to define a range of hours (e.g., 7:00–23:00) instead of specifying each hour individually. Calendar utility functions are extracted into a dedicated module with tests.

image

Changes

  • Extract calendar interval formatting and next-occurrence logic from JobDetail.tsx and JobForm.tsx into src/lib/calendar-utils.ts
  • Add three hour modes in JobForm: "Specific hour", "Every hour", and "Hour range"
  • Detect existing hour-range patterns (contiguous hours with same base config) when editing agents
  • Display hour ranges compactly in JobDetail (e.g., "Every day at :00 (7:00–23:00)")
  • Expand hour ranges into individual CalendarInterval entries for plist output
  • Add getNextOccurrencesMulti to compute next runs across multiple intervals
  • Add unit tests for all calendar utility functions (14 tests)

Breaking Changes

None

Test Plan

  • Run pnpm test — all 23 tests pass (14 new calendar-utils tests)
  • Run pnpm lint and pnpm typecheck — no errors
  • Create a new agent with hour range schedule and verify the plist contains individual CalendarInterval entries
  • Edit an existing agent with multiple contiguous-hour CalendarIntervals and verify the range is detected
  • Verify next run preview shows correct times for range schedules

Open with Devin

Extract calendar utilities into dedicated module and add support for
hour range scheduling (e.g., run every hour from 7:00 to 23:00).
Detects existing hour-range patterns when editing agents and displays
them compactly in the detail view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@azu azu added the Type: Feature New Feature label Mar 14, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread src/lib/calendar-utils.ts
Comment on lines +159 to +161
const hour = ci.hour ?? 0
const minute = ci.minute ?? 0
parts.push(`at ${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 formatSingleCalendarInterval displays hour: null (every-hour) schedules as "00:XX" instead of indicating hourly recurrence

When the user creates a schedule with the new "Every hour" mode, calendarInterval.hour is set to null and saved as start_calendar_interval: [{ hour: null, minute: 0, ... }]. When this job is viewed in JobDetail, formatCalendarIntervals at src/lib/calendar-utils.ts:127 is called with a single interval. Since detectHourRange requires intervals.length >= 2 (src/lib/calendar-utils.ts:14), it returns null, falling through to formatSingleCalendarInterval. There, const hour = ci.hour ?? 0 at line 159 treats null (meaning "every hour" in launchd) as 0, producing output like "Every day at 00:00" when the job actually runs 24 times per day (at :00 of every hour). This is misleading — a user would believe their job runs once at midnight rather than every hour.

Suggested change
const hour = ci.hour ?? 0
const minute = ci.minute ?? 0
parts.push(`at ${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`)
if (ci.hour === null || ci.hour === undefined) {
const minute = ci.minute ?? 0
parts.push(`every hour at :${String(minute).padStart(2, "0")}`)
} else {
const minute = ci.minute ?? 0
parts.push(`at ${String(ci.hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`)
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@azu azu merged commit 2047008 into main Mar 14, 2026
3 checks passed
@azu azu deleted the feature/calendar-hour-range branch March 14, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Feature New Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant