aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-29 13:55:47 +0200
committerDanilo M. <danix@danix.xyz>2026-08-29 13:55:47 +0200
commit04c32f7c4eb5f72247af9963fa3debdbf6eb8911 (patch)
tree72ce61af1489194772ede2935245af411c4caf31 /docs/superpowers
parentb1fdf59e22f153e27b6526129fea51f5f679f3b9 (diff)
downloadqtmaildir-04c32f7c4eb5f72247af9963fa3debdbf6eb8911.tar.gz
qtmaildir-04c32f7c4eb5f72247af9963fa3debdbf6eb8911.zip
docs: settle item 187 on Trash's shapeHEADmaster
Three decisions from the user. Spam is path-based like Trash, not a tag generator: a tag query finds only what this application marked and misses everything the server filed, which is most of what those folders hold. Mark spam moves the file, as Delete does, which is what makes a path-based view honest and is a change to an existing action rather than only a new view. The Junk folder one account carries is out of scope. Every account can reach a spam folder now. The three channels that listed their folders explicitly gained the provider's spam folder this session, verified against mbsync --list rather than guessed, since Create Both turns a wrong name into a folder created on the server. Records the cleanup pass as a copy of showStrandedDeletedMail() from item 103, which solves the same problem one version earlier: it reports mail whose file never moved and leaves the user to act, and its empty-list and AlreadyScoped details are load-bearing. Notes that a mandatory spam key breaks existing configs on upgrade exactly as trash did, and that the trash predicate behind the message bar must not treat a spam folder as a trash one. Resized S to M: the move path, the origin tag and the cleanup are three parts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NY6poqw199LfFaXe5BHKNe
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md124
1 files changed, 67 insertions, 57 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 c0db234..32888a2 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
@@ -260,7 +260,7 @@ taking that too literally.
| 184 | New mail waits up to ten minutes, because sync is a fixed cron tick | workflow | ? | open, 2026-08-29, from the user: the 10 minute tick "has always bothered me", and it is already a compromise down from 30. Outgoing edits are immediate (`auto_sync_delay_ms`), so this is the INCOMING half only. Polling faster is not the answer; IMAP IDLE is, and it lives in a watcher that triggers `mailsync.sh`, NOT in qtmaildir, which does no network protocol work. Needs decisions first: which watcher, whether it packages on Slackware, and what the server supports. **Blocked on 174**, whose status file is the reporting channel this needs anyway |
| 185 | The message-pane bar offers Reply and Forward on a trashed message | presentation | S | **done 2026-08-29**, unreleased, with 186. The bar has a third branch keyed on the SELECTION being in a trash folder, the same predicate the menus use: Restore, Delete permanently and Empty trash replace the reply pair, and Restore alone is tinted. Added `purge`, the selection-scoped sibling of `empty_trash`, which inherits both its safeguards. Refilled from the digest as well as from the selection, since a conversation's trash-ness is not known until every path is reported. Section in the closed file. Original entry: `MainWindow::refreshMessageBarActions()` (`mainwindow.cpp:2311`) swaps the bar's message half for a DRAFT and for nothing else, so the trash view shows the two actions that make least sense there. The notes ask for Restore and Delete permanently in their place, and for Delete to move here from the main toolbar (item 186). The visibility rules already exist in `refreshTrashActions()`; what is missing is the bar consulting them |
| 186 | Delete sits on the main toolbar rather than beside Reply and Forward | presentation | XS | **done 2026-08-29**, unreleased, with 185. Moved to the message bar's ordinary branch; still in the Message and context menus. Section in the closed file. Original entry: `toolBar->addAction(... "delete")` at `mainwindow.cpp:2251`. The user places it with the message actions, so this rides with item 185 rather than being done alone: moving it before the bar is trash-aware leaves Delete in a bar that still offers Reply on trashed mail |
-| 187 | There is no Spam view beside Trash | workflow | S | open, 2026-08-29, from the notes; the shape was **corrected 2026-08-29** after looking at the real Maildir, having first been written from a wrong assumption. Spam folders DO exist on some accounts: the two whose mbsync channels use `Patterns *` have a `Spam` folder, one of them a `Junk` beside it, while the accounts whose channels list folders explicitly have none, because that list never names the provider's spam folder. So neither a pure tag generator nor a mandatory folder key like `trash` fits. See the entry |
+| 187 | There is no Spam view beside Trash | workflow | M | open, 2026-08-29, from the notes; **shape settled 2026-08-29** after two corrections and three decisions from the user. Spam works like Trash: path-based, a mandatory per-account `spam` key, and Mark spam MOVES the file. Every account can now reach a spam folder, the three Gmail ones having gained `[Gmail]/Spam` in `.mbsyncrc` this session. Grew from S to M: the move path, the origin tag and a cleanup pass are three parts, and it changes what an existing action does. See the entry |
| 188 | Does Empty trash respect the account selector? | question | XS | **answered 2026-08-29** by reading the code, no work needed. It does: `MainWindow::emptyTrash()` (`mainwindow.cpp:6567`) reads `m_accountBox->currentData()` and uses `allTrashQuery()` only for All accounts, and the confirmation names which. Recorded so the notes' question has an answer rather than sitting open |
| 189 | The message bar carries only Reply, Forward and Delete | presentation | S | **done 2026-08-29**, unreleased. Star and Archive joined the bar's ordinary branch, Archive leaving the main toolbar as Delete did. `mark_all_read` deliberately did NOT move, at the user's decision: it is the one action that ignores the selection. Item 140's toolbar test listed `archive` as a list-wide action and had to be corrected, which is the classification this item changed. Section in the closed file. Original entry: Asks for Star (`flag`) and Archive on the bar, and raises Mark all read as a question. Two of the three are selection-scoped and fit the bar's rule as it stands; **`mark_all_read` does not**, since it deliberately ignores the selection and acts on every row in the view, which is the one action in the window that does. Needs a decision from the user on that one and on whether Archive LEAVES the main toolbar the way Delete did |
@@ -1431,70 +1431,80 @@ The `spam` action has existed since the first toolbar and writes the tag
(`mainwindow.cpp:1770`, adds `spam`, removes `inbox`), so the write half is
built and the read half is missing.
-**The folder layout, measured rather than assumed.** This entry first said no
-account names a spam folder and that a tag generator was therefore the answer.
-That was wrong, and the user corrected it. What is actually on disk, described
-generically since account names are personal data:
-
-| account shape | spam folders present |
-|---|---|
-| two accounts, mbsync `Patterns *` | `Spam`; one of them also `Junk`, the other a confirmed-spam/confirmed-ham pair |
-| two accounts, explicit `Patterns` list | none |
-
-The difference is the mbsync configuration, not the provider. A channel with
-`Patterns *` receives everything the server has. The other channels name their
-folders one by one (inbox, sent, drafts, starred, trash) and simply do not name
-the spam one, which the provider does expose over IMAP. Whether to add it there
-is a decision for the user in `.mbsyncrc`, and it is NOT this item's to change.
-
-**So neither existing shape fits, and that is the whole difficulty.**
-
-- **A pure tag generator** (like `unread` or `flagged`) matches `tag:spam`,
- which finds only what this application's own Mark spam action tagged. It
- would not show a message the SERVER filed as spam, which is the majority of
- what is in those folders and the reason to want the view at all.
-- **A folder generator** (like `trash`) needs a per-account key. `trash` is
- mandatory and that is safe because every account has one; a mandatory `spam`
- key cannot be filled for the two Gmail accounts. Item 103's hazard is live
- here: a folder named in config that does not exist is CREATED by the move,
- mbsync adopts it, and under `Create Both` it propagates to the server where
- every other client sees it. A wrong or speculative default would do exactly
- that.
-
-**Approach, and it needs one decision from the user.** An OPTIONAL per-account
-`spam` key, defaulting to unset, with the generator composing the union of
-whatever is set, in the way `sent` already composes across accounts. An account
-with no spam folder contributes nothing rather than a guess. Two sub-questions
-the user has to answer, since the repository cannot:
-
-- **Does the view mean the folder, the tag, or either?** The honest answer is
- probably `path:"<account>/<spam folder>/**" or tag:spam` per account, so it shows both what
- the server filed and what the user marked. That also makes it useful on the
- Gmail accounts, where only the tag half can ever match.
-- **Which folder for the account that has both a `Spam` and a `Junk`?** The
- key holds one name. Either it takes a list, or the user picks one and the
- other stays invisible.
+**Two wrong premises were corrected before any design, and both are worth
+keeping.** This entry first said no account names a spam folder, so a tag
+generator was the only option. Wrong: the accounts synced with `Patterns *`
+had a spam folder all along. It then said the accounts with an explicit
+`Patterns` list could never have one. Also wrong, and the cause was local
+rather than remote: the provider exposes the folder over IMAP and mbsync was
+simply never asked for it. Adding it to those three channels on 2026-08-29
+took one line each, verified against `mbsync --list` rather than guessed,
+which matters because `Create Both` turns a wrong folder name into a folder
+created on the server (item 103).
+
+**So every account can now reach a spam folder, and the design is Trash's.**
+The user settled three things on 2026-08-29:
+
+- **Path-based, exactly like Trash.** Not a tag generator. A tag query finds
+ only what this application marked and misses everything the server filed,
+ which is most of what those folders hold.
+- **Mark spam MOVES the file**, as Delete does. This is a change to an
+ existing action, not only a new view, and it is the part that makes the
+ path-based view honest.
+- **`Junk` is out of scope.** One account has a `Junk` folder beside its
+ `Spam`; it is not used and the key names one folder.
+
+**Approach.** Follow item 103's implementation rather than inventing one.
+
+1. A mandatory per-account `spam` key beside `trash`, an `Account::spamQuery()`
+ beside `trashQuery()`, and `Config::allSpamQuery()` beside
+ `allTrashQuery()`.
+2. `spam` added to `kQueryGenerators` and to `builtinFilter()`, threaded like
+ Trash rather than flat, composing with the account selector through the same
+ path in `resolvedQuery()`.
+3. The `spam` action moves the file instead of only writing tags, through
+ `moveMessages()`, with an origin tag so it can come back. Restore already
+ reads `deleted-from:`; this needs the same for spam, or one shared origin
+ scheme.
+4. A cleanup pass for mail tagged `spam` that never moved, which is every
+ message the action has ever touched.
+
+**The cleanup pass has a precedent and should copy it.**
+`showStrandedDeletedMail()` (item 103) is the same problem one version earlier:
+mail tagged `deleted` whose file never left its folder. It builds
+`tag:deleted and not (<all trash folders>)`, puts it in the query bar, and
+REPORTS, moving nothing, leaving the user to select and act. Do the same with
+`tag:spam and not (<all spam folders>)`. Two details of it are load-bearing:
+an empty folder list must never be written as `not ()`, which notmuch parses
+happily and matches nothing, reporting a clean database; and it runs
+`AlreadyScoped` so the account dropdown does not narrow it and hide other
+accounts' stranded mail.
**Constraints.**
-- **Never write a default.** The key is unset unless the user sets it. A
- default of `Spam` would be right for the accounts that have one and would
- create a folder on the servers that do not.
-- **`Config::matchNothingQuery()` for an account that configures nothing**, not
- an empty string: notmuch reads an empty query as "match everything", so a
- Spam button would otherwise show the whole Maildir for an unconfigured
- account. This is the trap `resolvedQuery()` already documents for `sent`.
+- **A mandatory key breaks every existing config on upgrade**, exactly as
+ `trash` did under item 103. That needs an `### Upgrading` note in the
+ changelog, and the same treatment `trash` got: name the missing key rather
+ than failing silently.
+- **Naming a folder that does not exist reaches the server.** Item 103's
+ lesson, and the reason the three Gmail patterns were verified against
+ `mbsync --list` before being written. A default value is not safe here; the
+ key is named by the user or the account has no spam view.
+- **`Config::matchNothingQuery()` for an account with no spam folder**, never
+ an empty string: notmuch reads an empty query as "match everything", so the
+ Spam button would show the whole Maildir.
+- **The trash view's own predicate must not be confused by this.**
+ `everySelectedRowIsInATrashFolder()` decides which actions the message bar
+ and menus offer (items 185, 186). A spam folder is not a trash folder and
+ must not satisfy it, or Restore and the purges appear on spam.
+- **Mark spam removing `inbox` stays.** The tag half is still what makes the
+ message leave the Inbox view; the move is in addition to it, not instead.
- **The label is translated, the generator is not.** `spam` is stored in
`queries.json` and matched against a closed set, so it is wire format; see
the `flagged`/"Important" note in `builtinFilter()`.
- **Adding a generator changes queries.json's readable set**, so an older build
reading a file that names `spam` reports an unknown generator and KEEPS the
- row. That is the existing behaviour and needs no version bump.
-- **Mark spam already removes `inbox`** and adds `spam`; it does not move the
- file. Whether it SHOULD move it into the account's spam folder, the way
- Delete moves into trash, is a separate question this item should not decide
- quietly. Moving would make the view path-based and consistent with Trash; not
- moving keeps the current behaviour and leans on the tag half.
+ row. Existing behaviour, no version bump.
## 188. Does Empty trash respect the account selector?