Skip to content

feat(thread): WhatsApp-style reply rendering — new text only, with quoted preview of the original#44

Open
Aravinda-HWK wants to merge 1 commit into
OpenGovMail:mainfrom
Aravinda-HWK:feat/reply-quote-preview
Open

feat(thread): WhatsApp-style reply rendering — new text only, with quoted preview of the original#44
Aravinda-HWK wants to merge 1 commit into
OpenGovMail:mainfrom
Aravinda-HWK:feat/reply-quote-preview

Conversation

@Aravinda-HWK

Copy link
Copy Markdown
Collaborator

Closes #43

Summary

Replies in a conversation now read like chat: the bubble shows only the newly written text, and the message being replied to appears as a compact WhatsApp-style quoted preview at the top of the bubble — click it to jump to the original.

Also includes a server-side threading fix discovered while testing (see below) — replies sent from this app were producing malformed In-Reply-To headers.

What changed

Reply bodies show only the new content

  • Plain-text replies were already stripped (stripQuotedText); rich-HTML replies now go through a new stripQuotedHtml helper (src/nonview/email/quotedHtml.ts) that removes only known reply-quote containers: Gmail's gmail_quote, Apple Mail's blockquote[type=cite], Outlook's divRplyFwdMsg block (plus everything after it), and this app's own attribution-paragraph + blockquote
  • Conservative by design: unrecognised quoting formats are left untouched (worst case = previous behaviour), and a quote-only reply is never blanked
  • Forwards keep their quoted content — they carry no In-Reply-To, and the forwarded body is the message
  • Richness (isRichHtml) is now judged on the stripped body, so a two-line reply quoting a newsletter renders as a chat bubble instead of a full-width document card

Quoted preview chip

  • Accent bar + original sender's name + two-line snippet, rendered at the top of the reply bubble (works in plain bubbles and rich document cards, light and dark)
  • Resolved by matching the reply's In-Reply-To against the thread's Message-IDs (angle-bracket differences normalised); ThreadMessage now carries inReplyTo from the existing Message DTO — no API changes needed
  • Click / Enter / Space smooth-scrolls to the original message and pulses a highlight around it (each bubble is addressable via data-message-key)
  • If the original isn't in the loaded conversation, no chip is rendered and the reply still displays clean
  • Optimistic local replies (quick-reply bar and compose dialog) are tagged with their inReplyTo, so the chip appears the moment you hit send

Server fix: malformed outgoing threading headers

The client sends the parent's Message-ID without angle brackets (the IMAP fetch path strips them), and smtp/sender.go wrote that raw value into In-Reply-To/References. That's malformed per RFC 5322 — strict parsers (including our own read path via MsgIDList) drop the header entirely, which is why replies sent from this app never resolved their parent while Gmail-composed replies did. ensureMsgID now wraps outgoing ids in the required <…> brackets.

Note: replies sent before this fix have the malformed header baked into the stored message and will never show a preview chip; only new sends benefit.

Testing

  • pnpm typecheck / pnpm build
  • go build ./... / go vet ./... / go test ./...
  • Manually verified against a live mailbox: Gmail-composed reply shows the chip and jump-to-original; new replies sent from the app show the chip both optimistically and after refetch; forwards keep their quoted body

…nGovMail#43)

Replies in a conversation now read like chat instead of classic email:

- Reply bubbles show only the newly written content. Plain text was already
  stripped via stripQuotedText; rich-HTML replies now go through a new
  stripQuotedHtml helper that removes only known reply-quote containers
  (Gmail gmail_quote, Apple Mail blockquote[type=cite], Outlook divRplyFwdMsg
  and everything after it, and this app's own attribution + blockquote).
  Unrecognised formats are left untouched and a quote-only reply is never
  blanked. Forwards carry no In-Reply-To, so their quoted content — which is
  the message — stays intact
- Richness is judged on the stripped body, so a short reply quoting a
  newsletter renders as a chat bubble instead of a full-width document card
- The replied-to message appears as a quoted preview chip at the top of the
  bubble (accent bar, original sender's name, two-line snippet), resolved by
  matching In-Reply-To against the thread's Message-IDs with bracket
  normalisation. Clicking it (keyboard accessible) smooth-scrolls to the
  original message and pulses a highlight; no chip when the original isn't in
  the loaded conversation
- ThreadMessage carries inReplyTo from the existing Message DTO; optimistic
  local replies are tagged with it so the chip appears immediately on send

Server fix uncovered while testing: outgoing In-Reply-To/References headers
were written with the bare message-id the client sends (the IMAP fetch path
strips angle brackets). A bracket-less header is malformed per RFC 5322 and
strict parsers — including our own read path — drop it, so replies sent from
this app never resolved their parent. sender.go now wraps outgoing ids in the
required angle brackets (ensureMsgID).
@Aravinda-HWK
Aravinda-HWK requested a review from maneeshaxyz July 12, 2026 08:45
@Aravinda-HWK Aravinda-HWK self-assigned this Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Thread view: render replies WhatsApp-style — show only the new text, with the replied-to message as a quoted preview

1 participant