diff --git a/components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx b/components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx index e517b2ba3..10c27b3eb 100644 --- a/components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx +++ b/components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx @@ -93,6 +93,7 @@ type ScheduledSessionFormProps = { initialData?: ScheduledSession; }; +/** Maps a cron string to a preset select value, falling back to "custom". */ function resolveSchedulePreset(schedule: string): { preset: string; customCron: string } { const match = SCHEDULE_PRESETS.find((p) => p.value !== "custom" && p.value === schedule); if (match) { @@ -101,6 +102,7 @@ function resolveSchedulePreset(schedule: string): { preset: string; customCron: return { preset: "custom", customCron: schedule }; } +/** Reverse-matches stored workflow fields against OOTB workflows, returning the select value and custom fields. */ function resolveWorkflowState( activeWorkflow: WorkflowSelection | undefined, ootbWorkflows: { id: string; gitUrl: string; branch: string; path?: string }[] @@ -109,7 +111,8 @@ function resolveWorkflowState( return { selectedWorkflow: "none", customGitUrl: "", customBranch: "main", customPath: "" }; } const match = ootbWorkflows.find( - (w) => w.gitUrl === activeWorkflow.gitUrl && w.branch === activeWorkflow.branch + (w) => w.gitUrl === activeWorkflow.gitUrl + && w.branch === activeWorkflow.branch && (w.path ?? "") === (activeWorkflow.path ?? "") ); if (match) { @@ -131,10 +134,22 @@ export function ScheduledSessionForm({ projectName, mode, initialData }: Schedul ? resolveSchedulePreset(initialData.schedule) : { preset: "0 * * * *", customCron: "" }; - const [selectedWorkflow, setSelectedWorkflow] = useState("none"); - const [customGitUrl, setCustomGitUrl] = useState(""); - const [customBranch, setCustomBranch] = useState("main"); - const [customPath, setCustomPath] = useState(""); + const initialWorkflow = isEdit && initialData?.sessionTemplate.activeWorkflow + ? { + selectedWorkflow: "custom" as string, + customGitUrl: initialData.sessionTemplate.activeWorkflow.gitUrl, + customBranch: initialData.sessionTemplate.activeWorkflow.branch || "main", + customPath: initialData.sessionTemplate.activeWorkflow.path ?? "", + } + : { selectedWorkflow: "none", customGitUrl: "", customBranch: "main", customPath: "" }; + + const [selectedWorkflow, setSelectedWorkflow] = useState(initialWorkflow.selectedWorkflow); + const [customGitUrl, setCustomGitUrl] = useState(initialWorkflow.customGitUrl); + const [customBranch, setCustomBranch] = useState(initialWorkflow.customBranch); + const [customPath, setCustomPath] = useState(initialWorkflow.customPath); + const [workflowResolved, setWorkflowResolved] = useState( + !isEdit || !initialData?.sessionTemplate.activeWorkflow + ); const [repos, setRepos] = useState( isEdit && initialData?.sessionTemplate.repos ? [...initialData.sessionTemplate.repos] : [] ); @@ -197,18 +212,24 @@ export function ScheduledSessionForm({ projectName, mode, initialData }: Schedul } }, [modelsData?.defaultModel, form, isEdit, initialData]); - // Resolve workflow state once OOTB workflows are loaded (edit mode) - const [workflowResolved, setWorkflowResolved] = useState(false); + // Resolve workflow state once OOTB workflows finish loading. The Skeleton + // guard on the Select (workflowsLoading || !workflowResolved) ensures Radix + // never sees a value change after mount — the Select only mounts after this + // effect has set the final selectedWorkflow value. useEffect(() => { - if (isEdit && initialData && !workflowsLoading && !workflowResolved) { - const resolved = resolveWorkflowState(initialData.sessionTemplate.activeWorkflow, ootbWorkflows); - setSelectedWorkflow(resolved.selectedWorkflow); - setCustomGitUrl(resolved.customGitUrl); - setCustomBranch(resolved.customBranch); - setCustomPath(resolved.customPath); - setWorkflowResolved(true); - } - }, [isEdit, initialData, ootbWorkflows, workflowsLoading, workflowResolved]); + if (workflowResolved) return; + if (workflowsLoading) return; + + const resolved = resolveWorkflowState( + initialData!.sessionTemplate.activeWorkflow, + ootbWorkflows + ); + setSelectedWorkflow(resolved.selectedWorkflow); + setCustomGitUrl(resolved.customGitUrl); + setCustomBranch(resolved.customBranch); + setCustomPath(resolved.customPath); + setWorkflowResolved(true); + }, [workflowResolved, workflowsLoading, ootbWorkflows, initialData]); const effectiveCron = schedulePreset === "custom" ? (customCron ?? "") : schedulePreset; const nextRuns = useMemo(() => getNextRuns(effectiveCron, 3), [effectiveCron]); @@ -345,7 +366,7 @@ export function ScheduledSessionForm({ projectName, mode, initialData }: Schedul Name - +

{(field.value ?? "").length}/50 characters

@@ -361,7 +382,7 @@ export function ScheduledSessionForm({ projectName, mode, initialData }: Schedul Schedule +
@@ -393,7 +414,7 @@ export function ScheduledSessionForm({ projectName, mode, initialData }: Schedul )} {effectiveCron && ( -
+

{cronDescription}

{nextRuns.length > 0 && (
@@ -458,7 +479,7 @@ export function ScheduledSessionForm({ projectName, mode, initialData }: Schedul Initial Prompt -