aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-14 17:41:56 +0200
committerDanilo M. <danix@danix.xyz>2026-08-14 17:41:56 +0200
commit22467bf5dfdff58850f4f705b36c464c68bc56e2 (patch)
treec349a881fc414e58f9f0e21e555d2912d82c2553 /docs/superpowers/plans
parent9fd9f06a2e9aa1e9f9a12e82cd8bf727d5f7b968 (diff)
downloadqtmaildir-22467bf5dfdff58850f4f705b36c464c68bc56e2.tar.gz
qtmaildir-22467bf5dfdff58850f4f705b36c464c68bc56e2.zip
docs(spec): design for excluding a value from a search, item 86
Two decisions the user made, both recorded with their rejected alternatives so they are not revisited. Excluding from an empty query would mean the whole Maildir minus one value. The menu entry is greyed rather than hidden when the query bar is empty, so the feature stays visible to someone exploring a fresh window, and SearchTerm::exclude returns empty for that case as a second layer against a caller that forgets the guard. The menus cannot see the query bar, so MainWindow pushes the fact down through MessageView::setHasQuery from the textChanged lambda it already runs for the Save button. A callback was rejected as an indirection with one implementation; silently doing nothing was rejected because a live menu entry that does nothing is worse than a greyed one. The backlog entry loses its inline approach and points at the spec, carrying the three constraints that decide whether it can be picked up. 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.md41
1 files changed, 10 insertions, 31 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 15377ea..087ce95 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,7 +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 |
+| 86 | A right-click search can replace or narrow, but never exclude | workflow | S | open, specced 2026-08-14; see `specs/2026-08-14-exclude-from-search-design.md`. 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.
@@ -597,36 +597,15 @@ bool: `extend ? SearchTerm::extend(m_queryEdit->text(), query) : query`.
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.
+**Specced 2026-08-14: read
+`specs/2026-08-14-exclude-from-search-design.md` rather than planning from
+here.** Three constraints decide whether it can be picked up. The `bool extend`
+becomes a `SearchMode` enum across four signatures, which is the bulk of the
+work and touches three files. Excluding from an EMPTY query would mean the whole
+Maildir minus one value, so the entry is greyed when the query bar is empty and
+the grammar returns empty as a second layer. And correctness is asserted against
+the constructed string, never a result count, because notmuch parses `from:((((`
+cleanly and matches zero.
**Size: S.** The grammar is one function beside an existing one with existing
tests; the spread is the signature change across three files.