From a00d35452fcef8c8bc723ba7045fcae38fe8f315 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 7 Sep 2026 10:12:10 +0200 Subject: 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 Claude-Session: https://claude.ai/code/session_01FWeBw3UqxpBktSc1AkZ6ir --- .../2026-08-03-post-0.1.0-usability-closed.md | 51 +++++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) (limited to 'docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md') diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md index c37f250..6613059 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md @@ -10187,11 +10187,48 @@ was that anything ever CALLS it for a sent copy. Asserting the path exists is not decoration: announcing a path that was never written is the ghost entry `removeIndexedFile()` exists to undo. Mutation-checked by removing the emit. -**Left open deliberately, and it is a real question rather than an -omission.** Indexing is not repainting. A Sent view already on screen does not -gain the row from this, because neither this nor the draft path re-runs the -query, and the model cannot insert a row optimistically for a thread its query -never returned (item 170's constraint). The message is now findable the moment -it is sent, which is what the user reported; whether the view should also -refresh itself is a separate decision. +**The open question was answered by the user on 2026-09-07: the Sent view +should refresh and show the message.** Indexing is not repainting, and the +first build left it there: the message became findable the moment it was sent +and a Sent view 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()`, connected to +`MainWindow::refreshCurrentQuery()`.** Emitted at the end of BOTH +`indexDraftFile()` and `removeIndexedFile()`, which are the only two entry +points that change what a path query would return without any query having +run. One connection 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, which is the same defect one signal over. + +Four decisions in it that are not incidental: + +- **The signal carries nothing.** A payload would invite an optimistic insert, + which item 170 rules out; the refresh re-runs the whole query, so which file + moved is not the UI's question. +- **`refreshCurrentQuery()`, never `runCurrentQuery()`.** A send must not clear + the selection, the expansions, the undo stack or the message being read in + the window behind the composer. The refresh reconciles instead of replacing, + returns early when no query has run, and leaves a view of another folder + showing no change at all. +- **Emitted AFTER the database is closed**, so a refresh reaching the database + on the next turn of the event loop cannot race the write handle. notmuch + permits one open handle per process, the same ordering `applyTags()` and + `moveMessages()` obey. +- **A send fires it twice**, once for the sent copy and once for the draft + removal. Both are queued and serialize, and the generation counter makes the + second supersede the first, so the cost is one redundant query and the final + state is correct. Not worth a coalescing timer. + +**One test, end to end through a real send.** +`aSentMessageAppearsInASentViewAlreadyOnScreen` builds a worker-backed window +with a sending account whose `sent` is configured, runs the Sent view's own +`path:` query, asserts it is EMPTY (a test starting from a non-empty view +could not tell a refresh from a row that was already there), composes and +sends, and asserts the row arrives with no further gesture: no second +`returnPressed()`, no sync. A test that re-ran the query by hand would pass +against the defect. Mutation-checked by disabling the connection, which fails +on the row count. -- cgit v1.2.3