From f051dd657757bca3ac7e36454d7f2fc21f322f73 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 29 Aug 2026 09:49:29 +0200 Subject: fix: judge a conversation's trash state on all of its messages Item 178. everySelectedRowIsInATrashFolder() read ThreadSummary::firstMessagePath for any row that was not a message row. That was correct while a thread row MEANT that message (item 108) and stopped being correct when item 177 made it mean the conversation. A conversation is in the trash only when ALL of its messages are, so a partly trashed thread answered on whichever message the query returned first: Delete could be hidden on a conversation that still had mail outside the trash, and Restore offered on one that mostly did not. Not data-affecting. Both actions are no-ops in the wrong direction: Delete on already-trashed mail takes moveMessages()' already-there branch, and Restore on mail that was never trashed finds nothing to move. qtmaildir cannot produce such a thread itself, since Delete is absent on a reply row and Restore is thread-scoped. Two things outside it can: another client trashing a single message, and a reply arriving after the conversation was trashed. ThreadDigest already walks every message of the selected conversation for its sender counts, and a filename is served from the index like everything else in it, so the paths ride along on a request the selection already makes rather than costing a walk on every query. ThreadDigest::messagePaths is relative to the mail root, for the reason firstMessagePath records: an absolute path matches no account and silently resolves every row to none. MainWindow keeps them beside the dashboard's thread id and clears them when the dashboard is left, so a late digest cannot answer about another row. One limit, stated in the code rather than hidden. The digest is requested only for a single selected conversation row, so that is the only case with a real answer; any other selection falls back to the summary's one path. That fallback IS the pre-177 answer and is wrong in exactly the same partial case, which is the point: a multi-row selection is left no worse than it was, rather than given a second, differently wrong rule of its own. Making it exhaustive costs a per-query walk over every message, which is what this avoids. Two tests, both mutation-checked. The worker test puts its two messages in different folders, since two in one folder answer identically whichever way the code resolves them. The window test asserts both directions, so a fix that simply hid Delete everywhere would fail it, and sets totalCount explicitly: a summary left at the default is a message row, and the test would otherwise exercise the other branch and pass for the wrong reason. Suite: 42 of 43, with undoMovesTheMessageBack failing as it does on master (item 136). --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'CHANGELOG.md') diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdc536..34d9d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,16 @@ point at which they are stable. ### Fixed +- **Delete and Restore now judge a whole conversation.** They asked whether a + row was in the trash by looking at one of its messages, so a conversation + with some messages trashed and some not answered on whichever the query + returned first: Delete could be hidden on a conversation that still had mail + outside the trash, and Restore offered on one that mostly did not. Both + actions were no-ops in the wrong direction rather than destructive. A + conversation is now in the trash only when every one of its messages is. + qtmaildir cannot produce such a thread itself, since it deletes and restores + whole conversations; another mail client trashing a single message can, and + so can a reply arriving after a conversation was trashed. - **Undo no longer rewrites messages the action never touched.** Undoing a thread-scoped action inverted its tags while keeping the whole thread as its scope, so undoing "mark thread read" on a conversation of 44 messages that -- cgit v1.2.3