diff options
Diffstat (limited to 'docs/superpowers/plans')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 244 |
1 files changed, 244 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 a187874..e71f354 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 @@ -122,6 +122,14 @@ taking that too literally. | 63 | No way to see sent mail, and no filter for it | workflow | M | **done** 2026-08-11; see `specs/2026-08-11-sent-mail-design.md` | | 64 | The Sync button carries a mailbox icon, not a refresh one | presentation | XS | **done** 2026-08-11 | | 65 | No full code review and optimization pass | correctness | ? | open, unspecified | +| 66 | Selecting a thread root leaves the message pane blank until a reply has been selected | defect | S | open; needs a reproduction before a fix | +| 67 | The placeholder pane counts unread, flagged and inbox, but not sent or drafts | information | XS | open | +| 68 | A forwarded subject gets no `passed` tag | workflow | S | open; no subject rule exists, measured 2026-08-11. Decision needed: display mark (XS) or write the flag (S, syncs out) | +| 69 | `passed` and `replied` read as words where every other state is a glyph | presentation | S | open; depends on 68 for what `passed` means | +| 70 | Pane icons are a private set where the main window uses the system theme | presentation | M | open | +| 71 | A toolbar action does not sync, so the edit sits until the next cron run | workflow | S | open; user decision on the delay | +| 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 | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -4231,6 +4239,242 @@ recorded. **Size: `?`, unspecified.** Do not propose a design for this; ask. +## 66. Selecting a thread root leaves the message pane blank until a reply has been selected + +**Observed (user, from the notes):** clicking the main message of a thread shows +nothing in the right pane. After expanding the thread and selecting a reply, +clicking the main message again renders it correctly. The user filed a second +observation as a possible duplicate: clicking the main message shows the whole +thread with stubs and the last two replies expanded, and after clicking a reply +and going back to the root, only the root's own message is shown. + +**Cause (partly verified, and the two halves are not the same fault).** + +The second half is not a defect at all. A thread row renders the conversation and +a message row renders one message; `onThreadSelected` (`src/mainwindow.cpp:1847`) +branches on `isMessageRow` for exactly that. What breaks the user's expectation is +that going back to the root should return to the conversation view, and it does +not always: `m_currentMessageId` is set by the message branch +(`src/mainwindow.cpp:1860`) and `onMessageLoaded` refuses to render when it is +empty (`src/mainwindow.cpp:1924`), so the two paths are ordered by which reply +lands first, not by which row is current. This is the "a queued load can outlive +the state that started it" class already recorded in CLAUDE.md. + +The first half, a blank pane on the very first click, has a candidate and only a +candidate: `onThreadSelected` returns without loading anything when the index is +not yet selected (`src/mainwindow.cpp:1804`), a guard added so that a view taking +focus does not open and mark-read a message the user never looked at. If a click +can deliver `currentRowChanged` before the selection model is updated, which +CLAUDE.md records as this signal's documented behaviour, that guard drops the +load and `onSelectionChanged` only reloads when the row CHANGED +(`src/mainwindow.cpp:1721`), which it did not. + +**Approach:** do not fix from this reading. The candidate above is exactly the +kind of plausible cause the project has been burned by twice; instrument the +running application and watch the order of `currentRowChanged`, +`selectionChanged` and the worker reply for one first click on a cold view. +`test_mainwindow` cannot reproduce it: it has no worker and never fires +`threadLoaded`. + +**Constraints:** the mark-read guard at 1804 must survive whatever is done, it is +item 35b's fix and removing it re-opens marking mail read without the user having +seen it. A fix that only re-orders the two guards will look correct and will still +be timing-dependent. + +**Size: S**, and a defect rather than an enhancement. + +## 67. The placeholder pane counts unread, flagged and inbox, but not sent or drafts + +**Observed (user, from the notes):** "stats in the blank pane should show also +drafts and sent emails." + +**Cause:** `kPlaceholderQueries` drives three counts and the labels are written +positionally against them, `%n unread`, `%n flagged`, `%n in inbox` +(`src/mainwindow.cpp:1410-1412`). The worker's `requestCounts` takes an arbitrary +list of queries and answers one count per query +(`src/notmuchworker.cpp:621-648`), so the machinery is already general; only the +list and its labels are fixed. + +**Approach:** add two entries. Sent is not `tag:sent`: item 63 established that +the sent query is composed from the configured per-account folders, and +`allSentQuery()` already builds it. Drafts has no such composition yet and the +obvious `tag:draft` should be checked against the real database before it is +shipped, since a count that always reads 0 is worse than no count. + +**Constraints:** the -1 convention is load-bearing. The worker returns -1 rather +than skipping an entry precisely because the caller pairs answers with labels by +position (`src/notmuchworker.cpp:632-634`), so a new entry must be added to the +query list and the label list at the same index. + +**Size: XS.** + +## 68. A forwarded subject gets no `passed` tag + +**Observed (user, from the notes):** "passed tag should appear when subject is +`Fwd:` and `Fw:`." Refined in session on 2026-08-11: the user had noticed +`passed` appearing on messages whose subject carried `Fwd:` and not on `Fw:`, +and asked to expand the rule to both. + +**Cause:** there is no rule to expand. `passed` is the Maildir `P` flag in the +message filename, translated into a tag by notmuch because +`maildir.synchronize_flags=true`. The flag is written by whichever client +forwarded the message, or by the server over IMAP; nothing reads a subject line +anywhere in the chain. qtmaildir only ever colours the tag +(`src/tagcolors.cpp:36-37`) and the database's `post-new` hook does not mention +it either. + +**Measured against the real database (2026-08-11):** + +| Query | Count | +|---|---| +| `tag:passed` | 6 | +| `tag:passed and subject:"Fwd:"` | 1 | +| `tag:passed and subject:"Fw:"` | 0 | +| `subject:"Fwd:" and not tag:passed` | 194 | +| `subject:"Fw:" and not tag:passed` | 28 | + +Six tagged messages in the whole database, and every one of them carries `P` in +its filename flags. The single overlap with `Fwd:` is a message that was +forwarded and whose subject was already a forward, not evidence of a rule: 194 +`Fwd:` subjects carry no tag at all. The correlation the observation rests on +does not exist. + +**Approach and the decision it needs first.** Two different features, and the +measurements above decide how far apart they are. + +*Display only.* The card shows a forwarded mark when the subject matches. Touches +no mail, changes no flag, reversible by deleting the rule. XS. + +*Write the tag.* qtmaildir sets `P` from a subject heuristic. With +`maildir.synchronize_flags=true` that flag is a filename change that mbsync +carries out to the server, on 222 existing messages, on a guess about a string. +Not cleanly undoable, and it asserts a meaning for a flag this application did +not define. Recommended against; recorded so the choice is deliberate rather than +forgotten. + +**Constraints:** localised clients use their own prefixes, and `Fwd:` can appear +inside a subject rather than at its head, so whatever matches must be anchored. +If the tag is ever written, it must not be re-applied on every sync in a way that +produces pending edits the user never made, item 28 is the record of a count +going wrong. The display-only route avoids that entirely, since it derives the +mark at paint time and stores nothing. + +**Size: S** as written, XS if it is display only. Most of it is the decision, not +the code. + +**Status:** left open deliberately on 2026-08-11. The cause is settled and the +options are costed; the user has not chosen, and no code was written. + +## 69. `passed` and `replied` read as words where every other state is a glyph + +**Observed (user, from the notes):** "tags like Passed and Replied should use +icons instead." + +**Cause:** both are ordinary tags, drawn as text chips with a built-in colour +(`src/tagcolors.cpp:36-37`) by the same `TagChip` painting helpers as every other +tag. Flagged already went the other way in item 57 and is a star, so the card +mixes one glyph state with two word states. + +**Approach:** treat this as an extension of what item 57 built rather than as new +machinery, a small map from tag name to glyph consulted before the chip painter. +Assert in `CardLayout`, not on a render: a glyph and a chip reserve different +widths, and the date's reserved width is computed from a sample string, which is +the trap item 62 hit. + +**Constraints:** depends on 68 only in that the meaning of `passed` may change +under it. The glyph must survive a card that carries neither tag without leaving +a gap, and the tag must remain readable to a user who does not know the glyph, +so keep the tooltip or the accessible name carrying the word. + +**Size: S.** + +## 70. Pane icons are a private set where the main window uses the system theme + +**Observed (user, from the notes):** "we should rework the icons used in the two +panes, leaving the main UI to use system icons." + +**Cause:** every action icon is resolved through `QIcon::fromTheme` +(`src/mainwindow.cpp:1057`), which is the system theme and is what the user wants +kept. The panes are the other half: the card and the message pane draw their own +marks, and item 57's star and item 15's paperclip arrived independently of each +other and of the theme. + +**Approach:** unspecified in shape until the user says what they pictured for the +panes, but the split they stated is clear and is the constraint worth recording +now: the toolbar and menus stay on `fromTheme`, the panes get a deliberate, +self-contained set that does not change under the user's icon theme. + +**Constraints:** an icon shipped as an asset needs to work on both light and dark +message-pane CSS, which item 12 already made theme-aware. `noTwoActionsShareAnIcon` +covers actions only and will not catch a collision between pane marks. + +**Size: M**, and it overlaps 69, which should probably be done inside it rather +than before it. + +## 71. A toolbar action does not sync, so the edit sits until the next cron run + +**Observed (user, from the notes):** "clicking one action in the toolbar should be +synced automatically (maybe after a configurable delay). EG I hit 'mark all read', +the view is updated but I still have to sync manually or wait for the cronjob." + +**Cause:** by design, and the design is recorded. Tag edits reach the notmuch +index at edit time and are held as pending until a sync carries them out to the +server, which is what the unsynced-changes indicator counts (items 18, 28, 54). +Nothing schedules that sync on the user's behalf. + +**Approach:** a debounced timer after a mutation, firing the existing sync path. +Item 49 already narrowed a sync to the accounts that actually changed, so the +automatic one is not the whole-mailbox operation it would have been before that. + +**Constraints and the decision needed.** The delay is the user's call and the +default matters: too short and every keystroke of tagging spawns an mbsync, too +long and it is indistinguishable from the cron job they already have. A sync +started this way must not fight the cron one, `SyncMonitor` watches +`/tmp/mbsync.lock` and the automatic sync has to skip rather than queue when the +lock is held. The undo stack has to survive it, which is what item 35 built. + +**Size: S** once the delay is chosen. + +## 72. No khard/khal integration + +**Observed (user, from the notes):** "investigate khard/khal integration (light +PIM, probably worthy after we add send capabilities)." + +**Cause:** not a defect. v1 is read-and-organize; there is no address book and no +calendar anywhere in the codebase. + +**Why this cannot be planned.** The user's own note places it after send, and +send is v2. What "integration" means is undecided: completing recipients from +khard when composing, showing a sender's card, or acting on an invitation. +Those are three different features. + +**Size: `?`, unspecified**, and out of scope until v2 exists. Ask before designing +anything. + +## 73. This backlog is past four thousand lines + +**Observed (user, from the notes):** "cleanup pass on the backlog in the project. +~4K lines is starting to become a problem." + +**Cause:** every item keeps its full Observed/Cause/Approach section forever, +including the fifty-odd that are done. The rule that would have prevented it +exists now, in "Adding to this document", but it was written on 2026-08-11 for +item 63 and nothing has been applied retroactively. + +**Approach:** the done items are the bulk, and their sections are history rather +than backlog. Move the closed ones out to a companion file, leaving the status +table intact and each section replaced by nothing at all, the table row already +carries the date and the outcome. Where a closed item records a trap that is +still true, that trap belongs in CLAUDE.md, which is where it would actually be +read, and several already are. + +**Constraints:** do not renumber and do not delete. The numbering is referenced +from commit messages, from CLAUDE.md, and from the specs, so a moved section has +to stay findable under its number. + +**Size: S**, and it is bookkeeping, so it competes with real work rather than +blocking it. + ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering |
