diff options
Diffstat (limited to 'docs/superpowers/plans')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 144 |
1 files changed, 144 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 26464ba..1216542 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 @@ -131,6 +131,10 @@ taking that too literally. | 72 | No khard/khal integration | workflow | ? | open, unspecified; the user places it after send, so v2 at the earliest | | 73 | This backlog is past four thousand lines | maintenance | S | open | | 74 | "Searching..." keeps claiming a query is running while rows are already arriving | feedback | XS | open; cause measured 2026-08-11, the delay itself is the cold page cache and is not fixable here | +| 75 | The tagging rules window forgets its size and its column widths | persistence | S | open; follows item 44 | +| 76 | Every field in the rules dialog is free text, so a rule is easy to get wrong | workflow | M | open; design approved 2026-08-13, 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 | open; the dialog counts matches, it cannot show them | +| 78 | No way to build a rule from something visible in a message | workflow | M | open; wants 76 first, so the created rule lands in a form that can hold it | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -4722,6 +4726,146 @@ inaccurate, not because anything is expected to happen. here and should not be attempted: prefaulting 1.1 GB at startup to make one query look fast is a worse trade than the wait. +## 75. The tagging rules window forgets its size and its column widths + +**Observed.** The rules window opens at the same size every time, whatever +size it was left at, and the rule table's columns reset to their computed +widths on every open. The user also asks whether it should present as a +primary window rather than a popup. + +**Cause.** `TagRulesDialog::TagRulesDialog` calls `resize(760, 520)` +unconditionally (`src/tagrulesdialog.cpp:59`) and never reads or writes a +saved geometry; there is no `saveGeometry`/`restoreGeometry` pair anywhere in +the file, and `MainWindow` is the only class that touches `uiStatePath()` +(`src/mainwindow.cpp:141,183`). The columns reset because `reloadList()` +calls `resizeColumnToContents` for the enabled and stage columns on every +repopulate (`src/tagrulesdialog.cpp:213-214`), and `onCountsReady` does the +same for the count column (`:326`); a width the user dragged is discarded by +the next reload, not only by a close. + +**Approach.** Save `saveGeometry()` and `m_list->header()->saveState()` into +the machine-written UI state file under keys of their own, and restore both +in the constructor, keeping the current `resize` as the fallback for a first +run. Drop the unconditional `resizeColumnToContents` calls once a saved +header state exists, or the restore is undone on the first reload. + +The "popup or primary window" question is a separate decision and not a +defect: the class is a `QDialog` (`src/tagrulesdialog.h:41`), which is what +makes it modal to the main window and what puts it above it. Changing it to a +top-level window means it can be left open beside the main window and can go +behind it, and the rule edits would then need to survive that. Ask before +changing it. + +**Constraints.** UI state goes to `~/.local/state/qtmaildir/uistate.conf` via +`MainWindow::uiStatePath()`, never into the hand-edited config. A geometry +restore under the offscreen platform is what item 46 already tripped over, so +the test asserts on the saved value rather than on the resulting frame. + +**Size: S.** + +## 76. Every field in the rules dialog is free text, so a rule is easy to get wrong + +**Observed.** A rule is written by typing into four line edits, and the user +would rather choose from buttons, radios and completion, with typing reduced +to the parts that genuinely have to be typed. + +**Cause.** Not a defect, this is what shipped. The form is four bare +`QLineEdit`s for id, add, remove and query plus one `QCheckBox` +(`src/tagrulesdialog.cpp:88-96`), and none of them is attached to a +completer. The tag completion machinery already exists as `QueryCompleter` +(`src/querycompleter.h:90`) and is not used here. + +**Approach.** Designed 2026-08-13. **Read +`specs/2026-08-13-rule-builder-design.md` instead of planning from this +entry.** A `RuleQuery` value type parses and compiles the query string, and a +row builder in the dialog edits it, in the shape the user asked for after +showing Thunderbird's filter window: field and operator dropdowns, `+`/`-` +buttons, an all/any radio, and a separate "but not" block. + +Three constraints decide whether to open the spec at all. **The stored format +does not change**, so this is a single-repo change and mailctl needs no edit. +**The query string stays authoritative**, so a rule the builder cannot +represent still opens, saves and runs, in a text mode that every rule +carries. And **the string is rewritten only when the rows actually changed**, +compared against the parsed value rather than tracked with a dirty flag, +which Qt would set during programmatic population. + +Measured against the seventeen real rules: sixteen are flat, one nests an +`or` group inside an `and` chain, which is what the exclusion block exists +for. + +**Completion is the other half of this item** and is independent of the +builder; it can land before or after. It reuses `QueryCompleter` +(`src/querycompleter.h:90`) for tag names on the add and remove fields. + +**Constraints.** A multi-value field must not use `QLineEdit::setCompleter`. +CLAUDE.md records this trap twice over: the line edit overwrites the +completer's prefix with the widget's whole text, so the first tag completes +and nothing after it does. Attach with `QCompleter::setWidget` and drive the +prefix by hand. And a test that uses `setText()` passes against the bug, +because `setText` never drives a completer at all, so the keys have to be +typed. + +The hook refuses to remove `unread` or `inbox`, so a builder that offers +those as removable tags produces a rule that silently does nothing. Say so in +the UI rather than letting the hook decline it invisibly. + +**Size: M.** + +## 77. No way to see what a rule would collect, in the thread list + +**Observed.** The user wants a button that runs the rule's query in the main +window, to look at what it would collect rather than at how many. + +**Cause.** Not a defect. The dialog already counts matches, over +`requestMessageCounts` and `messageCountsReady` +(`src/notmuchworker.h:172`, `src/mainwindow.cpp:1322-1335`), which answers +"how many" and cannot answer "which". Nothing carries a query from the dialog +back to the query bar. + +**Approach.** One signal from the dialog carrying the rule's query string, +and a slot on `MainWindow` that puts it in the query bar and runs it. The +dialog stays open, since the point is to compare the two. + +**Constraints.** The stored query carries no scope on purpose: the hook +supplies `tag:new` and wraps the rule's query in parentheses. A preview must +therefore run the query WITHOUT `tag:new`, or it shows nothing at all outside +a sync window, and it must not add the parentheses silently either, since +what the user is checking is the query as stored. + +A count request must never bump `m_generation`; item 44 already had to add +`m_ruleCountGeneration` for exactly this reason (`src/mainwindow.h:657-664`). +A preview is a real query and does bump it, which is correct, but it also +means the preview discards whatever thread load was in flight. + +**Size: S.** + +## 78. No way to build a rule from something visible in a message + +**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. + +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.** + ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering |
