Skip to content

[Bug]: Android keyboard state remains hidden after nonanimated IME recovery #22484

Description

@trahane

Reproduction scope clarification

The reporter confirms the upstream T3 Code Android app on a Samsung Galaxy Tab S8 Ultra: Chrome is already open, T3 is dragged from the taskbar into the other pane, and the composer stays low on first chat focus/typing. The reporter uses Samsung Keyboard. They report using the latest release (October 21, 2026); the exact app build/distribution channel and Android/One UI version have not been supplied. The initial baseline/patched recordings start with T3 already focused before entering split screen; that pair does not reproduce the reported visual failure. The later taskbar-drag recordings below use the corrected entry sequence, including a failing official-release run. This issue establishes the keyboard-state/dismissal defect; its connection to the Chrome-first composer symptom is not yet verified.

Three Chrome-first Recents checks on the unpatched Android 17 Pixel Tablet emulator kept both the editor and Send button above Gboard on first focus: background T3, cold-launched T3, and cold-launched T3 after starting with Chrome’s address bar and keyboard open. Those results are not verification of Samsung One UI, taskbar, or Edge panel behavior.

Before submitting

Area

apps/mobile

Steps to reproduce

The deterministic reproduction exercises the actual react-native-keyboard-controller source module and mounted useKeyboardState hook, replacing only the native event emitter and commands:

  1. Emit keyboardDidHide with height 0 to establish a hidden state.
  2. Emit keyboardDidShow with height 376, without an intervening keyboardWillShow.
  3. Read KeyboardController.isVisible(), KeyboardController.state().height, and the mounted hook's visibility/height.
  4. Call KeyboardController.dismiss() and check whether its promise resolves before a native keyboardDidHide event.

This is a real Android event sequence: KeyboardAnimationCallback.onApplyWindowInsets can call syncKeyboardPosition when the IME inset changes without an animation, and the shown repair emits keyboardDidShow only. The library's JS cache and hook currently subscribe to keyboardWillShow and keyboardDidHide, dropping that shown repair.

Expected behavior

After the shown repair, the imperative cache and mounted hook report visible with height 376. Dismissal waits for the native hide event before resolving.

Actual behavior

Both consumers remain hidden with height 0, and dismissal resolves before the native keyboard is hidden. The thread screen awaits KeyboardController.dismiss() before scrolling a submitted message, so the stale cache breaks that wait contract even though newer composer layout changes no longer gate translation directly on isKeyboardVisible.

Impact

Minor bug or occasional failure. Keyboard state and dismissal completion can be incorrect after Android restores or resizes the IME without an animation-start event.

Version or commit

Upstream main at 7e289ae1f77b9137580688aaccf00d48906984ad, with react-native-keyboard-controller@1.22.7 and the repository's existing patch.

Environment

The focused regression runs on macOS with Node 24.18.1 and pnpm 11.10.0. The original native event sequence was observed with Gboard on an Android 17 ARM64 tablet emulator while entering split-screen with a focused composer in Harbordex, based on upstream 2577baf8b0dc541283fa93d6919b0e354effadf4.

That older fork's composer was hidden behind the keyboard. I am not claiming that exact visual symptom still reproduces on current upstream: #20803 changed focused-input resume handling, and #19980 removed visibility from the sticky composer's enabled condition. This report concerns the remaining library state/dismissal defect.

Related work

Focused regression evidence

Test-only commit, based directly on the upstream commit above. It tests the real source cache and mounted hook; only native bindings are mocked. From apps/mobile, run:

vp test run --project react src/features/keyboard/keyboardControllerState.test.tsx

All three tests fail before the fix: hidden/0 instead of visible/376; dismissal settled before hide; completed height remains 320 instead of 376. All three pass with the same keyboardDidShow listeners proposed in #21595 (verification commit). Targeted formatting/lint, mobile typecheck, and native lint also pass. No current-upstream native UI run is claimed by this regression.

Workaround

A later normal keyboard show/hide animation can refresh the cached state. The targeted repair is to consume keyboardDidShow in both the imperative cache and useKeyboardState, as proposed in #21595.

Native verification update

Separate focused PR #22514 now includes this repair and the regression coverage, with the overlap with #21595 explicitly disclosed. On an Android 17 Pixel Tablet emulator with real Gboard and isolated upstream development state, entering split screen and returning focus to a retained draft produced keyboardDidHide(0) followed by keyboardDidShow(376) without keyboardWillShow. The baseline cache and mounted hook continued reporting isVisible: false, height: 0; with the patch both reported isVisible: true, height: 376. The composer stayed visible in both runs. Screenshots and short recordings were captured, inspected, and attached below and in #22514.

Screenshots and recordings

These show the native split-screen interaction. The composer remains visible in both upstream runs; the event trace above establishes the corrected cache/hook state.

Before — baseline:

Baseline upstream composer during split-screen typing

After — patched:

Patched upstream composer during split-screen typing

Before recording (31 seconds):

before-baseline-keyboard-recovery-short.mp4

After recording (33 seconds):

after-fixed-keyboard-recovery-short.mp4

Recordings are trimmed at original speed. Assets were uploaded through GitHub CLI using the contributor's public fork and embedded here.

Chrome-first taskbar-drag follow-up

The reporter has confirmed the upstream T3 Code Android app on a Samsung Galaxy Tab S8 Ultra, opened beside Chrome by dragging from the taskbar. The reporter uses Samsung Keyboard and describes the installed app as the latest release. Exact app build/distribution channel and Android/One UI version remain unknown.

A new continuous recording tests that entry method in a development build from the unpatched upstream base using an Android 17 Pixel Tablet emulator, native 2560×1600 resolution, and Gboard. It starts with Chrome fullscreen and the taskbar visible, drags T3 to the right pane, opens an existing chat, and focuses/types in the composer for the first time after the drag. Recents is not used. The editor and Send button remain above the keyboard (bottoms 716px and 819px; keyboard top 848px). This is an unsuccessful reproduction attempt, not evidence that the reported Samsung failure is fixed.

Unpatched upstream taskbar-drag attempt: composer stays visible above Gboard

Continuous taskbar-drag recording (40 seconds, original speed):

baseline-taskbar-continuous.mp4

Official released-app follow-up

A public upstream release now reproduces the matching visual failure with the correct Chrome-first taskbar entry: official Android beta 2.0.0 (93), package com.t3tools.t3code, after an observed Expo update (01a14684-bbee-74b8-84d5-3b5af89c023d; exact executing JS commit unestablished). Both a background-app run and a force-stopped/cold run hid the composer and Send button below Gboard on first focus/typing. Dismissing Gboard revealed the retained draft. The capture uses a Pixel Tablet Android 17 emulator; the reporter's Galaxy Tab S8 Ultra uses Samsung Keyboard, and they report using the latest release; exact app build/channel and OS versions remain unknown.

The development baseline above already passes the same taskbar route without this PR. Therefore, these release-versus-development results do not establish that this PR fixes the visual failure. The separate visual report is tracked in #22751.

Official upstream release candidate: composer hidden after taskbar-drag first typing

Failing released-app recording (39 seconds, original speed):

upstream-release-taskbar-composer-hidden.mp4

Newer official APK check

The reporter now describes their app as the latest release. The newest public Android APK found at test time, official 2.0.0 (306), kept the composer visible in both background-app and cold-launch Chrome-first taskbar-drag checks on Android 17 with Gboard. No available Expo update or restart was observed in the captured checks. The exact executing JS commit and the reporter's installed build remain unknown; Samsung Keyboard/One UI remain unverified. This does not establish #22514 as a fix for the visual symptom.

The inspected screenshot, continuous 23-second recording, and release comparison are in #22751.

Activity

  1. added
    needs more infoInitial triage showed no bug. Awaiting more info
    and removed
    needs more infoInitial triage showed no bug. Awaiting more info
    on Oct 21, 2026
  2. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Reproduced on current main (60b35a1) with the regression test from https://git.cafe/pingdotgg/t3code/pulls/22514 (apps/mobile/src/features/keyboard/keyboardControllerState.test.tsx, run with vp test run --project react). It exercises the real react-native-keyboard-controller@1.22.7 source with only the native bindings mocked:

    • main: 3/3 fail. After keyboardDidHide(0) → keyboardDidShow(376), the cache and useKeyboardState report hidden/0, and KeyboardController.dismiss() resolves before the native hide. After willShow(320) → didShow(376) the height stays at 320.
    • main + the patch change from https://git.cafe/pingdotgg/t3code/pulls/22514: 3/3 pass. The only conflict is the patch hash line in pnpm-lock.yaml, which vp install regenerates.

    https://git.cafe/pingdotgg/t3code/pulls/22514 is the fix for this issue. A separate PR will not be opened.

  3. juliusmarminge commented on Oct 27, 2026

    @juliusmarminge
    Member

    Update to the earlier comment: https://git.cafe/pingdotgg/t3code/pulls/22514 edits patches/react-native-keyboard-controller@1.22.7.patch, and https://git.cafe/pingdotgg/t3code/pulls/25141 has since replaced that file with the 1.22.8 patch, so https://git.cafe/pingdotgg/t3code/pulls/22514 no longer applies to main. The defect is unchanged on 1.22.8: on main at 71ba6b3, a regression test adapted from https://git.cafe/pingdotgg/t3code/pulls/22514 fails 2/2 (hidden/0 after keyboardDidShow(376), and dismiss() resolves before keyboardDidHide).

    The same fix, rebuilt on the 1.22.8 patch and credited to @trahane, is in https://git.cafe/pingdotgg/t3code/pull/1000000012. The tests pass 2/2 with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions