mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
fix: close the gate's load-time window and stop sealing a failed sweep
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f
This commit is contained in:
co-authored by
Claude Opus 5
parent
c80002538d
commit
4e91b4e2f6
@@ -128,8 +128,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Input that arrives before this workspace's handle exists cannot be an edit
|
||||
// to its form — the form is not on screen yet — but it would open the gate,
|
||||
// and the gating effect would then un-suspend the moment the fetch lands,
|
||||
// in time for the schema's materialized values to POST as a draft.
|
||||
onUserInput(() => {
|
||||
if (selected) userEdited[selected] = true
|
||||
if (selected && selected in states) userEdited[selected] = true
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -139,6 +139,23 @@ describe('pruneMeaninglessDrafts', () => {
|
||||
expect(discardDraft).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not count, or seal the pass on, a delete the syncer failed to send', async () => {
|
||||
listDrafts.mockResolvedValue([row()])
|
||||
getDraftDiffValues.mockResolvedValue(diff())
|
||||
// The discard's own POST is what fails, so the state flips during it.
|
||||
discardDraft.mockImplementation(async () => {
|
||||
syncState = 'failed'
|
||||
return { success: true }
|
||||
})
|
||||
await pruneMeaninglessDrafts('main', 'me@x.dev')
|
||||
expect(sendUserToast).not.toHaveBeenCalled()
|
||||
discardDraft.mockResolvedValue({ success: true })
|
||||
// Sentinel unwritten, so the next mount retries the draft left behind.
|
||||
syncState = 'none'
|
||||
await pruneMeaninglessDrafts('main', 'me@x.dev')
|
||||
expect(discardedPaths()).toEqual(['u/me/r', 'u/me/r'])
|
||||
})
|
||||
|
||||
it('runs once per workspace and user', async () => {
|
||||
listDrafts.mockResolvedValue([row()])
|
||||
getDraftDiffValues.mockResolvedValue(diff())
|
||||
|
||||
@@ -124,6 +124,7 @@ export async function pruneMeaninglessDrafts(workspace: string, userKey: string)
|
||||
})
|
||||
|
||||
let discarded = 0
|
||||
let failed = 0
|
||||
for (const c of empty) {
|
||||
// Re-check: the reads above took a while, and the user may have opened
|
||||
// this item in the meantime.
|
||||
@@ -131,19 +132,24 @@ export async function pruneMeaninglessDrafts(workspace: string, userKey: string)
|
||||
const q = { workspace, itemKind: c.kind, path: c.path }
|
||||
UserDraftDbSyncer.recordRemoteSync(q, c.createdAt)
|
||||
const res = await discardDraft(c.kind, c.path, workspace, false, c.legacy, false)
|
||||
// A refused delete surfaces as a conflict, not an error: the row moved
|
||||
// past the baseline, so it is no longer the draft we judged empty.
|
||||
if (res.success && !UserDraftDbSyncer.getConflict(q).conflict) discarded++
|
||||
// Neither outcome throws: the syncer swallows an HTTP failure into its
|
||||
// per-key state, and a delete refused for a moved row comes back as a
|
||||
// conflict. So `success` alone says nothing about whether the row went.
|
||||
if (!res.success || UserDraftDbSyncer.getState(q).state === 'failed') failed++
|
||||
else if (!UserDraftDbSyncer.getConflict(q).conflict) discarded++
|
||||
}
|
||||
if (discarded > 0) {
|
||||
invalidateWorkspaceDrafts(workspace)
|
||||
sendUserToast(`Cleared ${discarded} draft${discarded > 1 ? 's' : ''} that carried no changes`)
|
||||
}
|
||||
// Only after a completed pass: a run that threw retries on the next mount.
|
||||
try {
|
||||
localStorage.setItem(sentinel, new Date().toISOString())
|
||||
} catch {
|
||||
// Nothing to do — the pass is idempotent, it just runs again.
|
||||
// Only once every deletion this pass attempted actually landed. A draft
|
||||
// left behind by a failed delete would otherwise never be revisited.
|
||||
if (failed === 0) {
|
||||
try {
|
||||
localStorage.setItem(sentinel, new Date().toISOString())
|
||||
} catch {
|
||||
// Nothing to do — the pass is idempotent, it just runs again.
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fire-and-forget from the layout: a workspace whose draft list can't be
|
||||
|
||||
Reference in New Issue
Block a user