summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md92
1 files changed, 72 insertions, 20 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 8a8f691..5faf859 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; design given by the user 2026-08-13. Needs one decision: how a pinned query is marked in the config |
+| 23 | No way to save a search query from the UI | workflow | M | open, specified 2026-08-13. Saved queries move to `queries.json`, which carries order, `pinned` and account scope; wants a spec before building |
| 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** |
@@ -229,24 +229,58 @@ more specific than the original sketch and settles the file question below.
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.
+**There is no built-in default set.** 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 yet.
+
+**Decision (user, 2026-08-13): saved queries move out of the INI into
+`~/.config/qtmaildir/queries.json`.** The user proposed it by analogy with the
+rules file, and it is a better answer than the two the entry had considered (a
+per-query `pinned` flag inside `[queries]`, or a `[general] pinned_queries`
+list). It solves three things at once where those solved one each.
+
+- **Order.** `[queries]` is read through `childKeys()`, which returns keys
+ alphabetically and not in file order, so button order is alphabetical today
+ and cannot be chosen. `src/config.cpp:401` already records that a hand-rolled
+ parser would be needed to change it. A JSON array is ordered intrinsically,
+ so the row of buttons can be arranged.
+- **The `pinned` flag**, which decides the two tiers.
+- **Per-query account scope**, which the save dialog wants and which
+ `SavedQuery` (`src/config.h:99`) has no room for: it is `{name, query}` and
+ nothing else.
+
+**Take the shape of `rules.json`, not its machinery.** That file is JSON because
+it has TWO independent implementations that must agree, this repo and mailctl's
+`mailrules.py`; the unknown-field preservation and the `kFormatVersion`
+handshake exist to stop two programs destroying each other's writes. Queries
+have exactly one reader, so copying that wholesale would import the ceremony
+without the reason for it. What is worth carrying across is a versioned document
+read and written by one class, preserving unknown fields on the way through so a
+field added later is not stripped by an older build.
+
+**Migration: read `[queries]` once, write the JSON, and leave the INI section
+alone.** `[queries]` exists in the user's live config and in the README, so it
+cannot simply stop being read. On a run where `queries.json` is absent, the
+section is read, converted and written out; from then on `[queries]` is ignored
+entirely. **Do not strip the section:** removing it means rewriting the
+hand-edited file with QSettings, which drops comments and key order across the
+WHOLE file and is exactly the loss this decision avoids. Leaving it costs a few
+stale lines the user can delete by hand, and keeps a downgrade working. The
+README should mark it legacy. Reject the third option, reading both forever:
+two sources of truth for one thing, with no answer for which wins on a name
+collision or where the dialog writes.
+
+**This retires the entry's original open question.** It asked whether the write
+went to `qtmaildir.conf` or `uistate.conf`, and both answers were poor: the
+first machine-writes a hand-edited file, the second files user intent as window
+state. A machine-written JSON document beside the hand-written INI is the
+cleaner split, and the README caveat about losing comments goes away with it.
+The file belongs in `~/.config/`, not `~/.local/state/`: it is the user's
+intent, hand-editable, and belongs in a config backup, which a state directory
+is commonly excluded from.
**"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
@@ -271,6 +305,21 @@ work not be proposed unprompted.
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.
+**Constraint: `startup_query` names a saved query by name**, and resolves
+against the list with a case-insensitive match plus a fallback to the first
+entry (`Config::startupSavedQuery()`, `src/config.cpp:422`). It must keep
+resolving across the migration, which also means the "first entry" fallback
+stops meaning "alphabetically first" and starts meaning "first in the user's own
+order". That is an improvement rather than a regression, but it changes which
+query a config with an unmatched `startup_query` opens on, so it is a
+user-visible change and belongs in the changelog.
+
+**Constraint: the README documents `[queries]` in three places** (the
+`startup_query` note at :109, the sample config at :194, and the query-row
+description at :256), including a line explaining that buttons appear in
+alphabetical order rather than file order. That explanation is exactly what this
+change removes, so all three need revising, not just the sample.
+
## 36. `test_mainwindow` cannot reach the worker
**Observed:** twice in one session (0.8.0), a defect could not be given a
@@ -581,9 +630,12 @@ 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
+query to `TagRules` alongside the `queries.json` 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.
+Note that two separate files are then written from one dialog, `queries.json`
+here and `rules.json` shared with mailctl, so decide what happens when the
+second write fails after the first succeeded.
**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