diff options
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 234 |
1 files changed, 233 insertions, 1 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 3fed54e..61b73c5 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 @@ -87,7 +87,7 @@ taking that too literally. | 18 | No visual cue that there are unsynced edits | feedback | S | **done** | | 19 | No prompt to sync on exit when edits are pending | behavior | S | **done** | | 20 | Thread view does not match the user's mental model | presentation | L | **done** 2026-08-10, as the card list; see 53 | -| 21 | Default shortcuts are not sensible enough | discoverability | S | open | +| 21 | Default shortcuts are not sensible enough | discoverability | S | open; **the user is drafting the table** in their own notes (`qtmaildir shortcuts and menu structure.md`), 2026-08-23. Read it first rather than proposing one. Settles `Ctrl+Return` for Send; leaves two collisions and an unfinished menu half, see the entry | | 22 | Translatability audit and i18n wiring | correctness | M | **done** 2026-08-15, unreleased; see `specs/2026-08-15-i18n-design.md`. Found eight rule-builder labels that could never be translated in any language, and twenty untranslatable warnings. Ships an Italian translation of all 355 strings | | 23 | No way to save a search query from the UI | workflow | M | **done** 2026-08-13, shipped in 0.18.0; see `specs/2026-08-13-saved-queries-design.md` | | 24 | No right-click actions on the thread list | discoverability | S | **done** | @@ -207,6 +207,16 @@ taking that too literally. | 135 | The formatting toolbar's buttons stack rather than toggle | v2 | S | open, 2026-08-21, asked for by the user during item 123 task 8 and reverted the same session. **A spec change, not a defect**: it conflicts with spec:236 ("deliberately no live toggle") and spec:187-190. Both sites need amending FIRST, and the amendment must resolve what replaces bold-then-italic, which is the gesture spec:187's preserved selection exists to serve and which a toggle makes unreachable. That question is the work; the state machine is understood and written up in the section | | 136 | `undoMovesTheMessageBack` fails about one run in six | defect | ? | open, 2026-08-21, found while running the suite during item 123 task 10. A pre-existing race in the test or in Delete's file move, NOT caused by 123: reproduced on a clean tree with the branch's work stashed out, 1 failure in 6 runs, and the failing run took 70s against a normal 25s. Unrelated to `SendDialog`. Size unknown until the race is located | | 137 | A reply to a message that arrived at two accounts can come from the wrong one | defect | S | open, 2026-08-22, found while building item 123 task 12. `ComposeContextBuilder::accountForReply()` takes `messagePaths` PLURAL to disambiguate, and nothing upstream ever gives it more than one path, so the disambiguation is inert | +| 138 | No Drafts filter beside Sent and Trash | workflow | S | open, 2026-08-23, from the notes. Verified: `kQueryGenerators` has no `drafts` entry, though every account already configures a `drafts` folder. Follows the `sent` generator exactly, which composes per-account folders rather than matching a tag | +| 139 | Forward is reachable only from the Message menu | discoverability | XS | open, 2026-08-23, from the notes. Verified: `forward` is added to `messageMenu` and to no toolbar. Compose and Reply are on the toolbar, so the third member of the set is the only one hidden | +| 140 | Compose, Reply and Forward belong over the message pane, not on the main toolbar | presentation | M | open, 2026-08-23, from the notes. The user's design: a bar of its own above the message pane carrying the three message actions, leaving the main toolbar for list-wide operations. Absorbs 139, which is the same three buttons in a worse place. See also 141 | +| 141 | The message pane has no button bar of its own | presentation | M | open, 2026-08-23, from the notes. The container 140 needs, and the home the user names for a `toggle_html` control. Sized as one item with 140 if built together | +| 142 | The composer's formatting buttons share a toolbar with Send and Attach | presentation | S | open, 2026-08-23, from the notes. Verified: one `addToolBar` carries Bold through Quote, then Attach, Remove attachment and Send. The user reads the row as a menu bar that is not one. Move the formatting half down to sit directly above the editor, beside the HTML checkbox | +| 143 | The formatting buttons are text, where every editor uses icons | presentation | XS | open, 2026-08-23, from the notes. Follows 142, and cheap once the row moves. `QIcon::fromTheme` per CLAUDE.md's chrome rule, with the text kept as the tooltip | +| 144 | "Also send a formatted copy" is prominent and does not say what it does | presentation | XS | open, 2026-08-23, from the notes. It means "send an HTML part as well as plain text", which the label never says. Secondary to writing the message, so it should read as such | +| 145 | Cc and Bcc are permanent rows on every composer | presentation | S | open, 2026-08-23, from the notes. Verified: both are unconditional `form->addRow` calls. Most messages use neither. Collapse behind a disclosure next to To:, expanded automatically when a draft or a reply already carries a value | +| 146 | The unsynced-changes count cannot be opened to see what it counts | information | S | **duplicate of 119**, recorded 2026-08-23 from the notes. Same request, and 119 already carries the blocker: one of the four things the count sums holds no message ids, so a list cannot be complete without changing how the count is kept | +| 147 | Toggle unread reads the same whichever way it will go | presentation | S | **duplicate of 99**, recorded 2026-08-23 from the notes. The notes ask for exactly what 99 describes: "Mark as read" on an unread message and the reverse. 99 already records that the label is harder than it looks, since a multi-row selection has no single direction | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -241,6 +251,45 @@ that appeared to be bound. See `KeyMap::defaultBindings()` and the query bar claims it back while focused, so a proposal that moves it must not resurrect that bug. +**The user is drafting the table, 2026-08-23.** It lives in their own notes as +`qtmaildir shortcuts and menu structure.md`, linked from the note this item +came from, and it is the specification this item was waiting for: a row per +action with the current binding, the proposed one, and an explicit "no +shortcut" column for the actions that should have none. **Read it before +starting, and do not propose a table of your own.** It is unfinished in two +known places, so it is a starting point rather than a finished spec: + +- **The menu-structure half is one line long** ("File should hold Save + message") and is where the second half of this item's work is specified. +- **The compose actions are absent from it**, because it predates them being + usable by hand. The user's position as of 2026-08-23, stated but not yet + written into their table: `Ctrl+Return` for Send is **kept**, which closes + that open question from item 123 task 11; and major actions should not go + three modifiers deep, so Reply becomes `Ctrl+R`, Reply all `Ctrl+Shift+R`, + and Forward `Ctrl+F`. + +**Two collisions that proposal creates, both to settle before building.** +`Ctrl+R` is `restore` today, and the draft's own row for it says "ok if not +needed for something else" — it now is, so Restore needs a new binding or +none. And `Ctrl+F` is Find in most applications; the draft frees it by moving +Find to `/`, so the two are coupled, and if `/` does not survive review then +Forward loses its binding with it. + +**`/` for Find needs an event filter, not a shortcut.** Qt withholds only +plain LETTERS from editable widgets, so a `/` registered as a `QAction` +shortcut is dispatched before the query bar, the tag dialog and the composer's +editor ever see it, and a user could not type a path or a URL in any of them. +This is the same trap `CLAUDE.md` records for arrow keys, and `Return` is the +worked example of the fix: claim it in `MainWindow::eventFilter` by accepting +the `ShortcutOverride`, narrowly, for the one widget that needs it. + +**Dropping a shortcut is not dropping the action.** The draft marks the five +`*_thread` actions (item 108) for removal, and the user confirmed on +2026-08-23 that this means their SHORTCUTS only. The menu entries must stay: +`everyActionIsReachableFromAMenu()` is a required rule, while item 132 made +the shortcut itself optional, so an action with no binding is now ordinary and +prints as `(unbound)` in the shortcut reference. + ## 40. No live filter over the current view **Observed (user, 2026-08-05):** "search in current view", spelled out as two @@ -1290,3 +1339,186 @@ flaky test must not be "fixed" by widening its timeout until it passes, which converts a real race into a slower green. If the race turns out to be in the production move rather than the test, this stops being a test-hygiene item and becomes a mail-safety one. + +--- + +## 138. No Drafts filter beside Sent and Trash + +**Observed.** The query row carries Unread, Inbox, Important, Sent and Trash. +There is no Drafts button, though the composer has been writing drafts to each +account's drafts folder since item 123. + +**Cause, verified 2026-08-23.** `kQueryGenerators` in `config.cpp:62-66` is a +closed set of five, and `drafts` is not among them. Every account already +carries a `drafts` key (`config.cpp:453`), read for the composer's autosave, so +the data the filter needs is configured and unused by the query row. + +**Approach.** Follow `sent`, not `inbox`. A tag query would be wrong for the +same reason it is wrong for Sent: `draft` is a Maildir flag notmuch surfaces as +a tag, but the folder is what the user means, and a message a provider marks +differently would disagree. `Config::allSentQuery()` composes the union over +every account's folder and `Account::sentQuery()` the per-account half; both +need a drafts twin. The generator string is wire format and must stay `drafts` +in queries.json whatever the button is called in a given locale. + +**Constraints.** An account with no `drafts` key contributes NOTHING rather +than an empty term, or the button shows the whole Maildir: this is +`Config::matchNothingQuery()`'s reason for existing. The hook's carve-out +(`assets/hooks/qtmaildirconf.py`) reads the same key, so the two now agree on +what a drafts folder is; they are separate readers and neither should start +importing the other. + +--- + +## 139. Forward is reachable only from the Message menu + +**Observed.** There is no Forward button anywhere in the interface. The action +exists and works; it is in the Message menu and nowhere else. + +**Cause, verified 2026-08-23.** `mainwindow.cpp:1714` registers the action and +`1763` adds it to `messageMenu`. The toolbar block at `1975-1994` adds Compose +and Reply but never Forward, so two thirds of the message-action set are +visible and the third is not. + +**Approach.** One line, if the toolbar is where it belongs. It probably is not: +item 140 records the user's own view that all three belong over the message +pane instead, which makes this item the cheap half of that one. Build 140 and +this closes with it; build this alone only if 140 is deferred. + +**Constraints.** The no-duplicate-icons rule covers any action that can reach +the toolbar, so Forward needs an icon distinct from Reply's rather than a +variant of it. + +--- + +## 140. Compose, Reply and Forward belong over the message pane + +**Observed.** The user's note: "'Write new message' and 'reply' live next to +the other icon only buttons, but they belong in a new bar on top of the message +pane, together with 'Forward'." + +**Cause.** Not a defect. The toolbar grew by accretion and now mixes two +different scopes: Sync, Archive, Delete, Mark all read and Undo act on the LIST +or on the selection, while Compose, Reply and Forward are about a message. The +main toolbar reads as the place for everything, so the distinction is invisible. + +**Approach.** A bar above the message pane carrying the three message actions, +and the main toolbar keeping the list-wide ones. Compose is arguably neither, +since it needs no message at all; the user grouped it with the other two, and +that grouping is theirs to make. It shares the container item 141 introduces. + +**Constraints.** The actions themselves do not move: they stay in +`m_actions`, keep their shortcuts, and keep their menu entries, which is what +`everyActionIsReachableFromAMenu()` asserts on. This is a second presentation +of the same `QAction`s. Absorbs item 139. + +--- + +## 141. The message pane has no button bar of its own + +**Observed.** The user asks for "a button bar in the message pane area", and +names `toggle_html` as a control that would fit it. + +**Cause.** Nothing exists to hang such a control on. The pane is a header +label, the web view, the attachment bar and the tag strip; a per-message +control has no home, which is why `toggle_html` lives in a menu. + +**Approach.** The container item 140 needs. Whether it holds only the three +message actions, only view controls like `toggle_html`, or both is the design +question, and it should be settled with the user before building: a bar that +mixes "act on this message" with "change how I am looking at it" is the same +confusion item 140 exists to remove, one level down. + +**Constraints.** `MessageView` is built inline in its own class rather than +from named widget classes, per CLAUDE.md, and this should not become the +exception. Size assumes 140 and 141 are built together; separately they are +each S and the seam between them is wasted work. + +--- + +## 142. The composer's formatting buttons share a toolbar with Send and Attach + +**Observed.** The user reads the composer's top row as a menu bar that is not +one, and asks for the formatting controls to move down beside the HTML +checkbox, directly above the editor. + +**Cause, verified 2026-08-23.** `composewindow.cpp:326-397` builds ONE +`addToolBar`, which carries Bold, Italic, Code, the heading and list actions, +Link and Quote, then a separator, then Attach, Remove attachment and Send. +Three different scopes in one row: text formatting, message composition, and +the terminal action. + +**Approach.** Split it. The formatting half moves to a row directly above the +editor, where the text it formats is; Attach, Remove attachment and Send stay +in the window's own toolbar. The HTML checkbox already sits under the editor +and is the anchor the user names. + +**Constraints.** `setInputsEnabled()` disables `m_formatToolbar` wholesale +during a send (`composewindow.cpp:692`), so a split needs both halves disabled, +and a test for the send path that asserts on only one of them would pass +against a live Attach button during a send. + +--- + +## 143. The formatting buttons are text where every editor uses icons + +**Observed.** The user asks for icon-only formatting buttons, "like any other +text editor". + +**Cause.** They were built as text actions, and the composer's toolbar has no +icons at all. + +**Approach.** `QIcon::fromTheme` per CLAUDE.md's rule that chrome is the +system's, keeping the current text as the tooltip so nothing becomes +unnameable. Cheap once item 142 has moved the row, and awkward before, since +the same row would then mix icon-only formatting with text Send and Attach. + +**Constraints.** `format-text-bold` and its siblings are standard freedesktop +names, but a theme may not carry all of them; an action with no icon must fall +back to its text rather than rendering as an empty button. Icon-only is also +the state where the tooltip stops being decoration, so every one needs to be +right. + +--- + +## 144. "Also send a formatted copy" is prominent and says nothing + +**Observed.** The user's note: "I suppose it means 'format/send as html', but +that flag is secondary, doesn't need to be so prominent." + +**Cause, verified 2026-08-23.** `composewindow.cpp:265`. The label describes a +mechanism ("a formatted copy") without naming it, so the reader has to infer +that "formatted" means HTML and that "copy" means an additional MIME part +rather than a second message. + +**Approach.** Two independent halves, and they can ship separately. Say what it +does: something closer to "Send an HTML version as well". And demote it: it is +a per-message override of a config default (`[compose] send_html`), which is +secondary to writing the message. + +**Constraints.** The string is translated, so changing it makes the Italian +entry stale; `ctest -R translations` fails on an untranslated string, which is +the intended safety net rather than an obstacle. Nothing matches on this text, +so CLAUDE.md's "translating a string something matches on" trap does not apply. + +--- + +## 145. Cc and Bcc are permanent rows on every composer + +**Observed.** The user asks for them hidden behind a disclosure next to the To: +field. + +**Cause, verified 2026-08-23.** `composewindow.cpp:251` and `255` add both as +unconditional form rows. Most messages address neither, so two of the four +header rows are usually empty. + +**Approach.** A disclosure beside To: that reveals both together. They expand +automatically, and stay expanded, whenever either already carries a value: a +reply that carries Cc, or a reopened draft, must not hide a recipient the +message is actually addressed to. + +**Constraints.** That auto-expansion is the load-bearing half. A hidden field +holding an address is a message going somewhere the sender cannot see, which is +worse than the clutter this removes. The seeding runs before `buildUi()`'s +`markDirty()` connections per the constructor's ordering comment, so whatever +decides the initial state has to read the seeded values rather than the widgets. |
