Skip to content

fix: recover mobile keyboard focus after dismissal and tab selection - #746

Draft
johoja12 wants to merge 2 commits into
Ark0N:masterfrom
johoja12:fix/mobile-tab-keyboard-focus
Draft

johoja12 wants to merge 2 commits into
Ark0N:masterfrom
johoja12:fix/mobile-tab-keyboard-focus

Conversation

@johoja12

@johoja12 johoja12 commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Explicit session selection can leave the mobile keyboard closed. Native dismissal can also leave xterm's textarea focused after the keyboard disappears, so the next tap focuses an already-focused element and does not reopen input. A delayed closing viewport event can then blur a newer tab's input. Opening another control happens to clear the stale editing focus, which explains the reported menu workaround.

This updates the original tab-focus fix with the final keyboard lifecycle recovery from the fork:

  • Focus the visible terminal/CJK input synchronously during explicit selection; keep hidden-keyboard automatic selection passive and avoid stealing focus after asynchronous history restoration.
  • Determine whether the keyboard is up from focus plus viewport state. Release stale focus at gesture start and reopen at gesture end, preserving normal prompt and transcript tap behavior.
  • Track focus generations and ownership so an old keyboard-close event cannot blur a newer opening. Preserve the keyboard-free viewport calibration across listener reconnects, focus-out recovery and foreground transitions.
  • On a visible iOS page that has lost native page focus, allow the bounded, trusted compatibility-click default needed for native activation while stopping xterm/CLI mouse propagation. Cancel recovery for stale generations, changed sessions, another editor, selection, or expiry. No delayed focus retry or script window.focus() workaround.
  • Add content-free focus/viewport diagnostics and regression coverage for repeated dismissal/reopening, CJK, tab transfers, delayed viewport transitions, reconnects and native-click cancellation.

Only shared upstream keyboard code is included. The fork's tab sheet, header utility menu, case-group UI, OpenCode click fallback and unrelated backend changes are excluded.

Validation on this upstream branch:

  • 32 real Chromium DOM/focus regression tests pass.
  • 246 related unit tests pass across eight files.
  • Typecheck, lint, formatting, lockfile sync, public assets, frontend syntax and browser-test exclusion checks pass.

The broader mobile keyboard suite finished with 48 passing and five failing tests. All five failures were reproduced against the previous PR head 15e27275 in a separate baseline worktree: toolbar transform, CJK local-echo text, readback collapse focus, inert transcript classification, and subagent status classification. These remain failing checks; they are not reported as passing keyboard gates.

The isolated Codex/shell smoke completed with 103 passing checks and three failing screenshot attachment-history assertions (the same category recorded in the original PR). Terminal replay, switching, scrollback retention and browser page/console checks passed. The runner was calibrated to upstream's 50,000-line default rather than the fork's 100,000-line default. The full Claude-enabled run stopped at preflight because the local OAuth session expired; the completed run explicitly skipped Claude. No remote-host smoke was configured. All temporary smoke sessions were removed and both temporary instances stopped.

Chromium touch emulation verifies focus and event routing; it does not prove native iOS keyboard presentation. The final fork build received a user report of improvement, but this adapted upstream build still needs physical-device confirmation. Keep this PR draft pending that verification.

RCA and fork implementation: johoja12#35 and johoja12#37.

@Ark0N

Ark0N commented Oct 31, 2026

Copy link
Copy Markdown
Owner

Thanks for this PR, johoja12, and for the careful write-up and the RCA on your fork. It makes an explicit session selection on a touch device focus the terminal, and so open the on-screen keyboard, even when the keyboard is closed; today a tab switch on touch focuses the terminal only when the keyboard is already up.

Open question. Today's behaviour is deliberate: _shouldFocusTerminalForTabSwitch() was written as "desktop: always; touch: only while the keyboard is open" (commit 7a39fd9), so that switching sessions on a phone does not cover half the screen with the keyboard, and typing starts with a tap on the terminal. This PR reverses that rule. Whether a session switch on a phone should open the keyboard is a product decision, and it is still open.

