diff options
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.md | 110 |
1 files changed, 91 insertions, 19 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 98a2cd7..8a8f691 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 @@ -87,7 +87,7 @@ taking that too literally. | 20 | Thread view does not match the user's mental model | presentation | L | **done** 2026-08-10, as the card list; see 53 | | 21 | Default shortcuts are not sensible enough | discoverability | S | open | | 22 | Translatability audit and i18n wiring | correctness | M | open | -| 23 | No way to save a search query from the UI | workflow | M | open | +| 23 | No way to save a search query from the UI | workflow | M | open; design given by the user 2026-08-13. Needs one decision: how a pinned query is marked in the config | | 24 | No right-click actions on the thread list | discoverability | S | **done** | | 25 | No select-all, and bulk actions are undiscoverable | workflow | S | **done** | | 26 | No way to add or remove an arbitrary tag from the UI | workflow | S | **done** | @@ -145,6 +145,7 @@ taking that too literally. | 78 | No way to build a rule from something visible in a message | workflow | M | open; wants 76 first, so the created rule lands in a form that can hold it | | 80 | A rule with many conditions squeezes the rule list to one visible row | defect | XS | **done** 2026-08-13 on `rule-builder`, unreleased. Follows item 76 | | 79 | Opening the rules dialog and saving destroys the first rule | defect | XS | **fixed on `rule-builder`** 2026-08-13, unreleased. Shipped in 0.16.0; damaged one real rule, repaired by hand | +| 81 | No way to turn a saved query into a tagging rule | workflow | S | open; split out of 23 on 2026-08-13 and depends on it. Writes to the shared rules file, so it spans this repo and `mailctl` | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -214,27 +215,61 @@ does not appear there is not translatable, whatever the source looks like. There is no way to keep a query you have just written without editing `qtmaildir.conf` by hand. -**Approach, and one real design question.** - -- A "save this query" action that names the current query bar contents and adds - it to `[queries]`. -- The user's own suggestion for presentation: **a few sensible ones as buttons, - the rest behind a menu.** Today `SavedQueryBar` shows them all, which does not - scale past a handful. - -**The design question is where the write goes.** `qtmaildir.conf` is hand-edited -and owned by the user, and item 1 established the rule that machine-written -state belongs in `uistate.conf` instead, precisely because QSettings preserves -neither comments nor key order and would quietly reformat the file on write. -Saved queries are not machine state though: they are user intent, they belong -with the hand-written ones, and splitting them across two files so the UI can -avoid touching one would be worse than either option. Decide explicitly, and -say so in the README whichever way it goes. +**Design (user, 2026-08-13).** The user described the shape they want, which is +more specific than the original sketch and settles the file question below. + +- A **Save query** button beside the search bar. It opens a dialog on the + current query bar contents, where the query gets a **name**, an **account + scope**, and whatever else is worth editing before it is kept. +- **Two tiers, presented differently.** Queries like Inbox and Unread stay + visible as buttons; user-made ones like "all messages from one correspondent" + go behind a menu. Today every entry in `[queries]` becomes a button + (`src/mainwindow.cpp:557`), which is the thing that does not scale. +- If the tiers split, **the button row moves to a row of its own**, rather than + sharing the query row with the account box, the sort dropdown and the query + bar. + +**There is no built-in default set, and that is the first thing to decide.** +Nothing in the code distinguishes a default query from a user-made one: every +entry in `[queries]` is user-written config and is rendered identically. Sent is +the lone exception and is not in `[queries]` at all, it is built from +`allSentQuery()` (`src/mainwindow.cpp:579`). So the two tiers need a mechanism +that does not exist: either a per-query `pinned` flag or a `[general]` +`pinned_queries` list naming the ones that get buttons. This is a config format +decision and it belongs to the user. + +**The file question is settled by the design.** The original entry left open +whether the write goes to `qtmaildir.conf` or `uistate.conf`. A query the user +named and scoped in a dialog is user intent, not machine state, so it goes in +`qtmaildir.conf` beside the hand-written ones; splitting them across two files +so the UI can avoid touching one would be worse than either option. **State the +cost in the README:** QSettings preserves neither comments nor key order, so the +first save from the UI reformats a hand-edited file. That is item 1's rule +applying in the opposite direction, and it is a real loss for a user who +comments their config. + +**"Save as a filter" is deliberately not part of this item.** The user raised it +as an option: the same dialog could write the query out as a tagging rule, the +reverse of item 77's Preview in list. It is left out because a rule is not a +view. Rules live in `~/.config/mailrules/rules.json`, which has two independent +implementations that agree by test rather than by shared code, and a `post-new` +hook applies them to real mail every ten minutes. Writing one is a two-repo +commitment with a live blast radius, and folding it in here would make a +presentation change carry that risk. It is item 81. **Relation to item 10.** Item 10 is postponed, but its second half proposed exactly this: "saved queries that carry their own account scope, so one action -gets there". If saved queries gain an account scope here, item 10 may be -answered as a side effect rather than needing its own work. +gets there". The 2026-08-13 design puts account scope in the save dialog +outright, so item 10's remaining complaint is answered as a side effect rather +than needing its own work. `Account::scopedQuery()` (`src/config.cpp:42`) +already composes the scope, so this is a UI question, not a query question. Do +not reopen item 10 to do it: the user postponed it and asked that the remaining +work not be proposed unprompted. + +**Constraint:** the button row is built inline in `MainWindow` +(`src/mainwindow.cpp:554-587`). There is no `SavedQueryBar` class and there +never has been, whatever an older revision of this entry said. Moving the +buttons to their own row is a change to that layout code. ## 36. `test_mainwindow` cannot reach the worker @@ -527,6 +562,43 @@ in CLAUDE.md. **Size: M.** +## 81. No way to turn a saved query into a tagging rule + +**Observed (user, 2026-08-13):** raised while describing item 23. The save-query +dialog could optionally keep the query as a tagging rule as well as a saved +query, "going the opposite route of creating a filter in the dialog and running +a preview first". Item 77 shipped that opposite route: a rule in the dialog can +be previewed in the thread list. This is the same journey backwards, from a +query the user has already run and can see the results of, to a rule that tags +it from then on. + +**Why it is its own item rather than part of 23.** A saved query is a view and +costs nothing if it is wrong. A rule is applied to real mail by a `post-new` +hook every ten minutes, and lives in `~/.config/mailrules/rules.json`, which has +two independent implementations that agree by test rather than by shared code. +Item 23 is otherwise a presentation change; folding this in would make it carry +a two-repo commitment and a live blast radius. Kept separate so 23 can ship +without it. + +**Approach.** A checkbox or a second button in the save dialog that hands the +query to `TagRules` alongside the `[queries]` write, with the tags to apply +asked for at that point: a rule needs an action, and a saved query has none. +Preserve unknown fields, per "Changing the shared rule format" in CLAUDE.md. + +**Constraints.** A stored query carries no scope: the hook supplies `tag:new` +and parenthesises the query, because `tag:new and a or b` binds as +`(tag:new and a) or b`. A saved query written for a view is frequently a +disjunction, so this is the common case here rather than an edge one, and a +rule made this way is scoped differently from the query the user just ran. Say +so in the dialog. The hook also refuses to remove `unread` or `inbox`, so a +dialog that offers to write such a rule produces one the hook will not honour; +that is the correct direction, but it has to be said in the UI rather than +failing silently. + +**Depends on item 23**, which builds the dialog this hangs off. + +**Size: S** on top of 23, and not meaningful before it. + ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering |
