Skip to content

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.jsonsoftwareKeyboardLayoutMode: "pan" (set in d0a00265, needed to match AndroidManifest) - main/app/android/app/src/main/AndroidManifest.xmlandroid: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

  1. Build the app on Android with adjustResize in AndroidManifest and edgeToEdgeEnabled: false
  2. Open the chat screen
  3. Tap the InputDock to open the keyboard
  4. 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)