From bbf3c570215688c553fd70d8f372ae215725ca02 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 14 Aug 2026 13:40:17 +0200 Subject: docs: close item 85, searching from the message pane Five surfaces in the message pane offer a search built from what they show, replacing the query or narrowing it. The details dialog became rows along the way, which the user wanted independently of this feature. Item 78 is narrowed to the rule shortcut alone and drops to S: item 85 built the menus and item 81 the seeded dialog, so both halves already exist. Its approach text is corrected too, since it claimed the thread list holds a usable sender and notmuch_thread_get_authors returns a display summary, not an address. Three traps recorded in CLAUDE.md: a modal dialog must close before the action it asked for runs, Qt::RFC2822Date validates the weekday against the date, and every query goes through SearchTerm so five surfaces cannot grow five quoting rules. --- .../2026-08-03-post-0.1.0-usability-closed.md | 61 +++++++++++++++ .../plans/2026-08-03-post-0.1.0-usability.md | 90 +++++++++------------- 2 files changed, 97 insertions(+), 54 deletions(-) (limited to 'docs/superpowers/plans') diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md index 8cfa6b0..8ae8dff 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md @@ -4741,3 +4741,64 @@ how this was found: the suite ran past its two-minute timeout with no output. The cause is not the account at all, it is `showWarnings()` raising a modal from the MainWindow constructor for any config problem, with nothing offscreen to dismiss it. Filed as item 84. + +## 85. Nothing on screen can be searched for by right-clicking it + +**Observed (2026-08-14).** The user asked, while discussing item 78, to be able +to right-click a sender, a subject, a date, a tag chip or a piece of selected +body text and be offered a search built from it, both replacing the query bar +and narrowing what is already in it. The narrowing case decided the shape: a +query returning a thousand threads is refined by adding a second condition, and +before this that meant retyping a query the user never typed. + +**Cause.** Not a defect, unbuilt. `MessageView` had no context menu on any +surface, and `TagStrip` painted chips with no hit test and no signals. + +**Done 2026-08-14**, designed in +`specs/2026-08-14-search-from-message-design.md` and built over eight tasks. +Five surfaces offer **Search for this** and **Add to search**: the header's +subject and date, its From/To/Cc for a single-message thread, a tag chip, a +body selection, and every header per message in the details dialog. + +**What is worth keeping from it.** + +`SearchTerm` (`src/searchterm.h`) owns the whole query grammar and holds no +widget, so the quoting is tested without a painter or a web engine. That +matters because **a mis-quoted notmuch query is not an error, it matches zero**: +nothing downstream would ever report the feature being broken. `extend()` +parenthesises both sides, since `a or b AND c` binds as `a or (b AND c)` and +silently WIDENS a search the user asked to narrow. + +**`Qt::RFC2822Date` validates the weekday against the date**, so `Thu, 14 Aug +2026` parses as invalid: that day is a Friday. Two test fixtures carried a wrong +weekday, one of them pre-existing, and the failure is indistinguishable from the +trailing-comment trap `MimeParser::parseDate` exists to handle. Check a date +fixture with `date -d +%A` rather than writing one from memory. + +The Date: parse was **extracted** into `MimeParser::parseDate` rather than +rewritten, because the existing copy inside a file-local function already +carried the fix for `Qt::RFC2822Date` rejecting a string with a trailing +timezone comment. A second parser without it would have lost the date on a +large share of real mail, showing up only as a menu entry that never appears. + +**The header lists its fields rather than hit-testing them.** It is one +rich-text `QLabel` of up to four lines, and mapping a point through laid-out +rich text breaks as soon as the label wraps. From/To/Cc are offered exactly when +the header displays them, which is a single-message thread, so the menu can +never name a value the header is withholding. + +**The details dialog stopped being a `QPlainTextEdit`**, which the user had +disliked independently. Its plain-textness was a security property rather than a +style: header values come from strangers and plain text cannot interpret markup. +Every value label therefore states `Qt::PlainText` explicitly, because a +`QLabel` guesses under `Qt::AutoText`, and a test enumerates every label and +asserts the format. + +**A modal dialog must close BEFORE the search it asked for runs.** The +connection is direct, so emitting first runs the query synchronously: the model +clears and the pane blanks while the dialog is still up, holding the `m_items` +it was built from. Caught by a test whose mutation check HUNG rather than +failed, which is what a missing `accept()` does to `exec()`. + +**Item 78 stays open**, carrying the rule shortcut alone. The road from a search +to a rule already exists: save the query, then create a rule from it. 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 f562e97..980aefb 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 @@ -142,14 +142,14 @@ taking that too literally. | 75 | The tagging rules window forgets its size and its column widths | persistence | S | **done** 2026-08-13, shipped in 0.17.0. The window-kind question is left open, see the closed-items file | | 76 | Every field in the rules dialog is free text, so a rule is easy to get wrong | workflow | M | **done** 2026-08-13, shipped in 0.17.0. See `specs/2026-08-13-rule-builder-design.md` | | 77 | No way to see what a rule would collect, in the thread list | workflow | S | **done** 2026-08-13, shipped in 0.17.0 | -| 78 | No way to build a rule from something visible in a message | workflow | M | open, narrowed 2026-08-14; the search half split out as item 85, which is the road a rule is made from. Revisit once 85 has been used | +| 78 | No way to build a rule from something visible in a message | workflow | S | open, narrowed 2026-08-14; the search half shipped as item 85, which is the road a rule is made from. Now a shortcut across that road: the menus and the seeded-dialog path both exist. Use 85 first and see which values are worth promoting | | 80 | A rule with many conditions squeezes the rule list to one visible row | defect | XS | **done** 2026-08-13, shipped in 0.17.0. 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 | **done** 2026-08-14, unreleased; see `specs/2026-08-14-query-to-rule-design.md` | | 82 | A saved query cannot be edited, unpinned or deleted from the UI | defect | S | **done** 2026-08-13, shipped in 0.18.0. Right-click offers Edit, Pin/Unpin and Delete | | 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 | open; designed 2026-08-14, see `specs/2026-08-14-search-from-message-design.md`. Split from 78; rebuilds the details dialog as rows | +| 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 | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -483,26 +483,45 @@ query look fast is a worse trade than the wait. **Observed.** The user would like to select an address or another piece of a message in the main window, right-click, and be offered a rule built from it. -**Cause.** Not a defect, unbuilt. The thread list has a context menu (item -24) and the message pane is a `QWebEngineView` whose selection is inside the -render process. - -**Approach.** Start from the thread list's own context menu, where the -sender is already a value the model holds, rather than from a text selection -in the web view. A "Create rule from sender" entry that opens the rules -dialog with the query prefilled covers the case the user described and needs -no new plumbing. - -**Constraints.** JavaScript is disabled in the profile and must stay -disabled, so reading a selection out of the web view means -`QWebEnginePage::selectedText()` and nothing that injects script. Do that -part only if the sender case turns out not to be enough. +**Cause.** Not a defect, unbuilt. + +**Narrowed 2026-08-14, and most of the work is already done.** The search half +shipped as item 85, which is the road a rule is made from: search for a value, +save the query, create a rule from the saved query. What remains here is a +SHORTCUT across that road, and both of its halves now exist. + +- The menus are built and every surface already extracts its value as a + finished query (`SearchOffer`, `src/searchterm.h`). A rule entry is another + action beside the two search ones, not new plumbing. +- The seeded-dialog path exists from item 81: + `MainWindow::showTagRulesDialog(const TagRule &seed)`. A rule from a message + becomes a second caller of it, with a different seed, which is what item 81's + spec anticipated when it made the seed a whole `TagRule` rather than a query + string. + +**Approach.** Decide it after using item 85 for a while. Which values are worth +promoting straight to a rule is a usage question, and the earlier answer to it +was wrong (see below), so it is worth having the evidence first. + +**Constraints.** The original approach here said to start from the thread +list's context menu "where the sender is already a value the model holds". +**That is false and item 85 verified it.** `ThreadSummary::authors` comes from +`notmuch_thread_get_authors` and is a DISPLAY SUMMARY, reading `Alice, Bob` or +`Alice| Bob`, so a `from:` built from it matches nothing. A real address comes +from `MessageNode::from` or `ParsedMessage::from`, neither of which the thread +list carries. Any thread-list entry needs an address resolved from a message +first. + +JavaScript is disabled in the profile and must stay disabled. Item 85 reads a +body selection with `QWebEnginePage::selectedText()`, which injects no script; +reuse that rather than adding anything. The rules file is shared with mailctl, so a rule created here must go through `TagRules` and preserve unknown fields; see "Changing the shared rule format" in CLAUDE.md. -**Size: M.** +**Size: S**, down from M now that item 85 has built the menus and item 81 the +seeded dialog. ## 84. A config problem blocks `test_mainwindow` on a modal nobody can dismiss @@ -558,43 +577,6 @@ here. **Size: S.** The diagnosis is the expensive part and it is already done. -## 85. Nothing on screen can be searched for by right-clicking it - -**Observed (2026-08-14).** The user asked, while discussing item 78, to be able -to right-click a sender, a subject, a date, a tag chip or a piece of selected -body text and be offered a search built from it, both replacing the query bar -and narrowing what is already in it. The narrowing case is the one that decides -the shape: a query returning a thousand threads is refined by adding a second -condition, and today that means retyping a query the user never typed. - -**Cause.** Not a defect, unbuilt. `MessageView` has no context menu on any -surface, and `TagStrip` paints chips with no hit test and no signals. - -**Approach.** Designed 2026-08-14. **Read -`specs/2026-08-14-search-from-message-design.md` rather than planning from this -entry.** - -Three constraints decide whether to open the spec. **This is search only, and -that is the point**: item 78's rule shortcut is deliberately left out, because a -saved query can already be promoted to a rule and searching is the missing step -on that road, as well as the safe one. **The details dialog is rebuilt as rows**, -which the user wanted anyway, and its value labels must stay explicitly -`Qt::PlainText`: the current `QPlainTextEdit` is a deliberate protection against -markup in header values that come from strangers. And **Add to search -parenthesises both sides**, because the bar may hold a hand-written `or` and -`a or b AND c` binds the wrong way, silently widening a search meant to narrow. - -**Constraints.** The thread list is out of scope and the reason is worth -keeping: a row's `authors` comes from `notmuch_thread_get_authors` and is a -display summary reading `Alice, Bob`, so a `from:` built from it matches -nothing. Item 78's own approach line assumed otherwise. - -A mis-quoted query is not an error to notmuch, it matches zero, so the quoting -helper is tested against the constructed string rather than against a provoked -failure. - -**Size: M.** - ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering -- cgit v1.2.3