Keep list refreshes within the server's limits and stop repeating them

Refresh the list in pages of at most maxObjectsInGet. A list scrolled past
500 rows used to send all of its ids to one Email/get, which the server
refuses whole, and the refresh failed without a word.

Fetch the other messages of listed threads in their own capped requests,
and only those not already held. They used to be back-referenced from
Thread/get with no bound.

Have loadThread fetch bodies only for messages not held in full. Every push
refetched the whole open thread's bodies, and the new attachment objects
made the reading pane redo work it had already done.

Build the list query from the folder names, roles and tree rather than the
mailbox map, which every reload replaces. Each reload used to build a new
query, which query() answered with another full refresh.
This commit is contained in:
2026-09-16 08:25:57 -07:00
parent 47a2477d9f
commit e2b4cc18db
3 changed files with 280 additions and 34 deletions
+18 -1
View File
@@ -79,8 +79,25 @@ export function MailView({ mailboxId, threadId, search }: { mailboxId?: string;
[settings.listSortScope, settings.listSortPreset, settings.listSortLevels],
);
/*
* What the list query reads from the folder map: names, roles and places in
* the tree. `mailboxes` itself is replaced on every reload -- every push that
* touches mail reloads it for the counts -- and depending on it directly made
* each reload build a new query, which `query()` answered with a second full
* refresh of the list.
*/
const folderShape = useMemo(
() =>
Object.values(mailboxes)
.map((m) => `${m.id}\u0000${m.name}\u0000${m.role ?? ""}\u0000${m.parentId ?? ""}`)
.sort()
.join("\u0001"),
[mailboxes],
);
// Build & run the list query
const listQuery = useMemo<ListQuery | null>(() => {
const mailboxes = useMail.getState().mailboxes;
if (search) {
if (!q) return null;
const parsed = parseQuery(q);
@@ -94,7 +111,7 @@ export function MailView({ mailboxId, threadId, search }: { mailboxId?: string;
// are outgoing, and collapsing them into their threads hides them.
const isDraftsOrSent = mb?.role === "drafts" || mb?.role === "sent" || mailboxId === scheduledId;
return { key: "", filter: { inMailbox: mailboxId }, sort: sortForFolder(mailboxId), collapseThreads: settings.conversationMode && !isDraftsOrSent, mailboxId };
}, [search, q, mailboxId, mailboxes, settings.conversationMode, scheduledId]);
}, [search, q, mailboxId, folderShape, settings.conversationMode, scheduledId]);
useEffect(() => {
if (listQuery && mailboxesLoaded) void query(listQuery);