aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-31 11:26:52 +0100
committerDanilo M. <danix@danix.xyz>2026-10-31 11:26:52 +0100
commit467fa84fd851459d5d9c9e5297e2500e938f0a18 (patch)
tree392d0237cac273a6a07db11b120a8a453e8e8ff6 /AGENTS.md
parent9ae9473acdb7cb3310c3028765cc4b30fedfce27 (diff)
downloadqtmaildir-467fa84fd851459d5d9c9e5297e2500e938f0a18.tar.gz
qtmaildir-467fa84fd851459d5d9c9e5297e2500e938f0a18.zip
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 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md11
1 files changed, 11 insertions, 0 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 6df8e18..ce0f380 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -872,6 +872,17 @@ message-scoped optimistic update would have left a FAILED message write showing
its optimistic state for good. Both scopes revert now. Undo needs nothing extra:
`MessageTagCommand` routes back through `sendMessageTagChange`.
+**A loop of optimistic writes goes through `batchModelWrites()`, and a
+model lookup by id is a hash, not a walk.** `MainWindow`'s `dataChanged`
+handler refreshes the action labels, and that refresh reads the whole
+selection. Run once per written row it made Select all + Archive on 10k rows
+cubic, about 10^12 string compares, and the window froze before the write ever
+reached the worker (item 211). `batchModelWrites()` holds the refresh and runs
+it once. `ThreadListModel::rowForThread()` / `rowForMessage()` answer from an
+index rebuilt lazily after any structural signal. A change to a node's ids
+that emits none (as `reconcile` and `setThreadMessages` do) must set
+`m_indexStale` by hand, which `idLookupsFollowEveryChangeToTheRows` guards.
+
**Testing this needs two things that are easy to miss.** Put the reply under the
SECOND thread, so the wrong answer is plausible rather than accidentally right,
and give the two threads OPPOSITE states, since two threads in the same state