docs/android-edge-to-edge-keyboard.md
How the global add-task bar is positioned over the keyboard, and the full #8508 saga. Read this before touching anything keyboard/IME-related on Android — this area has regressed repeatedly (#8295, then #8508).
⚠️ Do NOT inset the WebView for the IME based on an assumption that the system "doesn't resize on Android 15/16." Real devices (incl. a Pixel-class Android 16 phone) still resize the window for the keyboard. Insetting on top of that double-counts and squashes the WebView. See #8508 below. Any future inset must detect whether the window already resized.
The global add-task bar is position: fixed and lifted off the bottom by a CSS
variable only:
// add-task-bar.component.scss
:host-context(.isTouchOnly).global {
bottom: calc(var(--keyboard-height) + var(--s2));
}
--keyboard-height defaults to 0px. On Android/web it is set by
GlobalThemeService._initVisualViewportKeyboardTracking()
(src/app/core/theme/global-theme.service.ts) from
obscured = window.innerHeight - visualViewport.height, with a 100px floor
(KEYBOARD_THRESHOLD_PX — obscured <= 100 is treated as 0). On iOS the
Capacitor Keyboard plugin sets it.
So the bar floats above the keyboard if either the window/WebView shrinks
(then bottom: 0 is already above the IME) or the visual viewport shrinks
(then --keyboard-height lifts the bar). On the devices we have tested, the
window does shrink (the system resizes for the IME), so --keyboard-height
stays 0 and the bar sits correctly at bottom: var(--s2).
Symptom. On Android, the add-task bar (and search) showed reversed or invisible characters; some users reported "I can't see what I'm writing and Enter does nothing." Reported on v18.11.0 only (Pixel 10/Android 17, Galaxy S23 Ultra, Pixel 8a, Tab S5e/Android 15).
Root cause. v18.11.0 shipped a patch-package patch (commit 5497212b9) to
@capawesome/capacitor-android-edge-to-edge-support that always inset the
WebView by the IME height (bottomMargin = max(imeInsets.bottom, …) on every
OnApplyWindowInsetsListener callback), to fix the bar sitting behind the
keyboard under assumed enforced edge-to-edge.
On real devices the assumption is false: the system still resizes the window
for the IME even on Android 16. Measured on an Android 16 phone with the
keyboard up: window.innerHeight went 732 → 141 (and --keyboard-height
stayed 0). The patch then added another ~909px inset on top of the already
shrunk window → the WebView was squashed to a ~141px sliver with a huge blank
gap above the keyboard. That squashed layout is almost certainly the
"can't see what I'm writing" report.
Fix (this change). The patch was removed entirely. The plugin's stock
behavior — bottomMargin = keyboardVisible ? 0 : max(imeInsets.bottom, …), i.e.
no inset while the keyboard is up — lets the system handle the keyboard.
Verified on an Android 16 phone: gap gone, WebView fills the resized window,
bar sits just above the keyboard (no behind-keyboard regression).
ngModel writeValue resets the caret during composition."
REFUTED. NgModel's isPropertyUpdated guard skips writeValue while the
model equals the just-typed value, and the add-task bar never touches
value/setSelectionRange/focus mid-composition. Proven with an e2e CDP IME
probe (since removed) and the unit specs.imeBottom 909↔996)
as the suggestion strip toggles — and the system resizes the window each
time. Typing during that system resize could still disrupt composition. This
is Android's own adjustResize, not our code. If reports persist, this is the
next lead (e.g. a content-stable layout, or debouncing).@angular/* 21.2.11 → 21.2.17 bump (commit f51954f80) — the
only other IME-adjacent change in the release.--keyboard-height still lifts
the bar. That is cosmetic and likely rare, vs. the squashed layout on every
real device tested. The correct inset would be resize-detecting: only inset
when the window did not already shrink for the IME. Web-side detection already
exists — GlobalThemeService._isVisualViewportResizedForKeyboard() — so a
future native inset can reuse that logic rather than re-derive it. Validate on
the device matrix below.android.util.Log.d("SP8508", …) in
EdgeToEdge.applyInsetsInternal logging kbVisible / imeBottom /
bottomMargin / whether a relayout fired, then adb -d logcat -s SP8508.
On the web side, chrome://inspect →
{innerH: innerHeight, vvH: visualViewport.height, kb: getComputedStyle(document.documentElement).getPropertyValue('--keyboard-height')}.Do not stack a second/third keyboard-height source on top of the VisualViewport
signal (native physical-px height + a baseInnerHeight-tracking path combined as
max(obscured, nativeKeyboardHeight - layoutShrink)). That was #8295; the
sources race on separate async events, the baseline gets reset to the shrunk
innerHeight mid-animation, the double-count guard collapses, and the bar is
mispositioned. It was reverted. Fix the inset at the source, and only after
detecting whether the system already resized.
Behavior differs across devices — test the add-task bar opening the keyboard, and typing a word fast right after tapping +, on:
Type.ime() insets are unreliable herewindowOptOutEdgeToEdgeEnforcementBoth gesture-nav and 3-button-nav, light and dark. Confirm: no blank gap above the keyboard, bar visible just above the keyboard, and typed characters appear in order (not reversed).