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).
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.
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.
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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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)
richtext/:RichTextContent(inline bold/italic/underline + links, block markers•/N./>) that serializes to a narrow, email-safe HTML subset and parses back (fromHtmlis a faithful inverse oftoHtml, so drafts round-trip).ComposeScreen(RichTextBodyField) with a bold / italic / underline, bulleted + numbered lists, block-quote, and link toolbar, backed by ComposeAnnotatedString. All editing ops live in the pureRichTextEditingobject (unit-tested).nulland the wire format is byte-for-byte the old singletext/plain— the editor feels unchanged and remains TalkBack/large-font friendly.MaterialTheme.shapes(rounded corners).#37 — multipart/alternative send + reply/forward quoting
SmtpSenderbuildsmultipart/alternative(text/plain fallback + text/html), nested insidemultipart/mixedwhen there are attachments.GraphSendersends HTML content when formatted, else plain text.HtmlToTextgenerates a readable text/plain fallback;ReplyBuildernow quotes HTML originals as clean<blockquote>s (tags stripped first) so quoting can never corrupt markup, and returns both plain and HTML forms.bodyHtmlcolumns ondrafts/outbox, Room v9 → v10 migration + exported schema.#38 — signatures
signaturestable (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.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 atext/htmlandtext/plainpart (and mixed+alternative with attachments), Graph HTML content-type, reply/forward HTML-original quoting, signature block formatting,SignatureRepositorydefault rules, and draft/outboxbodyHtmlmapping. Existing compose/reply/repository tests updated.Fast CI gate green locally:
assembleDebug+testDebugUnitTest+lintDebug, plusktlintCheck+detekt. Emulator E2E and any purely visual/WYSIWYG interaction checks are left to CI/manual.🤖 Generated with Claude Code