Skip to content

Commit 9fc5186

Browse files
committed
fix(scheduled-tasks): optimistic create insert + always-reset submit guard
- Seed the created job into the workspace-list cache on success so the new task renders instantly and the first-run empty state never flashes between the success toast and the list refetch; onSettled still reconciles authoritatively. - Reset the modal's submitting flag in a finally so the submit button can never stick disabled if the modal is kept open.
1 parent b46dcbd commit 9fc5186

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

apps/sim/app/workspace/[workspaceId]/scheduled-tasks/components/task-modal/task-modal.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ function TaskModalContent({
232232
* Submits the draft and waits for it to persist. The `submitting` guard blocks
233233
* a double-submit (Enter racing the click); on success the modal closes, on
234234
* failure it stays open so the draft survives — the mutation hook surfaces the
235-
* error via toast, so no message is duplicated here.
235+
* error via toast, so no message is duplicated here. `submitting` always resets
236+
* in `finally`, so the button can never stick disabled if the modal is kept open.
236237
*/
237238
const handleSubmit = async () => {
238239
if (!promptText || isPastLaunch || submitting) return
@@ -248,6 +249,8 @@ function TaskModalContent({
248249
})
249250
close()
250251
} catch {
252+
// Failure keeps the modal open; the mutation hook surfaces the error via toast.
253+
} finally {
251254
setSubmitting(false)
252255
}
253256
}

apps/sim/hooks/queries/schedules.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { requestJson } from '@/lib/api/client/request'
77
import { deployWorkflowContract } from '@/lib/api/contracts/deployments'
88
import {
99
type CreateScheduleBody,
10+
type CreateScheduleResponse,
1011
createScheduleContract,
1112
deleteScheduleContract,
1213
disableScheduleContract,
@@ -391,6 +392,55 @@ export function useUpdateSchedule() {
391392
})
392393
}
393394

395+
/**
396+
* Builds the workspace-list row for a just-created job from the create response
397+
* (server-generated `id`/`status`/`cronExpression`/`nextRunAt`) plus the request
398+
* body. Seeded into the list cache on success so the new task renders instantly
399+
* and the first-run empty state never flashes between success and the refetch;
400+
* `onSettled` then reconciles it with the authoritative row.
401+
*/
402+
function optimisticScheduleRow(
403+
response: CreateScheduleResponse,
404+
body: CreateScheduleBody,
405+
nowIso: string
406+
): WorkspaceScheduleRow {
407+
return {
408+
id: response.schedule.id,
409+
workflowId: null,
410+
deploymentVersionId: null,
411+
blockId: null,
412+
cronExpression: response.schedule.cronExpression,
413+
nextRunAt: response.schedule.nextRunAt,
414+
lastRanAt: null,
415+
lastQueuedAt: null,
416+
triggerType: 'schedule',
417+
timezone: body.timezone ?? 'UTC',
418+
failedCount: 0,
419+
infraRetryCount: 0,
420+
status: response.schedule.status,
421+
lastFailedAt: null,
422+
sourceType: 'job',
423+
jobTitle: body.title,
424+
prompt: body.prompt,
425+
lifecycle: body.lifecycle ?? 'persistent',
426+
successCondition: null,
427+
maxRuns: body.maxRuns ?? null,
428+
runCount: 0,
429+
sourceChatId: null,
430+
sourceTaskName: null,
431+
sourceUserId: null,
432+
sourceWorkspaceId: body.workspaceId,
433+
jobHistory: null,
434+
contexts: body.contexts ?? null,
435+
excludedDates: null,
436+
endsAt: body.endsAt ?? null,
437+
archivedAt: null,
438+
createdAt: nowIso,
439+
updatedAt: nowIso,
440+
workflowName: null,
441+
}
442+
}
443+
394444
/**
395445
* Mutation to create a standalone scheduled job
396446
*/
@@ -399,8 +449,14 @@ export function useCreateSchedule() {
399449

400450
return useMutation({
401451
mutationFn: async (body: CreateScheduleBody) => requestJson(createScheduleContract, { body }),
402-
onSuccess: () => {
452+
onSuccess: (data, variables) => {
403453
toast.success('Task scheduled')
454+
const nowIso = new Date().toISOString()
455+
queryClient.setQueryData<WorkspaceScheduleRow[]>(
456+
scheduleKeys.list(variables.workspaceId),
457+
(current) =>
458+
current ? [...current, optimisticScheduleRow(data, variables, nowIso)] : current
459+
)
404460
},
405461
onError: (error) => {
406462
logger.error('Failed to create schedule', { error })

0 commit comments

Comments
 (0)