diff --git a/apps/web/src/routes/app/lists/[id]/+page.svelte b/apps/web/src/routes/app/lists/[id]/+page.svelte index 71e75eff..667e27b7 100644 --- a/apps/web/src/routes/app/lists/[id]/+page.svelte +++ b/apps/web/src/routes/app/lists/[id]/+page.svelte @@ -90,6 +90,7 @@ const canEditList = $derived(role === "OWNER" || role === "EDITOR"); const isOwner = $derived(role === "OWNER"); let removingId = $state(null); + let reordering = $state(false); // Local, reorderable copy of the items — svelte-dnd-action mutates this // directly during a drag; resets to the query's own items whenever those @@ -150,9 +151,10 @@ async function handleDndFinalize(e: CustomEvent<{ items: ListItemDto[] }>) { dragItems = e.detail.items; - if (!list) return; + if (!list || reordering) return; const listId = list.id; const expectedUpdatedAt = list.updatedAt; + reordering = true; patchList({ items: dragItems }); try { await reorderListItems( @@ -160,13 +162,16 @@ dragItems.map((i) => i.id), expectedUpdatedAt, ); + await queryClient.refetchQueries({ queryKey: detailKey }); } catch (err) { if (err instanceof ApiError && err.status === 409) { conflictNotice = true; - void queryClient.refetchQueries({ queryKey: detailKey }); + await queryClient.refetchQueries({ queryKey: detailKey }); return; } throw err; + } finally { + reordering = false; } } @@ -300,7 +305,7 @@ class="mt-6 flex flex-col gap-2" use:dndzone={{ items: dragItems, - dragDisabled: !canEditList, + dragDisabled: !canEditList || reordering, flipDurationMs: 150, }} onconsider={handleDndConsider} diff --git a/apps/web/src/routes/app/lists/lists.spec.ts b/apps/web/src/routes/app/lists/lists.spec.ts new file mode 100644 index 00000000..989be2cf --- /dev/null +++ b/apps/web/src/routes/app/lists/lists.spec.ts @@ -0,0 +1,15 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const source = readFileSync( + new URL("./[id]/+page.svelte", import.meta.url), + "utf8", +); + +describe("list reordering", () => { + it("refreshes the optimistic-lock timestamp after a successful reorder", () => { + expect(source).toMatch( + /await reorderListItems\([\s\S]*?\);\s*await queryClient\.refetchQueries\(\{ queryKey: detailKey \}\);/, + ); + }); +});