fix: enable a draft-only schedule the way deploying it will

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViTUkt4czrvZdxdWwxqRAQ
This commit is contained in:
Diego Imbert
2026-09-13 06:09:01 +02:00
co-authored by Claude Opus 5
parent e3658ec288
commit db791f0493
3 changed files with 34 additions and 2 deletions
@@ -55,6 +55,7 @@
import { onUserInput } from '$lib/userDraftEditGate'
import { isTemporaryPath, newItemPath, useItem, type ItemAdapter } from '$lib/itemStore.svelte'
import {
draftOnlyScheduleCfg,
newScheduleCfg,
normalizeScheduleCfg,
scheduleCfgOf,
@@ -192,9 +193,12 @@
const s = await ScheduleService.getSchedule({ workspace, path, getDraft: true })
// Drafts are saved in the form's config shape, over the deployed fields.
const { draft, draft_saved_at, no_deployed, ...deployedSchedule } = s as any
const loadedDraft = draft
? normalizeScheduleCfg({ ...deployedSchedule, ...draft })
: undefined
return {
deployed: no_deployed ? undefined : normalizeScheduleCfg(deployedSchedule),
draft: draft ? normalizeScheduleCfg({ ...deployedSchedule, ...draft }) : undefined,
draft: no_deployed && loadedDraft ? draftOnlyScheduleCfg(loadedDraft) : loadedDraft,
draftSavedAt: draft_saved_at
}
} catch (err) {
@@ -1,5 +1,10 @@
import { describe, it, expect } from 'vitest'
import { normalizeScheduleCfg, scheduleCfgOf, scheduleFormOf } from './scheduleCfg'
import {
draftOnlyScheduleCfg,
normalizeScheduleCfg,
scheduleCfgOf,
scheduleFormOf
} from './scheduleCfg'
// A deployed schedule as the API returns it: server fields the form never shows, a short cron.
const deployed = {
@@ -37,4 +42,17 @@ describe('schedule config normalization', () => {
})
expect(once).not.toHaveProperty('edited_by')
})
// Deploying a draft-only schedule creates it, and a create always enables it. A draft that
// kept its stored state would show a disabled schedule the server has enabled, and later
// updates omit the field, so nothing would ever correct it.
it('records a draft-only schedule as enabled, however it was stored', () => {
expect(
draftOnlyScheduleCfg(normalizeScheduleCfg({ ...deployed, enabled: false }))
).toMatchObject({ enabled: true })
const { enabled: _dropped, ...withoutEnabled } = deployed
expect(draftOnlyScheduleCfg(normalizeScheduleCfg(withoutEnabled))).toMatchObject({
enabled: true
})
})
})
@@ -173,6 +173,16 @@ export function normalizeScheduleCfg(raw: ScheduleCfg): ScheduleCfg {
return scheduleCfgOf(scheduleFormOf(raw))
}
/**
* A schedule that exists only as a draft, as deploying it will leave it: deploying one creates
* it, and a create always enables it (`writeScheduleCfg`), so the draft has to carry the state
* that create produces. Left as stored, a disabled draft would record `enabled: false` against
* a server that says otherwise, and later updates omit the field, so nothing corrects it.
*/
export function draftOnlyScheduleCfg(cfg: ScheduleCfg): ScheduleCfg {
return { ...cfg, enabled: true }
}
/** The handlers a new schedule starts with, from the workspace's defaults. */
async function workspaceDefaultHandlers(workspace: string): Promise<ScheduleCfg> {
const [error, recovery, success] = (await Promise.all(