From bde7409ef817089298718376e46a57b2d303cf02 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 14 Aug 2026 20:02:14 +0200 Subject: fix(read): repaint the card when one message is marked read Follows the message-scoped mark-read. The user reported the write going out with nothing visible changing: the status bar counted an unsynced edit while the card stayed bold and the message pane still showed the `unread` tag, until the next query corrected it. sendMessageTagChange made no optimistic model update on purpose, because applyTagChange is keyed by THREAD and repainting a whole row for a one-message edit would claim every reply had changed too. That trade is right for an explicit tag edit and wrong for auto mark-read, where the visible change IS the feature and the delay exists to deliver it. ThreadListModel::applyMessageTagChange updates the message wherever it is held, as a child row and as `first`, and lets the thread's summary follow only when the answer is unambiguous: a thread reads as unread while ANY message does, so the tag is cleared from the thread only when no other message still carries it. For an unexpanded multi-message thread the per-message tags are not loaded, so the summary is left for the next query rather than guessed at. Mutation checked: without the call the card holds `unread` for the full timeout. Co-Authored-By: Claude Opus 5 --- src/mainwindow.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 17a5a65..ae5e8fc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3482,10 +3482,17 @@ void MainWindow::sendMessageTagChange(const QStringList &messageIds, if (messageIds.isEmpty()) return; - // No optimistic model update. applyTagChange is keyed by THREAD and would - // repaint the whole row as though every message in it had changed, which - // for a one-message edit is a lie the user would see and then watch - // silently correct itself on the next query. + // Optimistic, but scoped to the message. applyTagChange() is keyed by + // THREAD and would repaint the whole row as though every message in it had + // changed, which for a one-message edit is a lie; applyMessageTagChange() + // updates that message and lets the thread's own tags follow only when the + // answer is unambiguous. + // + // Not optional for auto mark-read: without it the write goes out, the + // status bar counts an unsynced edit, and the card stays bold with + // `unread` on it until the next query. The user reported exactly that. + for (const QString &messageId : messageIds) + m_model->applyMessageTagChange(messageId, add, remove); // The accounts this touches, resolved through the containing threads: the // account is a property of the thread, and the sync needs the channel -- cgit v1.2.3