summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md87
1 files changed, 56 insertions, 31 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 2e5e8c9..aefa6a5 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
@@ -155,7 +155,7 @@ taking that too literally.
| 86 | A right-click search can replace or narrow, but never exclude | workflow | S | **done** 2026-08-14, unreleased; see `specs/2026-08-14-exclude-from-search-design.md`. Follows 85. The `extend` bool became a `SearchMode` enum across four signatures |
| 89 | A sync moves the list under the user's hands, and the auto-sync skips rather than retries | workflow | XS | **done** 2026-08-15, unreleased. The timer half only: a skipped auto-sync re-arms instead of giving up. The list-churn half is **dropped**, not built: the user resolved it as a mental-model question, an Unread view is SUPPOSED to be volatile |
| 90 | A saved-query button clears the account selection | workflow | S | **folded into 93** 2026-08-15. Not fixed in place: the button that misbehaves stops being a saved query at all. See `specs/2026-08-15-builtin-filters-design.md` |
-| 91 | Double-clicking a thread should open it on its own | workflow | S | open, fully specified 2026-08-15 and ready to build. A drill-down to that one thread, the same result an `id:` query gives. Nothing is built for getting back: the filter buttons already are that |
+| 91 | Double-clicking a thread should open it on its own | workflow | S | open, fully specified 2026-08-15 and ready to build. The view is always the whole thread, EXPANDED; the pane shows whichever row was double-clicked, so a reply drills to its thread and not to itself. It is `recoverStaleThread()` triggered by a gesture |
| 92 | Nothing distinguishes a tag written by a rule from one the user applied | information | M | **postponed** 2026-08-15 at the user's request: "I don't see the utility, so I don't really know how to answer." Needs per-MESSAGE provenance nothing records, a two-repo format change blank on all existing mail. Reopen only if the need appears in use |
| 93 | The query buttons are whatever the user pinned, not a designed set of filters | workflow | M | **done** 2026-08-15, unreleased; see `specs/2026-08-15-builtin-filters-design.md`. Absorbs item 90. Four built-in filters composing with the account dropdown; the user's own queries unpinned, never deleted |
| 95 | A query in the overflow menu cannot be run | defect | XS | **done** 2026-08-15, unreleased. Pre-existing and not caused by 93: the entry's action owned a submenu, and Qt emits no `triggered` for those, so the connection had never fired. Surfaced because 93 moved every query into the menu |
@@ -498,42 +498,67 @@ bump either way: an ignored optional field is not a breaking change.
**Observed (user, notes):** "double clicking a message/thread should open it in
its own view (?) UX not sure."
-**Clarified by the user, 2026-08-15**, and it is NOT a second window: "I didn't
-mean open in a new window, I meant that double clicking on a message/thread in
-the left pane should open it by itself, same as searching by id and displaying
-only that message." So this is a drill-down into the existing view, the same
-result an `id:` query produces, reached with a gesture instead of by typing.
+**Specified by the user, 2026-08-15**, in three cases. The view is always the
+whole THREAD; what changes is which message the pane shows:
-That removes the constraint this entry was mostly made of. No second
-`QWebEngineView`, no second render process, nothing reparented: the pane and
-the list are the ones already on screen. Size drops from `?` to S.
+- Double-click a single message: that message in a view by itself, loaded in
+ the right pane.
+- Double-click a thread: the whole thread, expanded, in a view by itself, with
+ the FIRST message in the right pane.
+- Double-click a reply inside a thread: the whole thread, expanded, in a view by
+ itself, with THAT REPLY in the right pane.
-**Cause (verified in code):** nothing is wired to `doubleClicked` on
-`ThreadListView`, so a double-click today is two single-clicks and selects the
-row twice. The machinery a drill-down needs already exists and is exercised by
-the query bar: `SearchTerm` builds the `id:` query and `runQuery()` runs it.
+So a reply does not drill down to itself alone. It drills to its thread, with
+itself selected. That is the part an implementation is most likely to get wrong,
+because the obvious reading of "open it by itself" is an `id:` query on the
+reply, and it is not what was asked for.
-**Approach.** On `doubleClicked`, run the query naming that row: `id:` for a
-message row, `thread:` for a thread row, built through `SearchTerm` so the
-quoting rules stay in one place.
+**It is NOT a second window.** The user ruled that out explicitly: "I didn't
+mean open in a new window." No second `QWebEngineView`, no extra Chromium render
+process, nothing reparented.
-**How the user gets back: nothing is built for it.** Answered by the user,
-2026-08-15: "I didn't think of a back action, usually I'd go back to a known
-list like unread or inbox at that point." The filter buttons are already that,
-and they are one click away in every view. A Back action, a history stack and a
+**Cause (verified in code):** nothing is connected to `doubleClicked` on
+`ThreadListView`, so a double-click today is two single-clicks and selects the
+row twice.
+
+**Approach: this is `recoverStaleThread()` triggered by a gesture.** That
+function already does the entire job for the stale-thread notice: it sets
+`m_recoverThreadId` and `m_recoverMessageId`, runs a `thread:` query, expands
+the thread when the row arrives, selects the target reply once the replies land,
+and falls back to selecting the thread when the message has gone. Every case
+above maps onto it, with the recovery target set to the double-clicked row's own
+message. Reuse it rather than writing a second selection-after-query path.
+
+Two properties of that code are exactly what this item needs and are already
+commented in place: the root card IS the thread's first message and is not among
+the children, so a thread double-click is the empty-target case; and a reply
+cannot be selected until the replies exist, so the first pass selects the thread
+provisionally and refines on the next.
+
+**Expansion is not automatic and must be asked for.** Nothing in the tree
+auto-expands: `expandAll` and `setExpanded` appear nowhere in `mainwindow.cpp`,
+so a plain `thread:` query lands on ONE COLLAPSED CARD and the user would still
+have to click the expander. `recoverStaleThread()` calls `expand()` on the
+thread, which is also what asks the worker for the replies.
+
+**How the user gets back: nothing is built for it.** Answered by the user:
+"I didn't think of a back action, usually I'd go back to a known list like
+unread or inbox at that point." The filter buttons already are that and are one
+click away in every view. A Back action, a history stack and a
restore-the-previous-query scheme were all drafted and are all unnecessary.
-That collapses the only open question in this item. A drill-down is an ordinary
-query, entered by gesture instead of by typing, and it is left the way every
-other query is left.
-
-**The undo-stack note below therefore stops being a concern.** `runQuery()`
-clears the undo stack because its rows are about to be discarded, and a
-drill-down discards them exactly as a typed query does. The user already
-expects that from the query bar, so the gesture inherits a behaviour that is
-consistent rather than surprising. No filter-over-the-model alternative is
-needed, and the item stays S rather than growing into item 40's machinery.
-
+**The undo-stack cost is inherited and acceptable.** `runQuery()` clears the
+undo stack because its rows are about to be discarded, and a drill-down discards
+them exactly as a typed query does. The user already expects that from the query
+bar, so the gesture inherits consistent behaviour rather than a surprise. No
+filter-over-the-model alternative is needed, which is what would have made this
+item 40's machinery and M rather than S.
+
+**Constraint.** A double-click also delivers a single click first, which selects
+the row and arms the mark-read timer. The drill-down must not mark a message
+read that the user only passed through, and the existing single-click path is
+what arms it, so the double-click handler has to cancel it the way the
+multi-row branch of `onThreadSelected()` does.
## Deferred, unsized, or split out