diff --git a/client/src/utils/randomId.ts b/client/src/utils/randomId.ts index f9ae2ea1c..b17e3f65b 100644 --- a/client/src/utils/randomId.ts +++ b/client/src/utils/randomId.ts @@ -1,8 +1,9 @@ /** * The one place idempotency keys are minted. * - * Both producers — the offline queue's mutation id and the axios interceptor's - * per-request key — end up in the same `X-Idempotency-Key` header, which the + * Every producer — the offline queue's mutation id, the axios interceptor's + * per-request key and the upload queue's per-file key — ends up in the same + * `X-Idempotency-Key` header, which the * server caches on (key, user_id, method, path). Two writes that share a key * therefore collapse into one: the second gets the first one's cached response * instead of being applied, and the user loses an edit with no error anywhere. diff --git a/client/src/utils/uploadQueue.ts b/client/src/utils/uploadQueue.ts index 721cc2d77..fa7bdb298 100644 --- a/client/src/utils/uploadQueue.ts +++ b/client/src/utils/uploadQueue.ts @@ -1,4 +1,5 @@ import type { AxiosProgressEvent } from 'axios' +import { randomId } from './randomId' export interface UploadProgress { done: number @@ -66,7 +67,7 @@ export async function uploadFilesResilient( const i = idx++ if (i >= files.length) break const file = files[i] - const idempotencyKey = crypto.randomUUID() + const idempotencyKey = randomId() loadedMap.set(i, 0) let items: T[] | null = null