Repository navigation
fix(android): keep chat composer above the keyboard (#194, #156) - #236
Conversation
Port of the live-IME-height mechanism from #201 (Keyboard.metrics() + keyboardDidShow/Hide listeners, explicit reset to 0 on hide) plus the bounded multiline composer and scrollable QuestionPrompt from #208. API-35 guard: the native IME-insets listener in MainActivity owns the IME height on Android 15+, so the JS padding applies only below API 35 — exactly one layer applies it on every API level. iOS keeps behavior="padding" with offset 90. #208's static insets.top offset is deliberately not ported: it measured 56 dp short on a Pixel 11 Pro (see #194).
Stage 4 review — PASSIndependent review of head Acceptance criteria
API-35 guard — exactly one layer applies IME height on every API level (verified)
Test-count delta — verified by my own runs
No scope creepNo Merge gate — NOT green yet, do not merge on this verdict aloneBranch protection on Filed notes (not failures, out of this PR's composer + connection-forms scope)
|
Fixes #194
Fixes #156
What
Re-implementation (from scratch, per the Stage 1 context pack) of the Android keyboard fix — no candidate PR merged as-is:
Keyboard.metrics()+keyboardDidShow/keyboardDidHidelisteners, applied aspaddingBottom, explicitly reset to 0 on hide (src/lib/keyboard-offset.ts+ test, session-screen wiring).composerMaxHeightfrom live IME geometry, internal scroll viascrollEnabled) + scrollableQuestionPromptwith pinned footer (src/lib/session-layout.ts+ test, composer cap,QuestionPrompt.tsx).API-35 decision (the mandatory design point)
JS padding applies only below API 35; the native IME-insets listener owns API 35+.
MainActivity.kt/plugins/withKeyboardInsets.jsinstall a native listener gated onSDK_INT >= VANILLA_ICE_CREAMthat adds the IME height to the root view's bottom padding. The newjsKeyboardPaddingApplies(platform, apiLevel)helper gates the JS padding toPlatform.Version < 35, so exactly one layer applies the IME height on every API level — no double-padding. Chosen over removing/extending the native plugin because the native fix is the working, shipped behavior on 15+; the JS gate is minimal and reversible.Deliberate non-ports
insets.topkeyboardVerticalOffsetis not ported (Stage 1: it measured 56 dp short on a Pixel 11 Pro, Android: chat input hidden behind keyboard #194).session-layout.tsowns only the composer cap + question scroll props; the offset lives inkeyboard-offset.ts. Its two offset tests were dropped from the ported test file (the iOS case is already covered bykeyboard-offset.test.ts).react-native-keyboard-controller— owner call needed, not granted), fix(android): prevent keyboard overlap across input screens #193'sadjustPan.behavior="padding", offset 90.Diff
6 files, +217/−14 (vs main
e8444931):app/session/[id].tsx(+50/−12): keyboard listeners, JS padding, composer capsrc/components/chat/QuestionPrompt.tsx(+9/−7): scrollable body, pinned footersrc/lib/keyboard-offset.ts(new) +src/lib/keyboard-offset.test.ts(new, 10 tests)src/lib/session-layout.ts(new) +src/lib/session-layout.test.ts(new, 5 tests)Connection add/edit screens need no change: already KAV-wrapped on main (merged #216).
Gate evidence (on head 484e227, this VM — no Android SDK/device)
npm run check:versions— exit 0npm run typecheck— exit 0npm test(full suite) — 395 pass, 0 fail (baseline on main: 380 pass — +15 tests, test files 48 → 50; 2 ported offset tests from fix(android): keep IME prompts usable #208 dropped, see above)applicationIduntouched (cc.agentlabs.opencode).Open residual (Stage 5)
Physical-device proof (open / multiline / dismiss) was not run — this VM has no Android SDK or device. Needs a real Android phone before merge, including the API-35 vs pre-35 check that exactly one layer applies the IME height.