summaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-13 18:54:35 +0200
committerDanilo M. <danix@danix.xyz>2026-08-13 18:54:35 +0200
commita198c116e14622be6ba76b78fca5d92b4b4f62bd (patch)
tree1a1661ebade3f4d083b3dcd5dbc64425314f8aa1 /docs/superpowers
parentaa722ec974c174298ea0313c988daa71ce43c775 (diff)
downloadqtmaildir-a198c116e14622be6ba76b78fca5d92b4b4f62bd.tar.gz
qtmaildir-a198c116e14622be6ba76b78fca5d92b4b4f62bd.zip
docs: move item 23's saved queries to a JSON file of their own
The user proposed managing saved queries the way the tagging rules are managed, in a JSON file rather than in the INI. It is a better answer than either option the entry had been weighing, a per-query pinned flag or a [general] pinned_queries list, because those each solved one problem and this solves three. Order is the one that could not be solved any other way. [queries] is read through childKeys(), which returns keys alphabetically rather than in file order, so the saved-query buttons appear alphabetically today and there is no way to arrange them; config.cpp already carries a comment saying a hand-rolled parser would be needed to change that. A JSON array is ordered intrinsically. On top of that the document has room for the pinned flag the two tiers need, and for the per-account scope the save dialog wants, which SavedQuery has nowhere to put: it is {name, query} and nothing else. The entry takes the shape of rules.json but explicitly not its machinery. rules.json is JSON because two independent implementations have to agree on it, this repo and mailctl's mailrules.py, and the unknown-field preservation and version handshake exist to keep them from destroying each other's writes. Queries have one reader, so only the versioned-document-with-unknown-fields part is worth carrying over. Migration reads [queries] once when queries.json is absent, writes the JSON, and leaves the INI section in place. Stripping it would mean rewriting a hand-edited file with QSettings, which drops comments and key order across the whole file and is the exact loss this decision was made to avoid; leaving it costs a few stale lines and keeps a downgrade working. Reading both forever was rejected as two sources of truth for one thing. This also retires the open question the entry had carried since 2026-08-04, where the write should go. Both of the original answers were poor, one machine-writing the user's hand-edited config and the other filing user intent as window state under ~/.local/state. A machine-written JSON document beside the hand-written INI is the cleaner split, in ~/.config so it lands in a config backup. Two constraints recorded that the build would otherwise meet late. startup_query names a saved query and must keep resolving, and its "first entry" fallback quietly changes meaning from alphabetically-first to first-in-the-user's-order, which is user-visible and belongs in the changelog. And the README documents [queries] in three places, one of which explains the alphabetical button order this change removes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers')
-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