Mail list: a hidden row flickered back before its move landed
The server only queues a thread move, so the refetch right after the request still carried the row, and un-hiding it then brought it back until the live update removed it for good. A row now stays hidden until a fetched list no longer contains it, with a 30 s fallback in case the move never lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,9 @@ function quotaLabel(q: QuotaOut): string {
|
|||||||
|
|
||||||
// Threads shown per page in the mail list.
|
// Threads shown per page in the mail list.
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
|
// How long a row the list hid for a queued move waits for the move to land
|
||||||
|
// before it is shown again.
|
||||||
|
const HIDE_FALLBACK_MS = 30_000;
|
||||||
|
|
||||||
// Collapsed sidebar width: just wide enough for the icons.
|
// Collapsed sidebar width: just wide enough for the icons.
|
||||||
const COLLAPSED_W = 60;
|
const COLLAPSED_W = 60;
|
||||||
@@ -480,13 +483,26 @@ export function MailLayout() {
|
|||||||
unmark();
|
unmark();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["search"] }); // keep search results in sync
|
queryClient.invalidateQueries({ queryKey: ["search"] }); // keep search results in sync
|
||||||
queryClient.invalidateQueries({ queryKey: ["quota"] });
|
queryClient.invalidateQueries({ queryKey: ["quota"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["folder-counts"] });
|
queryClient.invalidateQueries({ queryKey: ["folder-counts"] });
|
||||||
// Once the list has refetched without the row, it no longer needs hiding.
|
// The server only queues the move, so the refetch above can still carry the
|
||||||
await queryClient.invalidateQueries({ queryKey: ["threads"] });
|
// row; the effect below un-hides it once a list arrives without it. Should
|
||||||
if (!searching) unmark();
|
// the move never land, the row comes back rather than staying lost.
|
||||||
|
if (!searching) window.setTimeout(unmark, HIDE_FALLBACK_MS);
|
||||||
};
|
};
|
||||||
|
// A row stays hidden until a fetched list no longer contains it.
|
||||||
|
useEffect(() => {
|
||||||
|
const rows = threadsQuery.data?.threads;
|
||||||
|
if (!rows) return;
|
||||||
|
setHiddenIds((prev) => {
|
||||||
|
if (prev.size === 0) return prev;
|
||||||
|
const present = new Set(rows.map((th) => th.id));
|
||||||
|
const next = new Set([...prev].filter((id) => present.has(id)));
|
||||||
|
return next.size === prev.size ? prev : next;
|
||||||
|
});
|
||||||
|
}, [threadsQuery.data]);
|
||||||
const listArchive = (id: string) => listMove(id, api.archiveThread);
|
const listArchive = (id: string) => listMove(id, api.archiveThread);
|
||||||
const listTrash = (id: string) => listMove(id, api.trashThread);
|
const listTrash = (id: string) => listMove(id, api.trashThread);
|
||||||
const listRestore = (id: string) => listMove(id, api.restoreThread);
|
const listRestore = (id: string) => listMove(id, api.restoreThread);
|
||||||
@@ -501,6 +517,7 @@ export function MailLayout() {
|
|||||||
// Selection resets when you leave the folder.
|
// Selection resets when you leave the folder.
|
||||||
useEffect(() => setCheckedIds(new Set()), [view]);
|
useEffect(() => setCheckedIds(new Set()), [view]);
|
||||||
useEffect(() => setDimmedIds(new Set()), [view, debouncedTerm]);
|
useEffect(() => setDimmedIds(new Set()), [view, debouncedTerm]);
|
||||||
|
useEffect(() => setHiddenIds(new Set()), [view]);
|
||||||
|
|
||||||
// Bulk actions run the given operation over every selected thread, then clear the
|
// Bulk actions run the given operation over every selected thread, then clear the
|
||||||
// selection and refresh (navigating away if the open thread was among them).
|
// selection and refresh (navigating away if the open thread was among them).
|
||||||
|
|||||||
Reference in New Issue
Block a user