aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
diff options
context:
space:
mode:
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.md85
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