diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-11 19:41:16 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-11 19:41:16 +0200 |
| commit | 64d3138ba923071069da6c9bc458a25a9cc7d27f (patch) | |
| tree | 0c35032240b7dcd2f92f613aec02147cc56ee8eb /src/mainwindow.h | |
| parent | 2c3a3d4da8ceb21bd1e2f7be16fcaa7473b6bbe6 (diff) | |
| download | qtmaildir-64d3138ba923071069da6c9bc458a25a9cc7d27f.tar.gz qtmaildir-64d3138ba923071069da6c9bc458a25a9cc7d27f.zip | |
feat(sync): sync a tag change automatically after a short delay
Item 71. A tag edit reached the notmuch index at edit time and then sat there
until the user clicked Sync or their cron job fired, so "mark all read" updated
the view while the change itself waited, sometimes for ten minutes.
A confirmed edit now arms a debounce that runs the existing sync path. The delay
is auto_sync_delay_ms in [general], defaulting to 2000, and follows
mark_read_delay_ms exactly, including that zero and negative are not errors:
zero syncs on the next trip through the event loop, and any negative value
disables the behaviour, which is the switch for a user who wants only their cron
job.
It is armed from onTagsApplied, where a write is confirmed and the pending count
is already current, rather than where one is sent: a sync scheduled for a write
the worker went on to reject would run for nothing. A debounce rather than a
schedule, restarted by each edit, because "mark all read" confirms one write per
thread in the view and an arm-per-edit timer would be the storm of syncs the
debounce exists to prevent. Nothing is armed when no sync command is configured
or when the pending count is zero, the case where an edit was netted against its
own inverse. When the timer fires with a sync already running, local or cron, it
skips rather than queues: mbsync's own answer to a second run is to fail on it,
and the edits stay pending rather than being lost.
Also fixes a pane blanked out from under the reader, found by hand testing this
feature. onSyncFinished called runCurrentQuery() where the cron path calls
refreshCurrentQuery(), and a re-run clears the model, the undo stack and the
message pane. The stale-thread notice handles a thread that stops matching the
query and has since item 35, but a re-run left nothing for it to describe. The
two paths had no reason to differ; before this item a local sync only followed a
click on Sync, so the difference went unnoticed. Reading a message in the Unread
view, having it marked read, and watching the pane go blank two seconds later is
what surfaced it.
Its test asserts on the undo stack rather than the pane: both paths issue a
queued query test_mainwindow has no worker to answer, so the pane ends up blank
either way and an assertion on it would pass against both, while the undo stack
is cleared by one and kept by the other.
Nine tests, four in test_config and five in test_mainwindow, each
mutation-checked: removing the schedule call, honouring a negative delay,
dropping the nothing-pending guard, dropping the already-running guard, and
restoring runCurrentQuery() each fail a test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/mainwindow.h')
| -rw-r--r-- | src/mainwindow.h | 19 |
1 files changed, 19 insertions, 0 deletions
diff --git a/src/mainwindow.h b/src/mainwindow.h index 0912520..19635dd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -390,6 +390,17 @@ private: /// Redraws the unsynced-edits indicator from pendingEditCount(). void updatePendingIndicator(); + /// Arms the debounce that syncs a confirmed tag edit out on its own + /// (item 71). Does nothing when the delay is negative, when no sync command + /// is configured, or when nothing is actually pending. + void scheduleAutoSync(); + + /// Starts the debounced automatic sync, unless a sync is already running + /// (local or external) or the edits it would carry are already gone. + /// + /// Q_INVOKABLE so a test can fire the debounce without waiting it out. + Q_INVOKABLE void runAutoSync(); + /// Records one confirmed (message, tag) change, cancelling it against an /// opposite change already outstanding for the same pair. void recordPendingEdit(const QString &messageId, const QString &tag, @@ -756,6 +767,14 @@ private: /// nothing rather than marking the wrong one. QString m_markReadThreadId; + /// Debounces the automatic sync that follows a tag edit (item 71). + /// + /// Single-shot and RESTARTED by every confirmed edit, for the same reason + /// m_markReadTimer is: tagging a multi-row selection confirms one write per + /// thread, and one sync per thread is exactly what a debounce exists to + /// prevent. + QTimer *m_autoSyncTimer = nullptr; + /// The optimistic update awaiting confirmation, kept so a worker error can /// put the model back. Only the most recent one: mutations are sent from /// the UI thread one user action at a time. |
