From f0e99dceb5a395eb542a6a67a0b56b7bb33a2474 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 4 Aug 2026 12:36:21 +0200 Subject: feat(sync): dismissable log pane, progress bar, and a skip that is not a failure Four changes to the sync UI, three of them from using it. The log pane could not be dismissed. It is hidden at construction and shown on failure, and nothing ever hid it again, so a single failed sync left it on screen until the application restarted. It now sits in a container with its own Close button, and is 200px rather than 120, because mbsync's output is wide and repetitive and the shorter pane showed too little of it to read. It still appears only on failure, which the user confirmed is what they want. A sync gives no feedback while it runs. The status bar now carries an indeterminate progress bar for the duration, and the Sync button is disabled rather than left looking live. The bar is indeterminate on purpose: mbsync reports no percentage and the script's output is unstructured, so a bar filling left to right would be inventing a fraction nobody knows. The log is also cleared at the start of each run, since leaving the previous run's lines in place makes a stale failure look like the current one. The lock skip was reported as a failure. mailsync.sh exits when another run holds the lock, and that was exit 1, which qtmaildir reads as "sync failed": it showed the log pane and, on the exit path, told the user their changes were still unsynced. With a cron timer every ten minutes, a click landing inside a run is routine and none of that is true. The script now exits 75 (EX_TEMPFAIL) and the window reports it as its own case, saying a sync is already running. On the exit path it stays open and says plainly that the other run is most likely carrying the changes over but that this window cannot see it finish, rather than guessing either way. That last hedge is what item 27 records: the application cannot see a sync it did not start. The user chose continuous polling of the lock file over the narrower "only while quitting" version, and the entry notes that the lock is already the signal, so no status file is needed, and that a kernel lock cannot go stale where a written file can. Verified against stub binaries: a second run while the lock is held exits 75 and says SKIPPED, while the run holding it completes at 0. Co-Authored-By: Claude Opus 5 --- src/mainwindow.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.h b/src/mainwindow.h index 7897038..d1f7b1c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -39,6 +39,7 @@ class QPushButton; class QComboBox; class QPlainTextEdit; class QSplitter; +class QProgressBar; class QTimer; class ThreadListModel; @@ -66,6 +67,15 @@ public: /// cid: references from resolving to another's. static QString cidPrefixForIndex(int index); + /// What the sync command returns when another run already holds the lock. + /// + /// EX_TEMPFAIL from sysexits.h. A skip is not a failure: the other run is + /// doing the work, and with a cron timer every ten minutes a click landing + /// inside one is routine. Reporting it as an error would show a log pane + /// and an alarming status for a situation that needs neither. + /// `assets/mailsync.sh` is the reference implementation of this contract. + static constexpr int kSyncSkippedExitCode = 75; + /// Path of the machine-written UI state file. Deliberately not /// Config::defaultPath(): the config is hand-edited and must never gain a /// base64 geometry blob, nor be rewritten on exit (QSettings does not @@ -134,6 +144,15 @@ private: /// Redraws the unsynced-edits indicator from m_pendingEdits. void updatePendingIndicator(); + /// Shows or hides the "syncing" state: the progress bar and a disabled + /// Sync button. + /// + /// The bar is INDETERMINATE by design. mbsync reports no percentage and + /// the script's output is unstructured, so a bar that filled from left to + /// right would be inventing a fraction nobody knows. An indeterminate one + /// says "working, duration unknown", which is the truth. + void setSyncBusy(bool busy); + /// Opens the tag dialog on the current selection and applies its result. /// /// The only route to an arbitrary tag: every other tag action writes a @@ -185,6 +204,12 @@ private: /// Says how many tag changes have not been seen to reach the mail store. /// Hidden entirely at zero rather than reading "0 unsynced", which is noise. QLabel *m_pendingLabel = nullptr; + + /// Indeterminate, shown only while a sync runs. See setSyncBusy(). + QProgressBar *m_syncProgress = nullptr; + + /// Holds the sync log and its close button, so the pane can be dismissed. + QWidget *m_syncLogPane = nullptr; QPlainTextEdit *m_syncLog = nullptr; /// Action name (as used in [keys]) to the QAction implementing it. Owned -- cgit v1.2.3