docs/plans/2026-06-22-android-keyboard-and-systembars-handover.md
Date: 2026-06-22
Branch: claude/android-task-bar-keyboard-overlap-jzd814 (PR #8528), worktree
.worktrees/feat/pr-8528-c391fc
Author of handover: prior session (Claude)
CapacitorMainActivity that gives the WebView an explicit layout height while
the IME is up. Keyboard verified fixed on-device; one final visual check pending
(no white navbar gap). Recommend merging this as the fix.@capawesome/capacitor-android-edge-to-edge-support plugin to Capacitor 8's
built-in SystemBars. This is the right long-term direction but is a
re-architecture, not a drop-in — do it as its own PR, not bundled with
#8528. Detailed plan + gotchas below.File: android/app/src/main/java/com/superproductivity/superproductivity/CapacitorMainActivity.kt
Method: adjustWebViewHeightForKeyboardBelowApi30(rect, isKeyboardOpen), called
from the existing keyboard OnGlobalLayoutListener. Field
webViewLayoutHeightDefault captures the resting height at startup.
How it works:
layout_height = rect.bottom − webViewTopOnScreen
(keyboard top, from getWindowVisibleDisplayFrame, reliable on API 28). Shrinks
the web layout viewport so the existing CSS lifts the bar above the keyboard.webViewLayoutHeightDefault (e.g. MATCH_PARENT).Why height (not margin / padding / listener takeover) — see §3.
Before merge:
Log.d("SUPKeyboard", ...) in adjustWebViewHeightForKeyboardBelowApi30.fix(android): ... (currently docs(android): ...; squash subject matters).docs/android-edge-to-edge-keyboard.md (#8508 follow-up section).The @capawesome edge-to-edge plugin (EdgeToEdge.applyInsetsInternal) sets the
WebView bottomMargin = keyboardVisible ? 0 : max(imeInsets.bottom, navbar).
On this device isVisible(ime()) returns true, so it zeroes the bottom margin
while the keyboard is up — expecting the system to resize the window. But under
enforced edge-to-edge (targetSdk 36, windowOptOutEdgeToEdgeEnforcement only opts
out the enforcement, the plugin still calls setDecorFitsSystemWindows(false))
the window does not resize on API < 30, so the WebView keeps full height and the
position: fixed bar is behind the keyboard.
Logcat proof (API 28, screen 2400, keyboard top rect.bottom=1533): margin
alternated 0 (plugin) ↔ 867 (our correction) every frame — the flicker.
margin += delta, clamp keypadHeight):
flicker + over-lift (clamp = navbar + keypadHeight, a white gap).margin = keypadHeight, idempotent): still flickers
— the plugin rewrites bottomMargin=0 on every inset dispatch (two writers).OnApplyWindowInsetsListener (single-writer override):
fixed the keyboard, but the plugin's updateColorOverlays then never ran → the
status/nav color overlays weren't painted → white navbar gap.Key lesson: the plugin owns webView.bottomMargin and its color overlays are
coupled to its inset listener — don't fight either.
@capawesome/capacitor-android-edge-to-edge-support) pinned 8.0.8;
Capacitor ^8.3.4.capawesome-team/capacitor-plugins issues
#845 (API 29 collapse), #490, #596, #725 (the white gap), #819, #812; #847
open (Android 15+).ionic-team/capacitor#8466
("insetsHandling 'disable' … breaks WebView on API 29 with keyboard"), fixed for
the built-in SystemBars by core PR #8481 (merged).⚠️ Correction (added later): Capacitor's built-in
SystemBarseffectively no-ops below WebView 140 / API 35, and this app supports WebView 107+. A naive@capawesome→SystemBarsswap therefore regresses API < 35 / WebView < 140 devices (the exact old-device class this whole bug is about). The migration must keep a fallback for that band (or stay on the plugin there). Treat §5c below as the shape of the work, gated behind this WebView/API check. See the corrected migration plan if present.
SystemBars (in @capacitor/core 8.3.4) exposes only setStyle (light/dark
content), show, hide, setAnimation — no setStatusBarColor /
setNavigationBarColor. The modern edge-to-edge model is transparent system
bars with the web content drawn behind them, padded via env(safe-area-inset-*).
The app currently does the opposite on purpose: @capawesome paints opaque color
overlays behind the bars, and config sets SystemBars.insetsHandling: 'disable' +
windowOptOutEdgeToEdgeEnforcement=true so the plugin owns insets. Migrating
reverses these deliberate choices.
capacitor.config.ts: SystemBars.insetsHandling: 'disable',
EdgeToEdge.{statusBarColor,navigationBarColor}: '#131314',
Keyboard.resizeOnFullScreen: false, StatusBar.overlaysWebView: true (iOS),
android.includePlugins[...] includes the @capawesome edge-to-edge plugin.android/app/src/main/res/values-v35/styles.xml:
android:windowOptOutEdgeToEdgeEnforcement = true.src/app/core/theme/global-theme.service.ts:
StatusBar.setStyle({ Dark|Light })EdgeToEdge.setStatusBarColor / setNavigationBarColor (theme bg) — needs a
replacement; SystemBars has none.NavigationBar plugin (setColor + setSystemBarsAppearance)
— still drives nav-bar icon/pill appearance; may be the place to keep bar color._initSafeAreaInsets() + _patchCdkViewportForSafeArea() (via
capacitor-plugin-safe-area) — becomes MORE important under transparent bars.CapacitorMainActivity.kt: keyboard JS-interface + the new height workaround;
references bridge.webView.StartupOverlayManager.kt: reads the plugin-applied WebView bottom inset
(webViewBottomInset) from WebView geometry to align the native startup overlay —
coupled to the plugin's insets; will need rework.EdgeToEdge plugin config; drop the plugin from
android.includePlugins; remove the dep from package.json; npx cap sync.
Decide SystemBars.insetsHandling: 'css' (expose env(safe-area-inset-*),
web pads itself) is the likely target — verify the core #8481 behavior.windowOptOutEdgeToEdgeEnforcement (likely remove so
Android 15+ enforces edge-to-edge natively; verify status-bar/cutout layout).EdgeToEdge.set*BarColor with either (a) transparent bars +
web background showing through (preferred, true edge-to-edge), or (b) the app's
custom NavigationBar plugin for the nav bar + SystemBars.setStyle for content
style. Status-bar color under edge-to-edge = the web content behind it._initSafeAreaInsets / CDK patch cover all surfaces now
that content draws behind the bars (bottom nav, add-task bar, dialogs, menus).insetsHandling core-handled (#8481) the
bar-behind-keyboard bug may be resolved by core; if not, keep the
adjustWebViewHeightForKeyboardBelowApi30 workaround (it's plugin-agnostic — it
only reads geometry — so it should still work).EdgeToEdge import in global-theme.service.ts, etc.).capacitor-plugin-safe-area + the CDK overlay patch must cover every floating
surface or content slides under the bars.StatusBar.overlaysWebView, ios.contentInset/backgroundColor) is
separate — verify the migration doesn't disturb it.API 28/29 (no edge-to-edge resize), API 30–34, API 35+ (enforced edge-to-edge); light + dark theme; gesture + 3-button nav; device with a display cutout; keyboard open/close on each; rotation. Watch: bar colors, content behind bars, add-task bar vs keyboard, startup overlay alignment.
ionic-team/capacitor#8466, core #8481, plugin #848.android/app/src/main/java/com/superproductivity/superproductivity/CapacitorMainActivity.kt (keyboard workaround)android/app/src/main/java/com/superproductivity/superproductivity/widget/StartupOverlayManager.kt (inset coupling)src/app/core/theme/global-theme.service.ts (EdgeToEdge color calls, safe-area, NavigationBar)capacitor.config.ts (plugin config, insetsHandling, includePlugins)android/app/src/main/res/values-v35/styles.xml (edge-to-edge opt-out)docs/android-edge-to-edge-keyboard.md (full history + current fix)node_modules/@capawesome/capacitor-android-edge-to-edge-support/android/src/main/java/io/capawesome/capacitorjs/plugins/androidedgetoedgesupport/EdgeToEdge.java