aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-29 12:45:39 +0200
committerDanilo M. <danix@danix.xyz>2026-08-29 12:45:39 +0200
commitca8b140de023d47ff7b34261051749ed9b254d4d (patch)
tree93e082d5991a7e5125fc5317a7d88536edd8b177 /docs/superpowers/plans
parent063be87405277aef3122c448b064241fd15f2a92 (diff)
downloadqtmaildir-ca8b140de023d47ff7b34261051749ed9b254d4d.tar.gz
qtmaildir-ca8b140de023d47ff7b34261051749ed9b254d4d.zip
feat: give the trash its own actions on the message bar
The pane's bar offered Reply and Forward on a message the user had thrown away, which are the two things a trashed message is least likely to want, while Restore and the purges lived only in menus. The bar now has a third branch, asked before the draft one: a deleted draft must come out of the trash before it can be edited. It is keyed on the SELECTION being in a trash folder, the same predicate the menu entries use, rather than on the trash VIEW, which disagree on mail reached from a search. It carries Restore, Delete permanently and Empty trash, and only Restore is tinted: the two purges are one act at two scopes and need no colour to tell them from each other, only from the one action that gives mail back. Delete moves here from the main toolbar in the same change (item 186). It acts on the displayed message, like Reply and Forward, so it belongs on the pane's bar by the rule items 139 to 141 settled for those two. It stays in the Message menu and the context menu. Delete permanently is new. It is Empty trash scoped to the selection, the same purgeMessages() call with the ids resolved from the selection rather than from a query, so it inherits both of that action's safeguards: it confirms, naming the count, and it carries no default shortcut. One combined thread:/id: query resolves a mixed selection, so a conversation and a reply selected together still ask once. The bar is refilled when the conversation digest arrives as well as on selection, since a conversation's trash-ness is not known until every path has been reported. Closes items 185 and 186. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NY6poqw199LfFaXe5BHKNe
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md72
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md48
2 files changed, 120 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
index 0972168..33bba8b 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
@@ -9670,3 +9670,75 @@ The test drives `onSyncFinished()` with the skip code after recording a real
pending edit, since `runAutoSync()` correctly declines when there is nothing to
carry and a fixture without one would arm nothing for a legitimate reason.
Mutation-checked by removing the call.
+
+## 185. The message-pane bar offers Reply and Forward on a trashed message
+
+**Observed.** In the Trash view the bar above the message pane reads Reply and
+Forward. Neither is a thing to do with a message the user threw away, and the
+two actions that are, Restore and Delete permanently, are reachable only from
+the Message menu and the context menu.
+
+**Cause.** `MainWindow::refreshMessageBarActions()` (`mainwindow.cpp:2311`)
+picks the bar's message half from exactly one question, whether the displayed
+message is a draft:
+
+- a draft gets `edit_draft`
+- everything else gets `reply` and `forward`
+
+The trash is not a third case, so a trashed message takes the `else` branch.
+The knowledge needed is already computed elsewhere:
+`refreshTrashActions()` (`mainwindow.cpp:3735`) already decides `inTrash` and
+already toggles `delete` and `restore` on it, for the menus.
+
+**Approach.** Give the bar a third branch that asks the same `inTrash`
+question, and fill it with `restore` and `empty_trash`, or with `restore` and a
+per-selection permanent delete if the user wants one that is not the whole
+trash. The notes ask for both to grey out when nothing is selected, which is
+what the existing visibility rules already do for the menu entries; the bar
+should read the same actions rather than duplicating the predicate.
+
+**Constraints.**
+
+- **One source for the predicate.** `inTrash` is computed in
+ `refreshTrashActions()`; the bar must read that rather than deriving the
+ trash-ness a second way, or the two surfaces will disagree the first time a
+ view changes.
+- **"Delete permanently" for a selection does not exist yet.** Item 118 built
+ `empty_trash` for the whole trash and deliberately gave it the project's one
+ confirmation. A per-selection permanent delete is a SECOND irreversible
+ action and inherits that rule, including the confirmation and the absence of
+ a default shortcut. Decide with the user whether the bar offers the existing
+ whole-trash action or a new selection-scoped one; they are different items of
+ work.
+- **Item 186 rides with this**, since moving Delete into a bar that still
+ offers Reply on trashed mail improves nothing.
+
+**Closed 2026-08-29**, unreleased, the two built together. See the status table in the open file for what shipped.
+
+## 186. Delete sits on the main toolbar rather than beside Reply and Forward
+
+**Observed.** The user reads Delete as a message action and expects it in the
+message pane's own bar, with Reply and Forward, not on the window's main
+toolbar.
+
+**Cause.** Placement, nothing more: `toolBar->addAction(m_actions.value("delete"))`
+at `mainwindow.cpp:2251`, beside `archive`, `mark_all_read` and `undo`.
+
+**Approach.** Remove it from the main toolbar's list and add it to the
+`messageActions` list in `refreshMessageBarActions()`. The action itself, its
+scoping and its trash-aware visibility all stay exactly as they are.
+
+**Constraints.**
+
+- **Do it with item 185, not before it.** The bar is not trash-aware yet, so
+ Delete would land next to a Reply button offered on a trashed message.
+- **The bar hides over an empty pane** (`messageview.cpp:710`), so Delete
+ disappears when no message is displayed, where the toolbar kept it visible
+ and disabled. That is a behaviour change worth confirming rather than
+ discovering.
+- **The no-duplicate-icons test covers the toolbar**, so check
+ `test_mainwindow`'s icon and menu-reachability assertions still hold after
+ the move; the action stays in the Message menu either way.
+
+**Closed 2026-08-29**, unreleased, the two built together. See the status table in the open file for what shipped.
+
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 aa56727..3ace9d2 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
@@ -258,6 +258,10 @@ taking that too literally.
| 182 | An edit made during a sync is announced twice and never says it is waiting | defect | XS | **done 2026-08-29**, unreleased, on `thread-row-identity`, found by hand. The hold branches set a deliberately NON-transient label; all three callers overwrote it a line later with the bare action, so the user was told the write had landed and then told again when it really did. `announceAction()` adds the wait to the action rather than replacing it, since that announcement is what stands in for the confirmation dialog this project rules out. Section in the closed file |
| 183 | `undoingAMarkReadRestoresOnlyWhatWasUnread` fails about 1 run in 9 under the full suite | testing | ? | open, 2026-08-29, measured. Item 176's regression test, which guards the undo that rewrote 44 messages of real mail. Nine runs on master: 4 standalone, 3 under `ctest -R mainwindow`, 3 under the FULL parallel suite, and the single failure was in the last group. Not a regression, the base commit behaves the same. Probably the same root cause as item 136 and worth solving with it |
| 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. `kQueryGenerators` (`config.cpp:62`) holds six generators and no `spam`, while the `spam` ACTION has existed since 0.2.x and writes the tag. So mail can be marked spam and never listed. A tag generator like `unread`, not a folder one like `trash`: nothing in the config names a spam folder, and adding one would make it a per-account mandatory key like `trash` |
+| 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 |
Sizes are rough: XS under an hour, S a sitting, M a session.
@@ -1413,3 +1417,47 @@ repository.**
174 or 125 better than a file does, and it adds a process that can wedge and
take mail delivery with it. What the user wants is a watcher, which is a
different thing in a different place.
+
+
+## 187. There is no Spam view beside Trash
+
+**Observed.** The user asks for a Spam view next to Trash. Mail can be marked
+spam today and there is no filter that lists it.
+
+**Cause.** `kQueryGenerators` (`config.cpp:62`) is a closed set of six:
+`unread`, `inbox`, `flagged`, `sent`, `drafts`, `trash`. There is no `spam`.
+The `spam` action has existed since the first toolbar and writes the tag
+(`mainwindow.cpp:1760`, adds `spam`, removes `inbox`), so the write half is
+built and the read half is missing.
+
+**Approach.** A TAG generator, like `unread` and `flagged`, not a folder one
+like `trash`: add `spam` to `kQueryGenerators`, return `spam` from
+`generatorTag()`, and give it a label in `builtinFilter()`. Threaded rather
+than flat, matching Trash. That is the whole change; `Config::resolvedQuery()`
+already composes a tag generator with the account selector.
+
+**Constraints.**
+
+- **Do NOT make it a folder generator.** No account config names a spam
+ folder, and adding one would be a new mandatory per-account key with the
+ item 103 hazard attached: a folder name that does not exist is CREATED and
+ propagates to the server.
+- **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.
+- **The button row is fixed and left to right.** Decide where Spam sits with
+ the user; the obvious place is beside Trash, which puts it last.
+
+## 188. Does Empty trash respect the account selector?
+
+**Answered on 2026-08-29 by reading the code; no work follows from it.** It
+does. `MainWindow::emptyTrash()` (`mainwindow.cpp:6567`) reads
+`m_accountBox->currentData()`, uses `Config::allTrashQuery()` only when that is
+empty and the account's own `trashQuery()` otherwise, and the confirmation
+dialog names the scope ("every account" or the account's display name).
+
+Recorded rather than dropped so the notes' question has an answer here, which
+is where the user will look for it.