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
Android's WebView shrinks the editor page's visual viewport to the part of
the view that is on screen and pans it to reveal the caret while typing.
The editor is an auto-height WebView inside the composer's ScrollView, so
nothing ever resets that pan: scrolled back to the top, the body is still
drawn shifted up by the pan offset and its first lines sit hidden under the
header fields until a scroll to the very bottom clamps it away.
The page now undoes every pan as it happens (scrolling the page top into
view, since window.scrollTo only moves the layout viewport) and reports the
offset as a `viewportShift` message; the composer scrolls its ScrollView by
the same amount so the caret stays visible. Android only; iOS and the other
RichTextEditor hosts keep their behaviour (hosts without the callback still
get the reset).
Reproduced and verified on a Pixel 4 (Android 13, WebView 153): before,
visualViewport.offsetTop stayed at ~18 px at the top of the composer and
cut the first line; after, it stays 0 and the caret still follows typing
above the keyboard.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>