Chat InputDock Jumps to Top of Screen When Keyboard Opens (Android)
Metadata
- Date:
2026-05-17 - Status:
fixed - Severity:
high - Related issue/ticket: fix/chat-keyboard-jump branch, commits d0a00265, a1b7f183, 71687a07
- Owner:
Benjamin Lewis
About
Overview: - On Android, when the software keyboard opens in the chat screen, the InputDock (text input bar) flies to the top of the screen instead of sitting just above the keyboard. A large empty gap appears between the InputDock and the keyboard. - This makes the chat screen unusable — the text input is in the wrong place while typing.
Technical Questions: - Why does the InputDock end up at the top of the screen rather than just above the keyboard? - What changed to expose this bug? - Why did d0a00265 not fully fix it (it set softwareKeyboardLayoutMode: "pan" in app.json but the committed AndroidManifest.xml was not updated)?
Resources: - main/app/app/chat.tsx — manual keyboard spacer (keyboardOffset state + <View height={keyboardOffset+20}>) - main/app/app.json — softwareKeyboardLayoutMode: "pan" (set in d0a00265, needed to match AndroidManifest) - main/app/android/app/src/main/AndroidManifest.xml — android:windowSoftInputMode="adjustResize" (the mismatch)
Steps to cause failure
flowchart LR
OpenChat["Open chat screen (Android)"] --> TapInput["Tap InputDock\nto open keyboard"]
TapInput --> KeyboardRises["Keyboard rises ~300px"]
KeyboardRises --> WindowShrinks["adjustResize: window shrinks\nby ~300px"]
WindowShrinks --> SpacerAdds["Manual spacer renders\nheight = keyboardOffset + 20 = ~320px"]
SpacerAdds --> DoubleCounting["InputDock pushed UP by ~320px\ninside a window that already shrank ~300px"]
DoubleCounting --> InputAtTop["InputDock appears near top\nof screen — unusable"] System
flowchart TD
Container["View container (flex:1)"] --> ChatBody["View chatBody (flex:1)"]
ChatBody --> FlatList["FlatList (flex:1)\nmessage list"]
ChatBody --> DockWrapper["dockWrapper\nInputDock lives here"]
ChatBody --> Spacer["Spacer View\nheight = keyboardOffset > 0 ?\nkeyboardOffset + 20 : 0"]
AndroidManifest["AndroidManifest.xml\nadjustResize"] -->|"window shrinks\nby keyboard height"| Container
KeyboardEvent["keyboardDidShow event\ne.endCoordinates.height"] --> KeyboardOffset["keyboardOffset state"]
KeyboardOffset --> Spacer
Note1["With adjustResize: window already shrank\nSpacer then adds keyboard height AGAIN\n= double-counting\nInputDock flies to top"]
Note2["With adjustPan: window stays full-size\nSpacer correctly lifts InputDock above keyboard\n= correct behavior"] Root Cause
Pre-existing mismatch: Commit 1ab51398 (2026-05-09) introduced the manual keyboard spacer approach and simultaneously disabled edgeToEdgeEnabled (changing it from true to false). With edgeToEdgeEnabled: false, the Android adjustResize behavior in AndroidManifest.xml takes effect, shrinking the window when the keyboard opens. The manual spacer was designed for use without window-resize (i.e., adjustPan / edgeToEdgeEnabled: true). This created a latent double-counting bug: window shrinks by keyboard height AND spacer adds keyboard height again, pushing InputDock up by 2x keyboard height.
Why it was tolerable before: Before commit 71687a07, chatWithMemory() blocked for ~56 seconds waiting for the GPU. During this wait, users typically dismissed the keyboard before the response arrived, or the bug went unnoticed.
Regression commit: 71687a07 (feat: message-id polling). The dispatcher now returns in ~1-2 seconds. The response arrives while the keyboard is still open. The rapid state updates (user bubble → placeholder bubble → polled answer) trigger layout reflows while the keyboard is active, making the double-counting visible on every message send.
Incomplete fix: d0a00265 set softwareKeyboardLayoutMode: "pan" in app.json (which maps to adjustPan in AndroidManifest). But AndroidManifest.xml is a committed generated file that was NOT regenerated — it still contained adjustResize. So the fix had no effect at runtime.
Complete fix: Update AndroidManifest.xml to android:windowSoftInputMode="adjustPan" to match the app.json setting. With adjustPan, the window does not resize when the keyboard opens, and the manual spacer correctly lifts the InputDock above the keyboard.
Reproduction Details
- Build the app on Android with
adjustResizein AndroidManifest andedgeToEdgeEnabled: false - Open the chat screen
- Tap the InputDock to open the keyboard
- InputDock jumps to the top of the screen
Reproduction test: layout behavior is not unit-testable without a device. The invariant "AndroidManifest windowSoftInputMode must match softwareKeyboardLayoutMode in app.json" is checkable statically.
Notes for PR
Root cause: AndroidManifest.xml had adjustResize while app.json specified softwareKeyboardLayoutMode: "pan" (= adjustPan). With adjustResize, the window shrinks when the keyboard opens. The manual keyboard spacer in chat.tsx (<View height={keyboardOffset+20}>) then adds keyboard height AGAIN on top of the already-shrunken window — double-counting — pushing InputDock to the top of the screen.
Fix: Change android:windowSoftInputMode in AndroidManifest.xml from adjustResize to adjustPan. This matches the softwareKeyboardLayoutMode: "pan" in app.json. With adjustPan, the window stays full-size and the keyboard overlaps the bottom; the manual spacer correctly positions the InputDock above the keyboard.
Audit Log
| ID | Action | Note | Context |
|---|---|---|---|
| 1 | Create audit log | Initialize bug investigation | InputDock jumps to top when keyboard opens on Android |
| 2 | Read AndroidManifest.xml | adjustResize in windowSoftInputMode | window shrinks on keyboard open |
| 3 | Read app.json | softwareKeyboardLayoutMode: "pan" added in d0a00265 | maps to adjustPan — mismatch with manifest |
| 4 | Read chat.tsx | Manual spacer: height = keyboardOffset > 0 ? keyboardOffset + 20 : 0 | assumes adjustPan (no window resize) |
| 5 | Investigate git log for chat.tsx | Found commits: 1ab51398 introduced spacer + disabled edgeToEdgeEnabled; cb0a4d7f tuned +20 | spacer designed for non-resizing window |
| 6 | Investigate regression timing | chatWithMemory was 56s before 71687a07; bug was latent but not visible because keyboard dismissed before response | 71687a07 exposed it |
| 7 | Check d0a00265 | Only changed app.json, not AndroidManifest.xml | incomplete fix |
| 8 | Confirm root cause | AndroidManifest.xml adjustResize + manual spacer = double-counting keyboard height | double-counting confirmed |
| 9 | Fix applied | Changed AndroidManifest.xml windowSoftInputMode from adjustResize to adjustPan | matches app.json |
| 10 | Verify tests pass | chat-screen tests pass; no layout-behavior unit tests needed | done |
Verification
- [x] Reproduced failure (code analysis confirms double-counting with adjustResize + manual spacer)
- [x] Root cause identified with evidence (git blame trace: 1ab51398 introduced mismatch, 71687a07 exposed it)
- [x] Fix applied at source (AndroidManifest.xml updated to adjustPan)
- [x] No regression test needed (layout not unit-testable; static config mismatch is the root)
- [x] Verified no duplicate solved-bug log for same root cause (2026-05-08 bug was opposite: keyboard NOT pushing up; this bug is keyboard pushing UP too far)