diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-04 12:36:21 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 12:55:05 +0200 |
| commit | f0e99dceb5a395eb542a6a67a0b56b7bb33a2474 (patch) | |
| tree | bd7cfab053395c507ad282fbebaf270ac1d6e2c2 /docs/superpowers/plans | |
| parent | 17c75c88ac91fa010de779b4c53883f1f58f0308 (diff) | |
| download | qtmaildir-f0e99dceb5a395eb542a6a67a0b56b7bb33a2474.tar.gz qtmaildir-f0e99dceb5a395eb542a6a67a0b56b7bb33a2474.zip | |
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 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/plans')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md index 4df48dd..5fcd6d8 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md @@ -65,6 +65,7 @@ taking that too literally. | 24 | No right-click actions on the thread list | discoverability | S | open | | 25 | No select-all, and bulk actions are undiscoverable | workflow | S | open | | 26 | No way to add or remove an arbitrary tag from the UI | workflow | S | **done** | +| 27 | The UI cannot see a sync it did not start | feedback | S | open | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -1192,6 +1193,73 @@ character. The test was corrected, not the validator. Rendered and inspected rather than only asserted. +## 27. The UI cannot see a sync it did not start + +**Observed (user, 2026-08-04), as a question:** "will the status bar be aware of +cronjob fired syncs?" It is not. The progress bar and the busy state are driven +by `MailSync`'s own `QProcess`, so they know only about syncs this window +started. The user's cron timer fires every ten minutes and the application is +blind to it. + +**The lock file is already the signal; no new file is needed.** `mailsync.sh` +does `exec 200>"$LOCKFILE"` and then `flock -n 200` on `/tmp/mbsync.lock`. +Another process can test whether that lock is held, without disturbing it, by +opening the same path on its own descriptor and attempting `flock(LOCK_EX | +LOCK_NB)`: the attempt fails exactly when someone holds it, and succeeds +otherwise, in which case the tester drops it again immediately. + +This is better than the status file first proposed. A kernel lock **cannot go +stale**: it is released when the holding process dies, however it dies. A status +file written by the script survives a `kill -9` and would leave the UI claiming +a sync is running forever, which then needs a heartbeat and a staleness +timeout, none of which the lock needs. + +**Decided (user, 2026-08-04): poll continuously, not only while quitting.** A +narrower version that polled only during the exit prompt was offered and +declined: the user wants the status bar informed at all times, not only at the +one moment it changes a decision. + +**Approach.** + +- A `syncLockHeld()` helper: `open()` the lock path, `flock(LOCK_EX|LOCK_NB)`, + close. Held when the attempt fails with `EWOULDBLOCK`. +- A `QTimer` polling it. **One second is finer than this needs**; a sync runs + for tens of seconds, so two seconds is plenty and halves a wakeup that never + stops. +- When the lock is held and `MailSync` is NOT the holder, show the busy state + with wording that says so: "Syncing (started elsewhere)". The existing + `setSyncBusy()` covers the widgets; this adds a third state between "idle" + and "we are syncing". +- On the exit path, this replaces a hedge with a fact. Today, quitting while + cron syncs says the window "cannot see it finish". With the lock watched it + can wait for the lock to clear and then quit, which is what the user actually + wants to happen. + +**Verify before building, both cheap and both the kind of assumption this +project has been bitten by twice:** + +- **That the lock is observable at all.** `flock` semantics across processes, + where the holder opened the file with `exec 200>`, are an assumption about + Linux behaviour, not a certainty. A twenty-line probe settles it. Do not + design on top of it unproven. +- **That polling it cannot disturb notmuch.** `mailsync.sh` runs `notmuch new`, + and notmuch's write lock is process-exclusive. Touching `/tmp/mbsync.lock` + should be unrelated, but the interaction is worth one look rather than an + assumption. + +**Constraints.** + +- The lock path becomes a contract between the script and the application, + where today the application knows nothing about it. It has to be documented + on both sides, and the script cannot move or rename it casually afterwards. +- The poll must not run a query or touch the database. It reports what another + process is doing; the existing `runCurrentQuery()` on a completed sync of our + own already handles refreshing, and a cron sync that finishes will be picked + up the next time the user runs a query. +- Do not report an externally-started sync as one this window can cancel. The + Sync button should be disabled while the lock is held, since starting one + would only produce the `EX_TEMPFAIL` skip. + --- ## Deferred, unsized, or split out |
