aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md234
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.