summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-13 18:49:14 +0200
committerDanilo M. <danix@danix.xyz>2026-08-13 18:49:14 +0200
commitaa722ec974c174298ea0313c988daa71ce43c775 (patch)
tree72c800fe869e32ccac202cd1b21fb71de5f2c013 /docs/superpowers/plans
parent2e62a0cf56a3424f6dfa2bd60bf7f9d5825a34b5 (diff)
downloadqtmaildir-aa722ec974c174298ea0313c988daa71ce43c775.tar.gz
qtmaildir-aa722ec974c174298ea0313c988daa71ce43c775.zip
docs: record the user's design for item 23, and split item 81 out of it
Item 23 said only that a query could not be saved from the UI, and left the presentation as a one-line sketch. The user described what they actually want: a Save query button beside the search bar, opening a dialog that takes a name and an account scope; saved queries split into two tiers, with a few kept visible as buttons and user-made ones behind a menu; and the button row moved onto a row of its own once it no longer holds everything. Two things came out of writing it down. There is no built-in default query set in the code at all: every entry in [queries] is user-written and renders identically, and Sent is the lone exception because it is built from allSentQuery() rather than living in [queries]. The two tiers therefore need a mechanism that does not exist yet, either a per-query pinned flag or a [general] pinned_queries list, and that config format choice is the one decision left on the item. The design also settles the question the entry had left open: a query the user named and scoped in a dialog is intent rather than machine state, so it goes in qtmaildir.conf beside the hand-written ones, at the cost of QSettings reformatting a hand-edited file on first save. The README has to say so. The save-as-a-filter half is split out as item 81. A saved query is a view and costs nothing if it is wrong; a rule is applied to real mail by the post-new hook every ten minutes and lives in the rules file that this repo and mailctl implement independently. Folding it into 23 would make a presentation change carry a two-repo commitment, so 23 can now ship without it. Item 81 records the constraints it will hit: a stored query carries no scope and the hook parenthesises it, which matters more here than usual because a query saved for a view is often a disjunction, and the hook refuses to remove unread or inbox, so the dialog must say so rather than failing silently. Item 23's relation to item 10 firms up as a result. Account scope in the save dialog answers item 10's remaining half as a side effect, so the entry now says that outright, and says not to reopen item 10 to do it: the user postponed it and asked that the rest not be proposed unprompted. Also corrects a stale reference. The entry claimed a SavedQueryBar class shows the saved queries; no such class exists or ever has, and the buttons are built inline in MainWindow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md110
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