feat(compose): rich HTML editor, multipart send, and signatures #47

Merged
JMR-dev merged 2 commits from feat-rich-compose into main 2026-07-01 16:06:22 +00:00
JMR-dev commented 2026-07-01 05:31:07 +00:00 (Migrated from github.com)

Brings rich composition to LibreMail, closing #36, #37, #38, and folding in #23. One branch/PR for the whole Composition milestone.

#36 — HTML editor + formatting toolbar (+ #23 rounded corners)

  • New pure, JVM-testable rich-text model in richtext/: RichTextContent (inline bold/italic/underline + links, block markers • / N. / > ) that serializes to a narrow, email-safe HTML subset and parses back (fromHtml is a faithful inverse of toHtml, so drafts round-trip).
  • A rich editor in ComposeScreen (RichTextBodyField) with a bold / italic / underline, bulleted + numbered lists, block-quote, and link toolbar, backed by Compose AnnotatedString. All editing ops live in the pure RichTextEditing object (unit-tested).
  • Plaintext-only stays plaintext-only: when nothing is formatted the HTML body is null and the wire format is byte-for-byte the old single text/plain — the editor feels unchanged and remains TalkBack/large-font friendly.
  • #23: compose fields and the body container now use MaterialTheme.shapes (rounded corners).

#37 — multipart/alternative send + reply/forward quoting

  • SmtpSender builds multipart/alternative (text/plain fallback + text/html), nested inside multipart/mixed when there are attachments. GraphSender sends HTML content when formatted, else plain text.
  • New HtmlToText generates a readable text/plain fallback; ReplyBuilder now quotes HTML originals as clean <blockquote>s (tags stripped first) so quoting can never corrupt markup, and returns both plain and HTML forms.
  • The HTML body persists/restores through drafts and the outbox — new nullable bodyHtml columns on drafts/outbox, Room v9 → v10 migration + exported schema.

#38 — signatures

  • New signatures table (multiple per account, one default) with repository + DAO; the migration backfills each account's existing per-account signature as its default so nothing is lost on upgrade.
  • Rich (HTML) signatures reuse the #36 editor. A Signatures management screen (list, add / edit / delete, set default) is reachable from per-account settings.
  • The account's default signature auto-inserts on new compose / reply / forward (honoring the per-account enable toggle), placed above the quoted original, and stays editable before send.

Tests

Added JVM unit tests: HTML↔plaintext serialization + round-trip, editing ops (styles/links/block markers with index remapping), HtmlToText, GreenMail assertions that a formatted send carries both a text/html and text/plain part (and mixed+alternative with attachments), Graph HTML content-type, reply/forward HTML-original quoting, signature block formatting, SignatureRepository default rules, and draft/outbox bodyHtml mapping. Existing compose/reply/repository tests updated.

Fast CI gate green locally: assembleDebug + testDebugUnitTest + lintDebug, plus ktlintCheck + detekt. Emulator E2E and any purely visual/WYSIWYG interaction checks are left to CI/manual.

🤖 Generated with Claude Code

Brings rich composition to LibreMail, closing #36, #37, #38, and folding in #23. One branch/PR for the whole Composition milestone. ## #36 — HTML editor + formatting toolbar (+ #23 rounded corners) - New **pure, JVM-testable rich-text model** in `richtext/`: `RichTextContent` (inline bold/italic/underline + links, block markers `• ` / `N. ` / `> `) that serializes to a narrow, email-safe HTML subset and parses back (`fromHtml` is a faithful inverse of `toHtml`, so drafts round-trip). - A rich editor in `ComposeScreen` (`RichTextBodyField`) with a **bold / italic / underline, bulleted + numbered lists, block-quote, and link** toolbar, backed by Compose `AnnotatedString`. All editing ops live in the pure `RichTextEditing` object (unit-tested). - **Plaintext-only stays plaintext-only**: when nothing is formatted the HTML body is `null` and the wire format is byte-for-byte the old single `text/plain` — the editor feels unchanged and remains TalkBack/large-font friendly. - **#23**: compose fields and the body container now use `MaterialTheme.shapes` (rounded corners). ## #37 — multipart/alternative send + reply/forward quoting - `SmtpSender` builds **`multipart/alternative`** (text/plain fallback + text/html), nested inside `multipart/mixed` when there are attachments. `GraphSender` sends HTML content when formatted, else plain text. - New `HtmlToText` generates a readable text/plain fallback; `ReplyBuilder` now quotes **HTML originals** as clean `<blockquote>`s (tags stripped first) so quoting can never corrupt markup, and returns both plain and HTML forms. - The HTML body **persists/restores through drafts and the outbox** — new nullable `bodyHtml` columns on `drafts`/`outbox`, Room **v9 → v10** migration + exported schema. ## #38 — signatures - New **`signatures`** table (multiple per account, one default) with repository + DAO; the migration **backfills** each account's existing per-account signature as its default so nothing is lost on upgrade. - **Rich (HTML) signatures** reuse the #36 editor. A **Signatures management screen** (list, add / edit / delete, set default) is reachable from per-account settings. - The account's **default signature auto-inserts** on new compose / reply / forward (honoring the per-account enable toggle), placed **above** the quoted original, and stays editable before send. ## Tests Added JVM unit tests: HTML↔plaintext serialization + round-trip, editing ops (styles/links/block markers with index remapping), `HtmlToText`, **GreenMail** assertions that a formatted send carries **both** a `text/html` and `text/plain` part (and mixed+alternative with attachments), Graph HTML content-type, reply/forward HTML-original quoting, signature block formatting, `SignatureRepository` default rules, and draft/outbox `bodyHtml` mapping. Existing compose/reply/repository tests updated. Fast CI gate green locally: `assembleDebug` + `testDebugUnitTest` + `lintDebug`, plus `ktlintCheck` + `detekt`. Emulator E2E and any purely visual/WYSIWYG interaction checks are left to CI/manual. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.