diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-07 10:12:10 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-07 10:12:10 +0200 |
| commit | a00d35452fcef8c8bc723ba7045fcae38fe8f315 (patch) | |
| tree | 510d0bb2d9265367648f38bbf43186f471379716 /src | |
| parent | 6326030b7179580b934ba852b0fd98577bfb464a (diff) | |
| download | qtmaildir-a00d35452fcef8c8bc723ba7045fcae38fe8f315.tar.gz qtmaildir-a00d35452fcef8c8bc723ba7045fcae38fe8f315.zip | |
fix: refresh the current query when the index changes
Item 192's second half, answered by the user: indexing is not repainting.
The sent copy became findable the moment it was sent and a Sent view already
on screen still did not show it, because nothing re-ran the query. The model
cannot insert the row optimistically either, since item 170's constraint
applies: the query never returned that thread.
NotmuchWorker::indexChanged() is emitted at the end of both indexDraftFile()
and removeIndexedFile(), the only two entry points that change what a path
query would return without any query having run. MainWindow connects it to
refreshCurrentQuery(), which covers all three gestures a path view can miss:
a sent copy indexed, a draft saved, a draft's entry dropped on send. Wiring
only the indexing half would have left a ghost draft row visible in a Drafts
view after a send.
The signal carries nothing, so it cannot invite an optimistic insert. It is
emitted after the database closes, so a refresh reaching notmuch on the next
turn of the event loop cannot race the write handle. refreshCurrentQuery()
rather than runCurrentQuery(): a send must not clear the selection, the
expansions or the undo stack of the window behind the composer.
aSentMessageAppearsInASentViewAlreadyOnScreen drives a real send through a
worker-backed window, asserts the Sent view is empty first, and asserts the
row arrives with no second returnPressed() and no sync. Mutation-checked by
disabling the connection.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWeBw3UqxpBktSc1AkZ6ir
Diffstat (limited to 'src')
| -rw-r--r-- | src/mainwindow.cpp | 13 | ||||
| -rw-r--r-- | src/notmuchworker.cpp | 8 | ||||
| -rw-r--r-- | src/notmuchworker.h | 15 |
3 files changed, 36 insertions, 0 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index abc7875..41dbe9c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2756,6 +2756,19 @@ void MainWindow::wireWorker() connect(m_worker, &NotmuchWorker::threadMessagesResolved, this, &MainWindow::onThreadMessagesResolved); + // Item 192's second half. Indexing a file is not repainting a view: the + // sent copy became findable the instant it was indexed, and a Sent view + // already on screen still did not show it, because nothing re-ran the + // query. Both index entry points report here, so a saved draft, a sent + // copy and a removed draft each reach the list that queries their folder. + // + // refreshCurrentQuery(), NOT runCurrentQuery(): a send must not clear the + // selection, the expansions or the undo stack of the window behind the + // composer. It returns early when no query has run and reconciles rather + // than replacing, so a view of another folder simply sees no change. + connect(m_worker, &NotmuchWorker::indexChanged, + this, &MainWindow::refreshCurrentQuery); + m_workerThread.start(); // Queued behind the thread start, so the completer has real tags as soon diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp index c8a2485..bc3ae65 100644 --- a/src/notmuchworker.cpp +++ b/src/notmuchworker.cpp @@ -1476,6 +1476,12 @@ void NotmuchWorker::indexDraftFile(const QString &path, notmuch_database_close(db); notmuch_database_destroy(db); + + // AFTER the close, so a refresh that reaches the database on the next turn + // of the event loop cannot race the write handle this function still held. + // notmuch permits one open handle per process, which is the same ordering + // constraint applyTags() and moveMessages() obey. + emit indexChanged(); } void NotmuchWorker::removeIndexedFile(const QString &path) @@ -1509,6 +1515,8 @@ void NotmuchWorker::removeIndexedFile(const QString &path) notmuch_database_close(db); notmuch_database_destroy(db); + + emit indexChanged(); } void NotmuchWorker::resolveMessages(const QStringList &messageIds, diff --git a/src/notmuchworker.h b/src/notmuchworker.h index a0c8feb..32eab58 100644 --- a/src/notmuchworker.h +++ b/src/notmuchworker.h @@ -351,6 +351,21 @@ signals: void threadDigestLoaded(const ThreadDigest &digest, quint64 generation); void tagsApplied(const TagChange &change); + /// One file entered or left the index outside a query (item 192). + /// + /// Emitted by indexDraftFile() and removeIndexedFile(), the two entry + /// points that change what a path query would return without any query + /// having run. A view built on such a query, Sent and Drafts both, is + /// stale the moment either fires: indexing a sent copy is what makes the + /// message findable, and MainWindow refreshes on this so the view the user + /// is looking at gains the row rather than waiting for the next sync. + /// + /// Carries nothing. The refresh re-runs the whole query, so which file + /// moved is not the UI's question; a payload would only invite an + /// optimistic insert, which item 170 rules out for a thread the query + /// never returned. + void indexChanged(); + /// Carries the ids that ACTUALLY moved, which may be fewer than requested. /// A stale id, a missing folder or a failed rename drops out here rather /// than aborting the batch. |
