diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-23 21:00:31 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-23 21:00:31 +0200 |
| commit | a603b41771dc7c2612ca37f3d728c16717546a7e (patch) | |
| tree | dd0245a47a3240e69bc0dd94b6d11a9570d5e0fc /docs/superpowers/plans | |
| parent | 0c6d43d62cae7815096919239b64891843864896 (diff) | |
| download | qtmaildir-a603b41771dc7c2612ca37f3d728c16717546a7e.tar.gz qtmaildir-a603b41771dc7c2612ca37f3d728c16717546a7e.zip | |
docs(backlog): reconcile with the notes, items 138 to 147
The 2026-08-23 pass over the user's notes. Ten rows, eight sections: two of
the ten are duplicates of items already open and get a row saying so rather
than a second entry.
Six of the eight are the compose interface, which had never been looked at by
a human until this week. They are presentation rather than defect, and every
cause is verified in the code rather than copied from the note: one toolbar
carrying formatting, Attach and Send together (142), text buttons where an
editor uses icons (143), a checkbox label that describes a mechanism without
naming it (144), and Cc and Bcc as unconditional form rows (145).
The other two are the message actions. Forward has no button anywhere (139),
and the user's own answer to that is bigger than a missing button: Compose,
Reply and Forward belong over the message pane rather than on a toolbar that
otherwise acts on the list (140), which needs a bar that does not exist yet
(141). 139 closes inside 140 if that is built.
138 is the closest thing to a defect here: every account already configures a
drafts folder and the query row has no generator for it, so the one folder the
composer writes to is the one folder with no button.
Item 21 gains a pointer rather than a plan. The user is drafting the shortcut
table in their own notes and it is the specification this item has been
waiting for, so the entry says to read it and not to propose one. It records
what the draft settles (Ctrl+Return for Send, which closes an open question
from item 123 task 11), the two collisions its proposal creates (Ctrl+R is
restore today, Ctrl+F is coupled to Find moving to `/`), and the trap under
that last one: a `/` registered as a shortcut is dispatched before any
editable widget sees it, so it needs the event filter Return already uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q2koFevoSxTLhfexJTZWQd
Diffstat (limited to 'docs/superpowers/plans')
| -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. |