What a version going ahead would need. From reading the change against its call sites and running it:

  1. The new default reaches far past the tab tap (src/web/public/app.js:9845). The predicate now returns true on touch for every call that does not pass auto: true, but selectSession treats an untagged call as user-initiated on purpose, so that an idle alert errs toward being acknowledged. For the keyboard the safe direction is the opposite. Two regressions follow:

    • src/web/public/panels-ui.js:416: _restoreOverlayFocus calls _shouldFocusTerminalForTabSwitch?.() with no arguments to stay passive on touch (its comment at lines 409-415 says so). It now returns true, so closing the command palette or the Session Manager on a touch device with the keyboard down focuses the terminal inside the close tap and opens the keyboard. Running the real _restoreOverlayFocus against both versions of the predicate (touch, keyboard hidden, saved focus on <body>): master makes 0 terminal.focus() calls, this branch makes 1. test/command-palette-ui.test.ts:954 stubs the predicate to () => false, which is why the suite does not catch it.
    • src/web/public/mobile-handlers.js:889-890 calls prevSession/nextSession from touchend, and those call selectSession(id) with no options (app.js:10965, app.js:10976), so every sideways swipe between sessions now opens the keyboard.

    Other untagged callers reachable on touch change the same way: the notification drawer (notification-manager.js:490), the approvals inbox (approvals-ui.js:162, :176), the phone overview (mobile-overview.js:402, :972), home sessions (home-sessions.js:148) and the push-notification relay (settings-ui.js:220, which has no gesture behind it).

    The ask: keep the predicate's default passive on touch, and have the gestures that are meant to open the keyboard opt in with their own option, for example handleSessionTabClick (app.js:7791) passing { forceReload: true, openKeyboard: true }, and _activateTabRow (app.js:7511) if rail rows are to match. Swipe then becomes a choice made at nextSession/prevSession rather than a side effect. Please add a test for the overlay close that uses the real predicate, not a stub.

  2. src/web/public/app.js:10514: the second terminal.focus(), after the buffer load, reads the same shouldFocusTerminal, so an explicit touch selection focuses again once the async load finishes, outside the gesture. The synchronous focus already does the work; gate this one on touch to KeyboardHandler.keyboardVisible at that moment, so a user who closed the keyboard during a slow load does not get focus handed back.

  3. Nit: config/test-suites.ts:22 adds the new glob at the top of BROWSER_TEST_GLOBS; please append it at the end like the others.

Two things that are good as they stand: test/mobile-tab-keyboard-focus.browser.test.ts is a real regression test (3 of its 5 cases fail against master's app.js), and the test/mobile/tabs.test.ts change fixes an assertion that has been red on master since preserveKeyboard was removed. Typecheck, lint, format, frontend syntax and browser-exclude checks pass, and test/mobile/keyboard.test.ts plus test/mobile/tabs.test.ts show no new failures against master (one fewer: the tabs fix).

Nothing needs to change on your side until the open question is answered; the list above is what a version going ahead would need.

@johoja12
johoja12 marked this pull request as draft October 31, 2026 17:14
@johoja12

Copy link
Copy Markdown
Contributor Author

Converted this PR to draft after the reporter confirmed the phone keyboard still fails and may feel worse. Expanded full-page testing found gaps the original shell-only focus tests missed: a webview-return focus is attempted while terminal-wrap is display:none; a lingering touch-selection focus guard rejects focus; and the late post-load refocus can steal focus from a control selected during a slow load. Candidate lifecycle changes and broader matrix tests are isolated in the fork investigation. Chromium focus is not proof that the native iOS keyboard opens. Please hold review/merge while that investigation is completed.

@johoja12 johoja12 changed the title fix: focus terminal input on explicit mobile session selection fix: recover mobile keyboard focus after dismissal and tab selection Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants