aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md118
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