From ae0c0ec339dba2ce9f2b27a9ce289feb4719dcea Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 15 Aug 2026 16:28:54 +0200 Subject: docs(backlog): correct item 91 to what the user actually described The entry said a double-click runs a query naming the row, an id: for a message and a thread: for a thread. That is wrong for a reply, which the user wants to drill to its THREAD with itself selected, not to itself alone. The view is always the whole thread, expanded; only which message the pane shows changes. It also assumed a thread: query would show the conversation. Nothing in the tree auto-expands, so it lands on one collapsed card and the replies still need a click. Both are already solved by recoverStaleThread(), which runs the query, expands the thread, selects the target reply when the replies arrive, and falls back to the thread when the message has gone. Item 91 is that mechanism triggered by a gesture rather than by the stale-thread notice, so the approach is to reuse it rather than write a second selection-after-query path. Recorded alongside: a double-click delivers a single click first, which arms the mark-read timer, so the handler must cancel it rather than marking a message read that the user only passed through. Co-Authored-By: Claude Opus 5 --- .../plans/2026-08-03-post-0.1.0-usability.md | 87 ++++++++++++++-------- 1 file changed, 56 insertions(+), 31 deletions(-) (limited to 'docs/superpowers/plans') 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 -- cgit v1.2.3