From fc0a955d0b16df09a0f64db28ca6256c4bdfe0e5 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 15 May 2026 17:22:13 -0700 Subject: [PATCH] Improve automations scheduling UX (#1997) * Improve automations scheduling UX * fix: address review findings --- .../automations/AutomationDetail.tsx | 31 +- .../automations/AutomationEditorDialog.tsx | 363 ++++++++++-------- .../automations/AutomationSchedulePicker.tsx | 226 +++++++++++ .../automations/AutomationsPage.tsx | 217 +++++++++-- .../automations/CreateFromPicker.tsx | 4 +- .../automations/WorkspaceCombobox.tsx | 4 +- .../automations/automation-templates.ts | 68 ++++ src/shared/automation-schedules.test.ts | 71 +++- src/shared/automation-schedules.ts | 315 ++++++++++++++- src/shared/automations-types.ts | 2 +- 10 files changed, 1049 insertions(+), 252 deletions(-) create mode 100644 src/renderer/src/components/automations/AutomationSchedulePicker.tsx create mode 100644 src/renderer/src/components/automations/automation-templates.ts diff --git a/src/renderer/src/components/automations/AutomationDetail.tsx b/src/renderer/src/components/automations/AutomationDetail.tsx index fe9228c4434..56d5eea7904 100644 --- a/src/renderer/src/components/automations/AutomationDetail.tsx +++ b/src/renderer/src/components/automations/AutomationDetail.tsx @@ -6,7 +6,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog' import type { Automation, AutomationRun } from '../../../../shared/automations-types' import type { Worktree } from '../../../../shared/types' -import { parseAutomationRrule } from '../../../../shared/automation-schedules' +import { formatAutomationSchedule } from '../../../../shared/automation-schedules' import { formatAutomationDateTime, formatAutomationDateTimeWithRelative, @@ -44,15 +44,6 @@ function DetailMetric({ label, value }: { label: string; value: string }): React ) } -function formatTime(hour: number, minute: number): string { - const date = new Date() - date.setHours(hour, minute, 0, 0) - return new Intl.DateTimeFormat(undefined, { - hour: 'numeric', - minute: '2-digit' - }).format(date) -} - function formatGrace(minutes: number): string { if (minutes <= 0) { return 'No grace' @@ -64,24 +55,6 @@ function formatGrace(minutes: number): string { return `${hours} ${hours === 1 ? 'hour' : 'hours'}` } -function formatSchedule(rrule: string): string { - const schedule = parseAutomationRrule(rrule) - if (schedule.preset === 'hourly') { - return `Hourly at :${String(schedule.minute).padStart(2, '0')}` - } - const time = formatTime(schedule.hour, schedule.minute) - if (schedule.preset === 'daily') { - return `Daily at ${time}` - } - if (schedule.preset === 'weekdays') { - return `Weekdays at ${time}` - } - const day = new Intl.DateTimeFormat(undefined, { weekday: 'long' }).format( - new Date(2026, 0, 4 + schedule.dayOfWeek) - ) - return `${day}s at ${time}` -} - function ToolbarIconButton({ label, children, @@ -223,7 +196,7 @@ export function AutomationDetail({ - + void onOpenChange: (open: boolean) => void onDraftChange: (updater: (current: AutomationDraft) => AutomationDraft) => void + onApplyTemplate: (template: AutomationTemplate) => void onSave: () => void } +function AutomationTemplateCard({ + template, + onSelect +}: { + template: AutomationTemplate + onSelect: () => void +}): React.JSX.Element { + return ( + + ) +} + export function AutomationEditorDialog({ open, isEditing, @@ -67,88 +98,142 @@ export function AutomationEditorDialog({ onProjectChange, onOpenChange, onDraftChange, + onApplyTemplate, onSave }: AutomationEditorDialogProps): React.JSX.Element { + const [templateOpen, setTemplateOpen] = React.useState(false) + return ( { event.preventDefault() }} > - - - {isEditing ? 'Edit Automation' : 'Create Automation'} - + +
+
+ + {isEditing ? 'Edit automation' : 'Create automation'} + + + onDraftChange((current) => ({ ...current, name: event.target.value })) + } + /> +
+ {!isEditing ? ( + + + + + +
+ {AUTOMATION_TEMPLATES.map((template) => ( + { + onApplyTemplate(template) + setTemplateOpen(false) + }} + /> + ))} +
+
+
+ ) : null} +
-
- - + {draft.scheduleWarning ? ( +
+ {draft.scheduleWarning} +
+ ) : null} + +