mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 16:02:59 +00:00
fix(drafts): settle what a schedule toggle queues, and void a removal marker any write outlives
Toggling enable writes the form before awaiting the deploy, so the autosave queues a draft against the pre-toggle baseline. Advancing the baseline on success then made that draft equal to it — invisible to the banner, still a row on the server and a `*` on the list. It is settled now, keeping an unrelated edit made during the request the way a save does. A draft-only removal marker was only ever voided by a chat write, so one left unread — discarded outside a session, where nothing consumes it — could be spent by an ordinary discard on a path since recreated, sending live editors away from an item that exists. Every write that gives the item a draft again voids it, which is also what `persistGlobalDraft` was doing by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
This commit is contained in:
co-authored by
Claude Opus 5
parent
069d66627d
commit
bfc9db5dfd
@@ -421,9 +421,6 @@ export async function persistGlobalDraft(
|
||||
const itemKind = itemKindFor(type, opts.triggerKind)
|
||||
if (!itemKind) throw new Error(`Unsupported draft type "${type}".`)
|
||||
const storagePath = resolveDraftStoragePath(workspace, itemKind, path)
|
||||
// Writing the item back means it exists again, so any removal marker left from
|
||||
// an earlier draft-only discard is void.
|
||||
UserDraft.clearDraftOnlyDiscard(itemKind, storagePath, { workspace })
|
||||
UserDraft.seed(itemKind, storagePath, value, { workspace })
|
||||
await UserDraftDbSyncer.save({
|
||||
workspace,
|
||||
|
||||
@@ -626,10 +626,10 @@
|
||||
const isSaved = await saveScheduleFromCfg(scheduleCfg, edit, wsId!)
|
||||
if (isSaved) {
|
||||
// A create deploys the schedule enabled whatever the form said (see
|
||||
// saveScheduleFromCfg), so what counts as written records that — otherwise
|
||||
// the baseline below claims a state the server does not have. The form
|
||||
// follows only while it still holds what was sent: changed during the
|
||||
// request, it is a newer edit, and `keptEdit` below is what protects it.
|
||||
// saveScheduleFromCfg), so what counts as written records that — the
|
||||
// baseline would otherwise claim a state the server does not have. The form
|
||||
// follows only while it still holds what was sent; changed since, it is an
|
||||
// edit of its own.
|
||||
if (wasCreate) {
|
||||
const sentEnabled = scheduleCfg.enabled
|
||||
scheduleCfg.enabled = true
|
||||
@@ -776,7 +776,20 @@
|
||||
if (initialConfig) initialConfig.enabled = nEnabled
|
||||
// This request carried the enabled flag alone: anything else the form has
|
||||
// diverged into is an edit of the user's, which a remount would drop.
|
||||
onUpdate?.(path, path, ws, !draftValuesEqual(getScheduleCfg(), initialConfig))
|
||||
const keptEdit = !draftValuesEqual(getScheduleCfg(), initialConfig)
|
||||
// Setting `enabled` above queued a draft against the pre-toggle baseline.
|
||||
// It matches the new one, so nothing would ever show it again — but it is
|
||||
// still a row on the server and a `*` on the list until it is settled away.
|
||||
if (initialConfig)
|
||||
settleDraftAfterWrite(
|
||||
'trigger_schedule',
|
||||
$state.snapshot(initialConfig),
|
||||
getScheduleCfg(),
|
||||
path,
|
||||
path,
|
||||
{ workspace: ws ?? undefined }
|
||||
)
|
||||
onUpdate?.(path, path, ws, keptEdit)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -320,6 +320,10 @@ export const UserDraft = {
|
||||
save<V>(itemKind: UserDraftItemKind, path: string, value: V, opts?: UserDraftOptions): void {
|
||||
const ws = resolveWorkspace(opts)
|
||||
const mk = mapKey(ws, itemKind, path)
|
||||
// The item has a draft again, so it exists again: a marker still standing
|
||||
// (nothing consumed it, because nothing was listening) describes an item this
|
||||
// write replaced, and would send the next editor to touch it away.
|
||||
draftOnlyDiscards.delete(mk)
|
||||
const entry = entries.get(mk)
|
||||
if (entry) {
|
||||
// The reactive effect in `acquireEntry` observes this write
|
||||
@@ -463,6 +467,7 @@ export const UserDraft = {
|
||||
seed<V>(itemKind: UserDraftItemKind, path: string, value: V, opts?: UserDraftOptions): void {
|
||||
const ws = resolveWorkspace(opts)
|
||||
const mk = mapKey(ws, itemKind, path)
|
||||
draftOnlyDiscards.delete(mk)
|
||||
const entry = entries.get(mk)
|
||||
if (!entry) {
|
||||
noteMarker(seedMisses, mk)
|
||||
@@ -483,12 +488,6 @@ export const UserDraft = {
|
||||
noteMarker(draftOnlyDiscards, mapKey(resolveWorkspace(opts), itemKind, path))
|
||||
},
|
||||
|
||||
/** Drop any removal marker for this cell — the item exists again, so a marker
|
||||
* still standing (nothing consumed it, because nothing was listening) would be
|
||||
* read by whatever touches it next and send its editor away. */
|
||||
clearDraftOnlyDiscard(itemKind: UserDraftItemKind, path: string, opts?: UserDraftOptions): void {
|
||||
draftOnlyDiscards.delete(mapKey(resolveWorkspace(opts), itemKind, path))
|
||||
},
|
||||
|
||||
/** Whether the last discard for this cell removed the item outright (see
|
||||
* {@link recordDraftOnlyDiscard}), clearing the record. */
|
||||
@@ -953,6 +952,8 @@ function acquireEntry(
|
||||
// copy. `untrack` so reactive reads in the predicate (the editor's
|
||||
// post-deploy baseline) don't re-fire the mirror.
|
||||
const atBaseline = untrack(() => val !== undefined && (discardIf?.(val) ?? false))
|
||||
// Same as `save`: a value persisted here means the item exists again.
|
||||
if (val !== undefined && !atBaseline) draftOnlyDiscards.delete(mk)
|
||||
void UserDraftDbSyncer.save({
|
||||
workspace,
|
||||
itemKind,
|
||||
|
||||
Reference in New Issue
Block a user