mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-15 16:03:02 +00:00
fix(drafts): see the settle through, so a rename leaves nothing at the path it left
The settle sent its delete and returned on the first flush, but the form stays editable across that request: an edit made then parks a write behind it, and once the callers re-key to the new path that write lands as a draft under a path the item no longer occupies, with no editor on it. The delete is re-sent while the key refuses to settle on it — bounded, since a form still being typed into can always add one more. 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
e1859f504d
commit
239e19e7c6
@@ -285,12 +285,18 @@ export async function settleDraftAfterWrite<V>(
|
||||
savedPath: string,
|
||||
opts?: UserDraftOptions
|
||||
): Promise<void> {
|
||||
if (savedPath !== fromPath || draftValuesEqual(live, written)) {
|
||||
if (savedPath === fromPath && !draftValuesEqual(live, written)) return
|
||||
// Sent before this resolves, not left on the keystroke debounce: callers report
|
||||
// the write and remount on it, and both read a cell this has to have finished
|
||||
// resolving — including through a frame with a draft store of its own.
|
||||
//
|
||||
// Re-sent while the key is not settled on that delete: the form stays editable
|
||||
// across the request, and an edit made then parks a write behind it that would
|
||||
// put a draft back — under a path the item has left, once the callers re-key.
|
||||
// Bounded, because a form still being typed into can always add one more.
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
UserDraft.discard(itemKind, fromPath, written, opts)
|
||||
// Sent before this resolves, not left on the keystroke debounce: callers
|
||||
// report the write and remount on it, and both read a cell this has to have
|
||||
// finished resolving — including through a frame with a draft store of its own.
|
||||
await flushDraftWrites(itemKind, fromPath, opts)
|
||||
if (await flushDraftDelete(itemKind, fromPath, opts)) return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
|
||||
const discard = vi.fn()
|
||||
// Whether the key ends up settled on the delete after a flush — false stands for a
|
||||
// form that keeps queueing writes behind it.
|
||||
let settles = true
|
||||
vi.mock('./gen', () => ({ DraftService: { updateDraft: vi.fn() } }))
|
||||
vi.mock('./gen/core/OpenAPI', () => ({ OpenAPI: { BASE: '' } }))
|
||||
vi.mock('./localDraftHints.svelte', () => ({ setLocalDraftHint: vi.fn() }))
|
||||
vi.mock('./userDraftDbSyncer.svelte', () => ({
|
||||
UserDraftDbSyncer: {
|
||||
save: vi.fn(),
|
||||
flush: vi.fn(async () => {}),
|
||||
lastLandedWasDelete: vi.fn(() => settles),
|
||||
getState: vi.fn(() => ({ state: 'none' }))
|
||||
}
|
||||
}))
|
||||
|
||||
import { settleDraftAfterWrite, UserDraft } from './userDraft.svelte'
|
||||
|
||||
beforeEach(() => {
|
||||
settles = true
|
||||
discard.mockClear()
|
||||
vi.spyOn(UserDraft, 'discard').mockImplementation(discard as any)
|
||||
})
|
||||
@@ -53,6 +65,21 @@ describe('settleDraftAfterWrite', () => {
|
||||
expect(discard).toHaveBeenCalledWith('variable', 'u/me/a', sent, OPTS)
|
||||
})
|
||||
|
||||
// The form stays editable across the delete's own request, so an edit made then
|
||||
// parks a write that would put the draft back — under a path the item has left,
|
||||
// once the callers re-key. The delete is re-sent while that is the case, and
|
||||
// bounded, because a form being typed into can always add one more.
|
||||
it('re-sends the delete while the key will not settle on it, and gives up bounded', async () => {
|
||||
settles = false
|
||||
await settleDraftAfterWrite('variable', sent, { ...sent }, 'u/me/a', 'u/me/b', OPTS)
|
||||
expect(discard).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('sends it once when the key settles', async () => {
|
||||
await settleDraftAfterWrite('variable', sent, { ...sent }, 'u/me/a', 'u/me/b', OPTS)
|
||||
expect(discard).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('compares through the draft normalization, so a nested edit is not missed', () => {
|
||||
const written = { path: 'u/me/a', args: { host: 'h' } }
|
||||
settleDraftAfterWrite(
|
||||
|
||||
Reference in New Issue
Block a user