From bdeaaeac9cad58ba2517bf2029b91c4f502d117b Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 16:36:39 -0500 Subject: [PATCH] Simplify routine configuration UI - Add "Every minute" schedule preset as finest granularity - Remove status and priority from advanced delivery settings - Auto-generate trigger labels from kind instead of manual input Co-Authored-By: Paperclip --- ui/src/components/ScheduleEditor.tsx | 12 +++++++- ui/src/pages/RoutineDetail.tsx | 46 +++++----------------------- ui/src/pages/Routines.tsx | 16 +--------- 3 files changed, 20 insertions(+), 54 deletions(-) diff --git a/ui/src/components/ScheduleEditor.tsx b/ui/src/components/ScheduleEditor.tsx index a15d6629..23f6865f 100644 --- a/ui/src/components/ScheduleEditor.tsx +++ b/ui/src/components/ScheduleEditor.tsx @@ -4,9 +4,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Input } from "@/components/ui/input"; import { ChevronDown, ChevronRight } from "lucide-react"; -type SchedulePreset = "every_hour" | "every_day" | "weekdays" | "weekly" | "monthly" | "custom"; +type SchedulePreset = "every_minute" | "every_hour" | "every_day" | "weekdays" | "weekly" | "monthly" | "custom"; const PRESETS: { value: SchedulePreset; label: string }[] = [ + { value: "every_minute", label: "Every minute" }, { value: "every_hour", label: "Every hour" }, { value: "every_day", label: "Every day" }, { value: "weekdays", label: "Weekdays" }, @@ -60,6 +61,11 @@ function parseCronToPreset(cron: string): { const [min, hr, dom, , dow] = parts; + // Every minute: "* * * * *" + if (min === "*" && hr === "*" && dom === "*" && dow === "*") { + return { preset: "every_minute", ...defaults }; + } + // Every hour: "0 * * * *" if (hr === "*" && dom === "*" && dow === "*") { return { preset: "every_hour", ...defaults, minute: min === "*" ? "0" : min }; @@ -90,6 +96,8 @@ function parseCronToPreset(cron: string): { function buildCron(preset: SchedulePreset, hour: string, minute: string, dayOfWeek: string, dayOfMonth: string): string { switch (preset) { + case "every_minute": + return "* * * * *"; case "every_hour": return `${minute} * * * *`; case "every_day": @@ -110,6 +118,8 @@ function describeSchedule(cron: string): string { const timeStr = HOURS.find((h) => h.value === hour)?.label?.replace(":00", `:${minute.padStart(2, "0")}`) ?? `${hour}:${minute.padStart(2, "0")}`; switch (preset) { + case "every_minute": + return "Every minute"; case "every_hour": return `Every hour at :${minute.padStart(2, "0")}`; case "every_day": diff --git a/ui/src/pages/RoutineDetail.tsx b/ui/src/pages/RoutineDetail.tsx index fafe3b93..a3209319 100644 --- a/ui/src/pages/RoutineDetail.tsx +++ b/ui/src/pages/RoutineDetail.tsx @@ -49,8 +49,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import type { RoutineTrigger } from "@paperclipai/shared"; -const priorities = ["critical", "high", "medium", "low"]; -const routineStatuses = ["active", "paused", "archived"]; const concurrencyPolicies = ["coalesce_if_active", "always_enqueue", "skip_if_active"]; const catchUpPolicies = ["skip_missed", "enqueue_missed_with_cap"]; const triggerKinds = ["schedule", "webhook", "api"]; @@ -266,7 +264,6 @@ export function RoutineDetail() { const [advancedOpen, setAdvancedOpen] = useState(false); const [newTrigger, setNewTrigger] = useState({ kind: "schedule", - label: "", cronExpression: "0 10 * * *", signingMode: "bearer", replayWindowSec: "300", @@ -466,10 +463,12 @@ export function RoutineDetail() { }); const createTrigger = useMutation({ - mutationFn: async (): Promise => - routinesApi.createTrigger(routineId!, { + mutationFn: async (): Promise => { + const existingOfKind = (routine?.triggers ?? []).filter((t) => t.kind === newTrigger.kind).length; + const autoLabel = existingOfKind > 0 ? `${newTrigger.kind}-${existingOfKind + 1}` : newTrigger.kind; + return routinesApi.createTrigger(routineId!, { kind: newTrigger.kind, - label: newTrigger.label.trim() || null, + label: autoLabel, ...(newTrigger.kind === "schedule" ? { cronExpression: newTrigger.cronExpression.trim(), timezone: getLocalTimezone() } : {}), @@ -479,7 +478,8 @@ export function RoutineDetail() { replayWindowSec: Number(newTrigger.replayWindowSec || "300"), } : {}), - }), + }); + }, onSuccess: async (result) => { if (result.secretMaterial) { setSecretMessage({ @@ -787,33 +787,7 @@ export function RoutineDetail() { {advancedOpen ? : } -
-
-

Status

- -
-
-

Priority

- -
+

Concurrency

-
- - setNewTrigger((current) => ({ ...current, label: event.target.value }))} /> -
{newTrigger.kind === "schedule" && (
diff --git a/ui/src/pages/Routines.tsx b/ui/src/pages/Routines.tsx index 890641f6..2ca8a3f7 100644 --- a/ui/src/pages/Routines.tsx +++ b/ui/src/pages/Routines.tsx @@ -34,7 +34,6 @@ import { SelectValue, } from "@/components/ui/select"; -const priorities = ["critical", "high", "medium", "low"]; const concurrencyPolicies = ["coalesce_if_active", "always_enqueue", "skip_if_active"]; const catchUpPolicies = ["skip_missed", "enqueue_missed_with_cap"]; const concurrencyPolicyDescriptions: Record = { @@ -420,20 +419,7 @@ export function Routines() { {advancedOpen ? : } -
-
-

Priority

- -
+

Concurrency