The app was locked to portrait in app.config.js and the hand-kept
AndroidManifest.xml. Android 16 ignores that lock on screens 600 dp and
wider, so tablets and foldables rotated into layouts that were never
tested. This removes the lock in both places, keeps React state across
rotation and split-screen resizes (`smallestScreenSize` joins
`configChanges`), and fixes what broke in landscape:
- the thread pager sizes its pages from its own width and snaps back to
the open message after a rotation instead of showing half a neighbour
- compose and the thread view share one keyboard tracker; a rotation
with the keyboard open closes it, since Android sends no new keyboard
frame and the old portrait one blanked the whole landscape composer
- in a short window with the keyboard up the composer hides the format
bar and tightens its header so a few lines of text stay visible
- inputs with suggestions or context around them opt out of Android's
full-screen landscape keyboard
- month views fit whole rows to the real height, keep the month across a
remount, and the month dropdown re-measures and caps its height
- the week grid's header follows the remounted list after a rotation
- the event sheet's actions stay on screen in landscape
- every RN Modal allows all orientations, or iOS forces portrait
The composer offered 8 text colours and no background colour. It now
has the webmail's 2 x 8 palette (7e0a86a6, 3957b358) for text colour,
and a background colour button with the same palette and a "Remove
background color" action, applied with the editor's `hiliteColor`.
The webmail's font-size picker is not ported: the editor has no mark
model to scope a size to, so it needs a selection-rewriting command in
the page script that can only be checked on a device.
The composer's editor page had no Content-Security-Policy, so quoting a
message loaded every remote image and tracking pixel in it, even when
the viewer had shown that message with remote content blocked.
The editor page now carries a CSP that mirrors the viewer's: nothing
loads but our inline script, styles and images, and remote images and
fonts are blocked whenever the quoted original is one the viewer would
block (untrusted sender and a policy other than "allow"). A reopened
draft that carries a quote counts as untrusted. Inline and pasted
images (`data:`, `blob:`) keep working, and the sent HTML is unchanged.
The template literal in buildEditorHtml cooked the backslash escapes in
the insertLink scheme regex (\/ and \? became bare / and ?), so the page
received /^(https?:|mailto:|tel:|sms:|/|?|#)/i — an invalid regex. The
resulting SyntaxError killed the entire inline editor script: no change
messages ever reached RN, bodyHtml stayed empty, canSend never enabled
without an attachment, and mails sent with one had empty bodies.
Regressed in 29b06a3.
Rewrite the scheme check without backslash escapes and move the page
builder into src/lib/editor-html.ts so a unit test can assert the emitted
script parses and stays backslash-free.
Verified on a Pixel 7 API 34 emulator (release build): typing now posts
change messages and the plain-text body extraction sees the content.
- Implemented a new RichTextEditor component for composing and editing rich text emails.
- Enhanced EmailThreadScreen to support downloading attachments with loading indicators.
- Added functionality to expand/collapse attachment list in EmailThreadScreen.
- Created utility functions for building initial HTML for email composition and converting HTML to plain text.
- Added tests for HTML composition and conversion utilities.