From 517fe5093e883784163164fd1c45b765a8de61fb Mon Sep 17 00:00:00 2001 From: dotta Date: Sat, 4 Apr 2026 19:57:25 -0500 Subject: [PATCH] Fix inbox archive flashing back after fade-out The archive mutation was only using CSS opacity to hide items while the network request was in flight. When the query refetch completed or the archiving timer expired, the item could reappear. Now we optimistically remove the item from React Query caches on mutate, snapshot previous data for rollback on error, and sync with the server in onSettled. Co-Authored-By: Paperclip --- ui/src/pages/Inbox.tsx | 49 +++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index b0898f40..45594db7 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -1245,30 +1245,53 @@ export function Inbox() { const archiveIssueMutation = useMutation({ mutationFn: (id: string) => issuesApi.archiveFromInbox(id), - onMutate: (id) => { + onMutate: async (id) => { setActionError(null); setArchivingIssueIds((prev) => new Set(prev).add(id)); + + // Cancel in-flight refetches so they don't overwrite our optimistic update + const queryKeys_ = [ + queryKeys.issues.listMineByMe(selectedCompanyId!), + queryKeys.issues.listTouchedByMe(selectedCompanyId!), + queryKeys.issues.listUnreadTouchedByMe(selectedCompanyId!), + ]; + await Promise.all(queryKeys_.map((qk) => queryClient.cancelQueries({ queryKey: qk }))); + + // Snapshot previous data for rollback + const previousData = queryKeys_.map((qk) => [qk, queryClient.getQueryData(qk)] as const); + + // Optimistically remove the issue from all inbox query caches + for (const qk of queryKeys_) { + queryClient.setQueryData(qk, (old: unknown) => { + if (!Array.isArray(old)) return old; + return old.filter((issue: { id: string }) => issue.id !== id); + }); + } + + return { previousData }; }, - onSuccess: () => { - invalidateInboxIssueQueries(); - }, - onError: (err, id) => { + onError: (err, id, context) => { setActionError(err instanceof Error ? err.message : "Failed to archive issue"); setArchivingIssueIds((prev) => { const next = new Set(prev); next.delete(id); return next; }); + // Restore previous query data on failure + if (context?.previousData) { + for (const [qk, data] of context.previousData) { + queryClient.setQueryData(qk, data); + } + } }, onSettled: (_data, error, id) => { - if (error) return; - window.setTimeout(() => { - setArchivingIssueIds((prev) => { - const next = new Set(prev); - next.delete(id); - return next; - }); - }, 500); + // Clean up archiving state and refetch to sync with server + setArchivingIssueIds((prev) => { + const next = new Set(prev); + next.delete(id); + return next; + }); + invalidateInboxIssueQueries(); }, });