aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md68
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