From fbc65d2413190faa873d6b0b5cb9ed31ebba453b Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 7 Aug 2026 18:29:58 +0200 Subject: feat(ui): add a Maildir overview under Help Nothing in the UI reported database-level facts: every query gave a thread count for that query, and nothing said how much mail there is overall. A dialog under Help now shows messages, threads and tags from notmuch, plus the account list from config, since notmuch does not model accounts at all. A separate worker call rather than a reuse of requestCounts, which counts threads to match the row count of a query. This counts messages, which is what a user means by "how much mail is in here". The test pins 4 messages in 3 threads against the fixture and fails if they are ever made equal, so routing both through one count cannot pass unnoticed. Every field starts at -1 and renders as "unknown" when notmuch could not answer it. Printing 0 would say the Maildir is empty, and telling someone their mail is gone is the worst way to report an index that failed to open. The dialog opens showing "Counting..." rather than blocking, since counting every message is not free on a large database. That makes two lifetimes matter: the reply can arrive after the dialog is closed, so the label is a QPointer, and the dialog can be closed and reopened while a count runs, so a generation counter drops the older answer. The test drains DeferredDelete before firing the late reply, because close() deletes through deleteLater and without that the dangling case is never actually reached. --- .../plans/2026-08-03-post-0.1.0-usability.md | 36 +++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'docs') 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 2e0930c..1cc75a7 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 @@ -82,7 +82,7 @@ taking that too literally. | 31 | The quit prompt has no highlighted default button | discoverability | XS | **done** | | 32 | Esc does not blank the right pane | workflow | XS | **done** | | 33 | Status bar messages never expire | feedback | S | **done** | -| 34 | No overview of the Maildir itself | information | M | open | +| 34 | No overview of the Maildir itself | information | M | **done** | | 35 | No refresh of the thread list after a sync | workflow | M | open | | 36 | `test_mainwindow` cannot reach the worker | testing | S | open, on demand | | 37 | The worker stalls on a tag edit made during a background sync | correctness | S | **done** | @@ -2001,6 +2001,40 @@ quietly reinterpret what it returns. **Still true from the constraints above:** the account count comes from config, never from notmuch, and the tag list is already fetched for the completer. +### Outcome (done 2026-08-07) + +A dialog under Help, as decided. `requestDatabaseStats` answers messages, +threads and tags in one round trip; the account list comes from `Config`. + +**A separate worker call, not a reuse of `requestCounts`**, exactly as the +revision above warned. That one counts THREADS to match the row count of a +query; this one counts MESSAGES, which is what a user means by "how much mail +is in here". The test asserts 4 messages in 3 threads against the fixture and +fails if they are ever made equal, so a later "simplification" that routes both +through one count cannot pass. + +**Unknown is not zero.** Every field starts at -1, and a field notmuch could not +answer renders as "unknown". Printing 0 would say the Maildir is empty, which is +a claim, and telling someone their mail is gone is the worst available way to +report an index that failed to open. Verified by mutation: removing the guard +puts a literal `-1` on screen. + +**The dialog opens before the answer arrives**, showing "Counting...". Counting +every message is not free on a large database, and a dialog that blocks first is +worse than one that fills in. + +Two lifetime problems follow from that, both handled and both tested: + +- The reply can arrive after the dialog is closed. The label is held in a + `QPointer`, since `WA_DeleteOnClose` means a raw pointer dangles for exactly + as long as the count takes, which is when the user is most likely to have + given up and closed it. The test drains `DeferredDelete` before firing the + late reply, because `close()` deletes through `deleteLater` and without the + drain the case being tested is not the one that occurs. +- The dialog can be closed and reopened while a count runs, so a generation + counter drops the older answer rather than filling in the newer dialog with + numbers that predate the reopen. + ## 35. No refresh of the thread list after a sync **Observed (user, 2026-08-04):** "auto refresh list after sync." -- cgit v1.2.3