From 5570d0e7495a42a90acf951d396c9185b0319eb9 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 27 Aug 2026 13:05:17 +0200 Subject: feat: forward an HTML message with its formatting Item 171. A forward carried only the plain-text version of the original, so formatting was lost; and an original with no plain-text part at all (30 of 342 sampled inbox messages, ~9%) forwarded as an empty quote with its content silently gone. A forward now sends ONE part chosen by the Send-as-HTML toggle: the original's markup when on, the text quote when off. Not a multipart/alternative, at the user's decision: a forward's shape is already decided by that toggle, and sending both hands the choice to the recipient's client. The toggle is honoured even for an HTML-only original, which then forwards as a text fallback. HtmlSanitiser strips remote content from the forwarded markup, checked by default with a per-forward opt-out. This is the security-critical part: the markup leaves this process and is rendered by the recipient's client, where none of MessageView's protections apply, so forwarding a tracking pixel forwards the tracking. It is an ALLOW-LIST, unlike HtmlBuilder::namespaceCids(), because a missed rewrite is a broken image while a missed strip is a beacon reaching the recipient. An HTML forward does not seed a text quote into the editor. The first build did, then subtracted it when building the HTML part, so the user could edit a quote whose edits were discarded; what the composer shows must be what gets sent. The forwarded message appears in a read-only pane beside the editor instead, a QSplitter at 60/40 with a toggle in the Format menu. A plain forward is unchanged. ComposeContextBuilder::quoteBody() renders htmlBody down to text when there is no plain part, so the plain path never emits an empty quote. Design in docs/superpowers/specs/2026-08-27-forward-html-design.md. Two tests repaired for the splitter: the 60/40 assertion reads stretch factors rather than pixels, since the offscreen platform gives the splitter no width and reports 49/49 whatever the code asks; and theComposerSplitsItsToolbarByScope looked for the body directly in the composer's column. Not yet hand-tested in this arrangement. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AtUzfNjMD8fiYfamDd3ywW --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'CHANGELOG.md') diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b09b9..062fa8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,27 @@ point at which they are stable. ### Fixed +- **Forwarding an HTML message kept its formatting.** A forward carried only + the plain-text version of the original, so tables, emphasis and layout were + flattened, and a message with no plain-text part at all (about one in eleven + of the mail measured here) forwarded as an empty quote with its content + silently gone. A forward now carries the original's own HTML when **Send as + HTML** is on, and the text version when it is off: one or the other, chosen + by that toggle, rather than both. + + On a forward that carries HTML, the message being forwarded is shown in its + own pane beside what you are writing, at a 60/40 split, with a toggle under + **Format** to close it. The editor holds your own note only, so everything + you can edit is something that gets sent. A plain-text forward is unchanged + and still quotes into the editor as before. + + **Remote content is stripped by default.** Images and styles loaded from the + internet are removed before the forward is sent, so the sender of the + original cannot learn that you forwarded it or that your recipient opened + it. A checkbox on the forward lets you keep them for a sender you trust; it + appears only when there is something to strip. Inline images that travel + inside the message itself are unaffected and still display. + - **A draft you wrote was marked unread.** Drafts were written to disk without the Maildir "seen" flag, and notmuch tags anything without it `unread`, so a draft you had just typed appeared in the Unread view. It corrected itself -- cgit v1.2.3