From dd76b434c47ebf41d7f6440df0b1af87c0f7361f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 8 Oct 2026 21:48:38 +0200 Subject: [PATCH] fix(documents): cancel a layout read in flight before flipping the switch A first read of the vault layout that was still running when the switch was flipped landed afterwards and painted the old layout until the save answered, so the switch jumped back for a moment. --- .../__tests__/use-documents-layout.test.ts | 35 +++++++++++++++++++ src/lib/queries/use-documents-layout.ts | 7 ++++ 2 files changed, 42 insertions(+) diff --git a/src/lib/queries/__tests__/use-documents-layout.test.ts b/src/lib/queries/__tests__/use-documents-layout.test.ts index b7c3d01e5..93c5fd438 100644 --- a/src/lib/queries/__tests__/use-documents-layout.test.ts +++ b/src/lib/queries/__tests__/use-documents-layout.test.ts @@ -72,6 +72,41 @@ describe("runSetDocumentsLayout", () => { await run; }); + it("is not undone by a first read still in flight", async () => { + const queryClient = new QueryClient(); + let answerRead: (value: DocumentsLayout) => void = () => {}; + const read = queryClient + .fetchQuery({ + queryKey: key, + queryFn: () => + new Promise((resolve) => { + answerRead = resolve; + }), + }) + .catch(() => undefined); + let settle: (value: DocumentsLayout) => void = () => {}; + vi.mocked(apiPut).mockReturnValue( + new Promise((resolve) => { + settle = resolve; + }), + ); + + const run = runSetDocumentsLayout({ + patch: { view: "list" }, + queryClient, + t, + }); + // The read answers with the stored layout while the save is pending. + answerRead(STORED); + await read; + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(queryClient.getQueryData(key)?.view).toBe("list"); + + settle({ ...STORED, view: "list" }); + await run; + expect(queryClient.getQueryData(key)?.view).toBe("list"); + }); + it("rolls back and says so when the save fails", async () => { const queryClient = new QueryClient(); queryClient.setQueryData(key, STORED); diff --git a/src/lib/queries/use-documents-layout.ts b/src/lib/queries/use-documents-layout.ts index e9602a5d7..5b1251e0e 100644 --- a/src/lib/queries/use-documents-layout.ts +++ b/src/lib/queries/use-documents-layout.ts @@ -40,6 +40,13 @@ export async function runSetDocumentsLayout(deps: { }): Promise { const { patch, queryClient, t } = deps; const key = queryKeys.documentsLayout(); + // A read still in flight (the first GET) would land after the flip and + // paint the old layout until the PUT answered. Cancel it first; only then + // is the flip safe, because a cancelled fetch reverts the cache as it + // settles. Without one in flight the flip stays synchronous. + if (queryClient.isFetching({ queryKey: key }) > 0) { + await queryClient.cancelQueries({ queryKey: key }); + } const previous = queryClient.getQueryData(key); queryClient.setQueryData(key, { ...(previous ?? DEFAULT_DOCUMENTS_LAYOUT),