aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-14 17:35:31 +0200
committerDanilo M. <danix@danix.xyz>2026-08-14 17:35:31 +0200
commit9fd9f06a2e9aa1e9f9a12e82cd8bf727d5f7b968 (patch)
treee415b3f1dceb9ab90f9de605a924b6883d2127a6
parentdd8a2ba3a04bb6f4d1841a5658b581ce05d95fbd (diff)
downloadqtmaildir-9fd9f06a2e9aa1e9f9a12e82cd8bf727d5f7b968.tar.gz
qtmaildir-9fd9f06a2e9aa1e9f9a12e82cd8bf727d5f7b968.zip
docs(backlog): record item 86, excluding a value from a search
The 2026-08-14 reconciliation against the user's notes found one entry with no item here: the right-click search offers "search for this" and "add to search" but no way to add negatively. Cause verified in the code rather than copied from the note. Item 85 shipped the two operations as a single bool, built identically in messageview.cpp:566 and messagedetailsdialog.cpp:92 and branched on in mainwindow.cpp:1654, and SearchTerm has no exclusion form at all. So this is not a missing menu entry over an existing capability; there is no third state for an entry to select. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md54
1 files changed, 54 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 980aefb..15377ea 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
@@ -150,6 +150,7 @@ taking that too literally.
| 83 | A rule named with spaces is written to the file and dropped by every reader | defect | S | **done** 2026-08-14, unreleased. The name is sanitised into an id, save validates, a bad id loads for repair |
| 84 | A config problem blocks `test_mainwindow` on a modal nobody can dismiss | testing | S | open; measured 2026-08-14, `showWarnings()` calls `QMessageBox::warning` from the constructor |
| 85 | Nothing on screen can be searched for by right-clicking it | workflow | M | **done** 2026-08-14, unreleased; see `specs/2026-08-14-search-from-message-design.md`. Split from 78; rebuilt the details dialog as rows |
+| 86 | A right-click search can replace or narrow, but never exclude | workflow | S | open; follows 85. The `extend` bool has no room for a third choice, so this widens a signature rather than adding a menu entry |
Sizes are rough: XS under an hour, S a sitting, M a session.
@@ -577,6 +578,59 @@ here.
**Size: S.** The diagnosis is the expensive part and it is already done.
+## 86. A right-click search can replace or narrow, but never exclude
+
+**Observed (user, 2026-08-14):** "we've added the possibility to search clicking
+on any element in the message pane, the current available search options are to
+run the query from scratch or add to the existing query. A missing option is to
+add negatively (not)."
+
+**Cause (verified in the code, 2026-08-14).** Item 85 shipped exactly two
+operations and wired them through as a single boolean. Every surface builds the
+same pair: `MessageView::addSearchEntries` (`src/messageview.cpp:566`) adds
+"Search for this" and "Add to search" per offer, and
+`MessageDetailsDialog` (`src/messagedetailsdialog.cpp:92`) repeats that pair per
+row. Both emit `searchRequested(query, extend)`, and
+`MainWindow::runSearchFromPane` (`src/mainwindow.cpp:1654`) branches on that one
+bool: `extend ? SearchTerm::extend(m_queryEdit->text(), query) : query`.
+`SearchTerm` has `extend()` and no exclusion form at all. So the gap is not a
+missing menu entry over an existing capability; there is no third state for a
+menu entry to select, and the signature cannot express one.
+
+**Approach.** Three changes, in this order, and the first is the only design
+decision.
+
+- **Widen the signal past a bool.** A third operation makes `bool extend` wrong
+ at four call sites. An enum (replace, narrow, exclude) carried through
+ `MessageView`, `MessageDetailsDialog` and `MainWindow` is the honest shape.
+ Note that it crosses no thread boundary, so the `Q_ENUM` metatype trap in
+ `CLAUDE.md` does not apply here; these are direct connections.
+- **Add `SearchTerm::exclude(existing, addition)`**, parenthesising both sides
+ exactly as `extend()` does, as `(existing) AND NOT (addition)`. The
+ parenthesising is load-bearing for the same reason it is in `extend()`: the
+ query bar can hold a hand-written disjunction, and an unparenthesised
+ `NOT a or b` binds so that the exclusion covers only the first term. This is
+ the whole of the testable surface and it needs no widget.
+- **A third menu entry per offer**, wrapped in `tr()`.
+
+**Constraints.**
+
+- **Excluding from an empty query is not a search.** `extend()` returns the
+ addition alone when `existing` is empty; the same rule here would produce
+ `NOT (x)`, meaning "all mail except", which is a plausible thing to ask for
+ and an implausible thing to have meant by right-clicking a sender. Decide
+ deliberately: either suppress the entry when the query bar is empty, or let it
+ through. Do not let it fall out of the code by accident.
+- **notmuch reports nothing for a malformed query**, so correctness is asserted
+ against the constructed string, never against a provoked failure or a result
+ count. `SearchTerm`'s own header records this.
+- The details dialog must keep calling `accept()` BEFORE emitting, per item 85's
+ trap. A new action added to that menu inherits the same requirement, and its
+ mutation check hangs rather than fails.
+
+**Size: S.** The grammar is one function beside an existing one with existing
+tests; the spread is the signature change across three files.
+
## Deferred, unsized, or split out
Items noted while triaging but not part of the original list. Same numbering