diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-29 10:11:59 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-29 10:11:59 +0200 |
| commit | e2bd68a863513209a5881696164fd463ddc83503 (patch) | |
| tree | fe9fc90462030f9f99d5298eb09785018cc60f40 /src/mainwindow.cpp | |
| parent | feb15bcec261501eb49ad0de1fdc05a9992227c2 (diff) | |
| download | qtmaildir-e2bd68a863513209a5881696164fd463ddc83503.tar.gz qtmaildir-e2bd68a863513209a5881696164fd463ddc83503.zip | |
fix: say an edit is waiting for the sync instead of claiming it landed
Item 182, found by hand: a thread of 9 messages with 5 unread, marked
read while a sync was running, reported "<subject>: mark as read" and
then reported the same work again when the sync finished. The user read
it as double reporting.
Not a double write, and the mail was correct. It is one action reported
twice because the FIRST report was the wrong one.
A sync holds notmuch's exclusive write lock and the worker's read-write
open blocks on it rather than failing, so an edit made during a sync is
held and sent when the lock frees. All three hold branches say exactly
that, in a label chosen deliberately: NOT transient, because it
describes state lasting until the sync ends, and a message that expired
would leave rows showing a tag the database has not got and no
explanation of why.
That label never survived. Every caller announced the action itself a
line later through showTransientStatus(), which overwrote it, so the
user was told the write had happened and the hold was never mentioned.
The flush at the end of the sync then reported the same work again and
read as a duplicate rather than as its completion.
announceAction() asks whether a sync holds the lock and, when one does,
sets a non-transient label naming the action AND the wait. The action is
still named because that announcement is what stands in for the
confirmation dialog this project rules out: it is how a user tells that
something larger than they meant has just happened, so the hold is added
to it rather than replacing it. The flush message is untouched and is
the only signal that held work actually landed, whose absence was item
106.
The test drives toggle_unread, the route the user took, and asserts both
halves: the text mentions the sync, and it still says what is waiting.
Asserting only the first would pass against an announcement that dropped
the action entirely. Mutation-checked by forcing the non-held branch,
which fails with the exact text the user reported.
The new string is translated, since one that misses the Italian ships as
English inside an otherwise Italian UI: lupdate found it with no context
warnings, lrelease reports 552 finished and 0 unfinished.
Suite: 42 of 43, with undoMovesTheMessageBack failing as it does on
master (item 136).
Diffstat (limited to 'src/mainwindow.cpp')
| -rw-r--r-- | src/mainwindow.cpp | 21 |
1 files changed, 18 insertions, 3 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index df7062d..1da9ac7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3638,7 +3638,7 @@ void MainWindow::markAllRead() description); m_undoStack.push(command); - showTransientStatus( + announceAction( tr("%1: %n thread(s)", "", threadIds.size()).arg(description)); } @@ -5198,6 +5198,21 @@ void MainWindow::onExternalSyncStateChanged(SyncMonitor::State state) } +void MainWindow::announceAction(const QString &text) +{ + if (!aSyncHoldsTheWriteLock()) { + showTransientStatus(text); + return; + } + + // Not showTransientStatus(): this describes state that lasts until the + // sync ends. The hold branch that queued the edit set the same kind of + // label and this replaces it, naming the action it was silent about. + m_statusLabel->setText( + tr("%1, waiting for the running sync to finish").arg(text)); + m_statusTimer->stop(); +} + void MainWindow::showTransientStatus(const QString &text) { m_transientMessage = text; @@ -5843,7 +5858,7 @@ void MainWindow::tagSelected(const QStringList &add, const QStringList &remove, // dialog CLAUDE.md rules out: undo is the safety net, and undo is only // usable if the user can tell that something larger than they meant has // just happened. - showTransientStatus( + announceAction( scope.wholeThread ? tr("%1: %n message(s) (whole thread)", "", scope.messageCount) .arg(description) @@ -6101,7 +6116,7 @@ void MainWindow::trashMessages(const QStringList &messageIds, tr("Delete"), false, wholeThreadIds); } - showTransientStatus( + announceAction( tr("%1: %n message(s)", "", messageCount).arg(tr("Delete"))); } |
