summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md244
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