From 467fa84fd851459d5d9c9e5297e2500e938f0a18 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 31 Oct 2026 11:26:52 +0100 Subject: fix: keep bulk tag actions linear, and say when they land Select all on a 10k-row search, then Archive, froze the window with one CPU pinned. gdb on the live process showed the UI thread cubic before the write was ever sent: each optimistic row write emitted dataChanged, whose handler refreshed the action labels by walking the whole selection, and each step of that walk looked the message up with a linear scan of every thread. ThreadListModel now answers id lookups from a lazily rebuilt hash, invalidated by its own structural signals and by hand at the two in-place id changes. MainWindow::batchModelWrites() holds the label refresh while a loop of optimistic writes runs, then refreshes once. Two list membership tests became sets. The announcement also used to fade on a timer whether or not the write had landed. It now reads "applying" until a round trip through the worker's queue says the write has run, then "done". An error takes the label back from a late "done". Item 211. Co-Authored-By: Claude Opus 5.5 --- src/threadlistmodel.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/threadlistmodel.h') diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h index bed099d..fc7f65a 100644 --- a/src/threadlistmodel.h +++ b/src/threadlistmodel.h @@ -20,6 +20,7 @@ #include #include +#include #include #include "tagcolors.h" @@ -450,7 +451,26 @@ private: /// than claiming the union as one message's. static ThreadNode nodeFor(const ThreadSummary &summary); + /// The top-level row holding a thread or a message, or -1. + /// + /// Answered from a hash rather than a walk, because a bulk action asks + /// once per message and a walk per message made archiving ten thousand + /// rows quadratic in the lookups alone (item 211). The FIRST row in list + /// order wins, which is the row every linear walk here used to stop at, + /// so the two are interchangeable; a flat view can hold one thread on + /// several rows and still answers with the first. + int rowForThread(const QString &threadId) const; + int rowForMessage(const QString &messageId) const; + void rebuildIndex() const; + QVector m_threads; + // Rebuilt lazily on the next lookup after anything renumbers rows or + // changes which ids a node carries. Every structural change signals, and + // the two in-place id changes (reconcile, setThreadMessages) mark it by + // hand. + mutable QHash m_rowByThread; + mutable QHash m_rowByMessage; + mutable bool m_indexStale = true; const TagColors *m_tagColors = nullptr; QString m_dateFormat; QStringList m_forwardPrefixes; -- cgit v1.2.3