diff options
Diffstat (limited to 'docs/superpowers')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 118 |
1 files changed, 118 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 8129704..2227cc6 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 @@ -195,6 +195,9 @@ taking that too literally. | 125 | A skipped sync leaves the spinner running for ever | defect | S | open, 2026-08-20, found by hand. `mailsync.sh` exits 75 (EX_TEMPFAIL) when another run holds the lock; the indicator never clears, and a held edit waits for a completion that never comes | +| 126 | Clicking a link in a message does nothing | defect | S | open, 2026-08-20. The interceptor blocks the request before `acceptNavigationRequest` can hand it to the browser, so the `QDesktopServices::openUrl` already in the code is never reached | +| 127 | A link's context menu offers four browser actions that cannot work | defect | XS | open, 2026-08-20. Item 100 removed the page-level actions and never saw these: they appear only on a link. Follows 126, which decides which of them should survive | + Sizes are rough: XS under an hour, S a sitting, M a session. --- @@ -1053,6 +1056,121 @@ Then Delete a message. Verified by hand on 2026-08-20; this is how it was found. **Size: S.** +## 126. Clicking a link in a message does nothing + +**Observed (user, 2026-08-20):** "if I click on a link in a mail in the message +pane, nothing happens. The only way now is to copy that link and paste it in a +browser." + +**Cause (verified in the code, and it is not the missing handler it looks +like).** `MessagePage::acceptNavigationRequest` (`src/messageview.cpp:89-109`) +already handles this correctly: a `NavigationTypeLinkClicked` is passed to +`QDesktopServices::openUrl(url)` and then refused as a navigation, so the pane +cannot follow it. That code is right and has presumably never run. + +**`RequestInterceptor` blocks the request first.** `shouldAllow()` +(`src/requestinterceptor.cpp:73-77`) denies `http` and `https` whenever +`m_allowRemote` is false, which is the default for every message and is reset +per message by `resetForNewMessage()`. The interceptor runs on the REQUEST, +before the page is asked whether to accept the navigation, so the click is +dropped at the network layer and `acceptNavigationRequest` is never consulted. +The user sees nothing at all: no error, no navigation, no browser. + +**This is the remote-content protection working as designed**, and the fix must +not weaken it. Blocking `https` is what stops a tracking pixel or a remote +stylesheet phoning home when a message is merely displayed. The bug is that a +deliberate CLICK is indistinguishable, at that layer, from a resource the +document fetched on its own. + +**Approach.** The distinction the interceptor cannot make is available one level +up: a click arrives as `ResourceTypeMainFrame` with a `NavigationTypeLinkClicked`, +while a tracking pixel arrives as `ResourceTypeImage` and never as a navigation +at all. Two shapes are worth considering, and the choice is the user's: + +- Let the main-frame navigation through the interceptor so + `acceptNavigationRequest` can see it, hand it to `QDesktopServices::openUrl`, + and still refuse the navigation. Nothing is ever fetched by the pane; the URL + only reaches the external browser. +- Or intercept the click before it becomes a request at all, which avoids + relaxing anything in the interceptor but needs the link target from the + context-menu data or a `linkHovered` cache. + +**Constraints.** + +- **`m_allowRemote` must stay false by default**, and this must not become a + reason to loosen it. Opening a link in an external browser is the user's + explicit act; fetching a resource into the pane is not. +- **The pane must still never navigate.** `acceptNavigationRequest` returning + false for a link click is what protects that, and it stays whatever else + changes. +- **A `mailto:` link is a separate question** and probably belongs to item 123, + since composing is v2. Until then it should behave like any other scheme + rather than being special-cased into silence. +- **`qtmaildir-query:` links must keep their existing path.** They are the + placeholder's own helper lines and are handled before the `openUrl` call; + a message body carrying one is already refused by the view. + +**Verification.** Not testable through the offscreen platform end to end, since +it ends in an external browser. The reachable assertion is that a link click +reaches `acceptNavigationRequest` at all, which is what fails today; the +`openUrl` call itself is one line beyond that and is better confirmed by hand. + +**Size: S.** + +## 127. A link's context menu offers four browser actions that cannot work + +**Observed (user, 2026-08-20):** right-clicking a link still offers "Open in new +tab", "open in new window", "save link", "copy link" and "select all". The user +identified them as probable survivors of an earlier removal, which is exactly +what they are. + +**Cause (verified).** Item 100 removed the page-level browser actions, and its +list is explicit (`src/messageview.cpp:689-694`): `Back`, `Forward`, `Reload`, +`SavePage`. Those four are what a standard menu offers on the PAGE. The +link-specific actions are different `WebAction` values entirely +(`QWebEnginePage::OpenLinkInNewTab`, `OpenLinkInNewWindow`, `DownloadLinkToDisk`, +`CopyLinkToClipboard`), and Chromium adds them only when the menu is raised over +a link. Item 100 was tested by right-clicking the page, so they were never in +the menu it was filtering and were never considered. + +**Two of them are dead and two are not**, which is why this is not a single +sweep: + +- `OpenLinkInNewTab` and `OpenLinkInNewWindow` cannot work at all. There are no + tabs, and a new window means a second `QWebEngineView`, which the pane + deliberately does not create (one Chromium render process per message is the + reason the pane renders a list into one view). Both are dead UI today. +- `DownloadLinkToDisk` needs a `downloadRequested` handler, which item 114 + records does not exist anywhere. It is dead for the same reason Save image is, + and should be decided WITH item 114 rather than separately. +- `CopyLinkToClipboard` works and is useful. It is currently the user's entire + workaround for item 126, by their own description. + +**Approach.** Remove `OpenLinkInNewTab` and `OpenLinkInNewWindow` by pointer, +the way `removeBrowserActions()` already does, so the removal survives +translation. Keep `CopyLinkToClipboard`. Leave `DownloadLinkToDisk` to item 114. + +**This is downstream of item 126 and should follow it.** If 126 makes a click +open the system browser, then "Open link" becomes a meaningful entry and the +right menu is one entry that does what the click does, rather than two Chromium +entries that do nothing. Removing them first and adding one back afterwards is +two changes to the same menu. + +**Constraints.** + +- **Filter by `pageAction()` POINTER, never by text.** Item 100 established + this and the reason is translation: the menu is Italian under `LANG=it_IT` + and a text match would silently stop matching. +- **Stranded separators must still be swept**, which `removeBrowserActions()` + already handles; removing two adjacent entries is exactly the case that + leaves one behind. +- **The call site cannot be tested**, per item 117: `createStandardContextMenu()` + returns nothing outside a real context-menu event. Assert on the filter + function against a menu built by hand, and state the gap rather than faking + coverage. + +**Size: XS**, and smaller still if done in the same sitting as 126. + ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering |
