summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
diff options
context:
space:
mode:
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.md68
1 files changed, 68 insertions, 0 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 268dbb4..5e97337 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
@@ -146,6 +146,7 @@ taking that too literally.
| 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; depends on 23, which builds the dialog, and is excluded from its spec on purpose. Writes to the shared rules file, so it spans this repo and `mailctl` |
+| 82 | A saved query cannot be edited, unpinned or deleted from the UI | defect | S | **done** 2026-08-13 on `saved-queries`, unreleased. Right-click offers Edit, Pin/Unpin and Delete |
Sizes are rough: XS under an hour, S a sitting, M a session.
@@ -574,6 +575,73 @@ failing silently.
**Size: S** on top of 23, and not meaningful before it.
+## 82. A saved query cannot be edited, unpinned or deleted from the UI
+
+**Observed (user, 2026-08-13):** hand-testing item 23. The user saved a query,
+then asked how to unpin it, and there is no answer that does not involve either
+a text editor or retyping the whole query.
+
+**Cause:** item 23 specified saving and nothing else, and that is exactly what
+shipped. `SaveQueryDialog` opens on the contents of the query BAR, not on a
+stored entry, so the only route to changing one field of an existing query is to
+reconstruct the whole query, name it identically, and let
+`MainWindow::saveCurrentQuery()` replace it by name. There is no delete at any
+price: nothing in the UI removes an entry from `queries.json`.
+
+This is a defect rather than a missing enhancement. An action that creates
+something the UI cannot then edit or remove is incomplete, and the user hit it
+within minutes of the first hand test.
+
+**Approach.** A context menu on a saved-query button and on each **More
+queries** entry, offering Edit, Unpin (or Pin) and Delete.
+
+- **Edit** opens `SaveQueryDialog` prefilled from the STORED entry rather than
+ from the query bar. The dialog already carries every field it needs; what it
+ lacks is a constructor that takes a `SavedQuery`.
+- **Unpin** is a one-field write and does not need the dialog at all.
+- **Delete** removes the entry and rewrites the file.
+
+**Constraints.**
+
+- `saveCurrentQuery()` already merges an existing entry's `unknown` fields over
+ the dialog's fresh value, and every one of these paths must do the same or a
+ field written by a later build is dropped by an edit here.
+- Renaming through Edit is a rename, not a second entry: match on the name the
+ dialog was OPENED with, not the one it returns, or renaming silently creates a
+ duplicate and leaves the original behind.
+- Delete is destructive and the file is user config, so it is one of the few
+ places in this application that wants a confirmation. The no-confirmation rule
+ in CLAUDE.md is about tag mutations, which are undoable through the undo
+ stack; this is not on that stack and cannot be undone.
+- A test must exercise every route the way item 75's did not: the dialog's
+ Cancel goes through `done(int)` and never sends a `QCloseEvent`.
+
+**Size: S**, and it should land before the saved-query work is called done.
+
+**Done 2026-08-13.** A context menu on each button and each menu entry, with
+Edit, Move to menu / Show as a button, and Delete. Every path goes through one
+`replaceSavedQuery()`, matched on the name the dialog was OPENED with, so a
+rename replaces rather than duplicating, and merging the stored entry's unknown
+fields in one place rather than three.
+
+Two things the approach above did not anticipate. A GENERATED entry has no
+query to edit, so the dialog shows its composed query read-only rather than
+offering a field that changes nothing, and carries `generated` and `flat`
+through an edit rather than letting it decay into a plain entry holding a
+snapshot. And the overwrite notice had to learn to ignore the entry being
+edited: warning that "Inbox" already exists while editing Inbox is noise.
+
+It also exposed a defect that predated it. `rebuildSavedQueryRow()` called
+`deleteLater()` on the old row, which defers destruction to the event loop, so
+the stale row went on answering `findChild()` and every lookup after a rebuild
+saw the state from before the edit. It was already reachable from the save path.
+Fixed by reparenting the row out immediately.
+
+The unknown-fields test initially passed against the merge being deleted: it
+drove UNPIN, which copies the stored entry and therefore carries `unknown`
+along by itself. It now goes through the edit path with a replacement that has
+none, which is what the dialog actually returns.
+
## Deferred, unsized, or split out
Items noted while triaging but not part of the original list. Same numbering