diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-08 10:11:51 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-08 10:11:51 +0200 |
| commit | 37c78f4839d6879144940ab3578e304f9714f839 (patch) | |
| tree | 0daea649fdac8a5b97bece2eba1a53646f302c45 /docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | |
| parent | 03f113dc0f6e8db13f600f2650e1536f4c09da21 (diff) | |
| download | qtmaildir-37c78f4839d6879144940ab3578e304f9714f839.tar.gz qtmaildir-37c78f4839d6879144940ab3578e304f9714f839.zip | |
docs(item-20): specify message rows in the thread list and plan the work
The item sat as 'open, unspecified' since 2026-08-04, recording only that the
user's mental model differed from what was built. Described now from three
screenshots plus four decisions: the left pane gains message rows, the root row
IS the thread's first message, replies indent by true reply depth, and action
scope follows the selected row kind.
No confirmation dialog, per this project's standing rule. The hazard the design
introduces is ambiguity rather than destruction, since deleted is a tag and
every mutation is invertible, so the scope is named in the status bar before and
after the action instead.
Sized L, the largest item in the backlog and the first to warrant a branch.
The plan records four API facts verified against the installed headers, each
contradicting the obvious approach: replies are unreachable from a query walk,
thread-derived messages must not be RAII-wrapped, QTreeView lacks
isRowSelected(int), and a tree numbers rows per parent.
Diffstat (limited to 'docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 85 |
1 files changed, 75 insertions, 10 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 ffdc8d5..a925cd8 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 @@ -68,7 +68,7 @@ taking that too literally. | 17 | No completion for tags in the query bar | workflow | M | **done** | | 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 | ? | open, unspecified | +| 20 | Thread view does not match the user's mental model | presentation | L | open, specified 2026-08-08, branch `item-20-message-rows` | | 21 | Default shortcuts are not sensible enough | discoverability | S | open | | 22 | Translatability audit and i18n wiring | correctness | M | open | | 23 | No way to save a search query from the UI | workflow | M | open | @@ -1149,12 +1149,43 @@ does fail it. the thread visualization was different than what was built, but that's ground for a small refactor later." -**Unspecified on purpose.** What the user pictured has not been described yet, -so there is nothing here to design against. Recorded now only so the remark is -not lost, and because item 2 touches the same surface and might otherwise be -mistaken for having addressed it. - -**What exists today**, as the starting point for that conversation: a thread is +**Specified 2026-08-08.** The user described the model with three Thunderbird +screenshots and four decisions. It is **not a message-pane redesign**: the +change is in the LEFT pane. A thread occupies one row carrying an "N replies" +affordance; expanding it reveals the replies as rows in the same list, indented +by reply depth; clicking one opens that single message in the reading pane. The +unit of selection stops being the thread and becomes the message, which is the +substance of the item and the source of its risk. + +**Decisions taken (user, 2026-08-08), each closing an option that the +screenshots alone did not settle:** + +1. **The root row IS the thread's first message**, not a thread-level summary + row. Expanding reveals only the REPLIES, so a thread of 7 shows 1 root and 6 + children, matching "6 risposte" in the screenshot. +2. **A true reply tree, indented by depth**, not a flat chronological list of + replies. notmuch supplies the structure via `notmuch_message_get_replies`. + Deep chains indenting off-screen and malformed `References` headers producing + misleading trees are accepted costs, to be capped in the view rather than + flattened in the model. +3. **Action scope follows the selected row.** A message row acts on that + message, a thread root acts on the whole thread. +4. **No confirmation dialog**, per this project's standing rule; the scope is + made visible instead, in the status bar, BEFORE the action ("1 thread + selected (7 messages)") and AFTER it ("Deleted 7 messages (whole thread)"). + The hazard this design introduces is not destruction, since `deleted` is a + tag and every mutation is already invertible on the undo stack. It is + **ambiguity**: with two kinds of row selectable, a keypress alone no longer + says whether it hit one message or seven. Naming the scope removes the + ambiguity where it exists, which a dialog on every correct action would not. + The user asked for a warning popup, was shown the rule in `CLAUDE.md`, and + chose this instead. + +**Improvement the user named but deferred:** moving from one message to the next +within a thread without returning to the list. An addition on top, not part of +this item. + +**What exists today**, as the starting point: a thread is one HTML document in one web view, messages stacked in chronological order, each with a small grey `.msg-header` carrying From and Date (`src/htmlbuilder.cpp:241`). Messages that did not match the query render as @@ -1168,9 +1199,43 @@ is why the thread is one document and why `cid:` references are namespaced per message. A refactor that splits messages into separate views has to answer that cost first. -**Next step: ask the user what they pictured** before proposing anything. Do -not design this item from the description above, which says only what is, -not what was wanted. +**What the codebase does not have yet**, verified 2026-08-08 rather than +assumed: + +| Needed | Status today | +|---|---| +| Per-message From / Subject / Date | Absent. `MessageRef` (`src/types.h:54`) carries only id, path, tags, matched | +| Reply parent, for indentation | Not carried at all. notmuch has it, the worker never asks | +| A tree-capable left pane | `ThreadListModel` is a `QAbstractTableModel`; `ThreadListView` is a `QTableView` | +| Loading ONE message into the pane | Absent. The worker exposes `loadThread` only | +| An expand affordance on the row | Nothing | + +**The two loads bearing the risk.** + +*The view port.* `ThreadListView` exists because a delegate cannot paint outside +its column, so the tag strip is painted across the full row width in +`paintEvent`. That code is written against a table. Mitigating fact, checked +rather than hoped: every geometry call it uses (`rowAt`, +`rowViewportPosition`, `rowHeight`, `columnViewportPosition`) exists on +`QTreeView` with the same semantics, so this is a port, not a rewrite. What does +NOT carry over is `isRowSelected(int)`, and the strip must not paint under +child rows the way it does under thread roots. + +*The selection semantics.* Every action path today assumes a selected row is a +thread: `applyTagsToThreads` resolves `thread:a or thread:b`, and the undo stack +and pending-edit map are thread-scoped throughout. Mitigating fact: they all +funnel through `m_model->threadAt(current.row())`, so the row-to-thread mapping +is centralised rather than smeared across call sites. + +**Size: L.** Larger than anything else in this backlog, and the first item to +warrant a feature branch (`item-20-message-rows`, 2026-08-08). It is a left-pane +model and view replacement plus a selection-scope change, not a refactor. + +**A cheaper alternative was offered and declined.** Collapsible `<details>` +blocks per message inside the existing single-document message pane would have +delivered per-message inspection at size S, touching only `htmlbuilder.cpp` and +neither fragile area, but gives no message rows in the list and no reply-tree +indentation. The user chose the full model deliberately. ## 21. Default shortcuts are not sensible enough |
