Skip to content

fix(mobile): restore keyboard focus across tab switches and repeated dismissals - #37

Merged
johoja12 merged 16 commits into
localfrom
investigate/35-mobile-keyboard-rca
Nov 2, 2026
Merged

johoja12 merged 16 commits into
localfrom
investigate/35-mobile-keyboard-rca

Conversation

@johoja12

@johoja12 johoja12 commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Mobile keyboard reopening can fail after dismissal even though xterm's input has DOM focus. The native phone traces show both failed and successful opens at the same onscreen editable input, so DOM focus alone is not an acceptance check.

This branch corrects the input lifecycle: a newly observed keyboard shrink owns an exact input and opening generation; dismissal releases only that owner and cannot cancel a newer request. Prompt taps that merely retain an already-open input preserve ownership. Actual opens and transfers supersede the old request. Touchstart releases remaining stale focus before touchend reopens input. Calibration survives same-width SSE listener reconnection, foreground recovery, and focus-out fallback. Existing explicit-tab, webview reveal, selection-guard and slow-load focus protections remain included. Diagnostic observers never focus or retry input and do not record typed contents.

Related #35. Latest candidate 2c5bc3ea is deployed for native phone testing. Production serves mobile-handlers.bff5571d.js / terminal-ui.441447b5.js, byte-matching the isolated build. HTTP 200/service active; existing backend artifacts and unrelated WIP hashes unchanged. The issue remains open and this PR draft until the actual phone dismiss/reopen result is confirmed.

Validation on this revision:

  • New prompt-retention and reconnect-calibration unit cases plus the repeated browser cycle failed before the correction.
  • 217 related unit tests passed, including previous late-close/newer-request protections.
  • 18 browser regressions passed: 20 dismiss/reopen cycles with zero/one/two actual prompt touches while open, reconnect while open then dismiss/reopen, 25 overlapping close transitions, real gesture phases and mouse guard, other-editor preservation, selection/webview and slow-load paths.
  • Full mobile keyboard suite: 56 passed.
  • Full-page disposable shell: 30 cycles with two actual prompt taps while open, all 30 commands executed; no page errors.
  • Realistic Codex/shell smoke: 86 passed, zero failed, one unconfigured external-remote skip. Temporary runner direction/traversal/final-marker races were corrected without removing assertions; earlier failures remain documented on bug: mobile session tab does not reliably open the keyboard #35.
  • Typecheck, lint, formatting, lockfile, frontend syntax and isolated build passed.
  • Fresh full CI is pending. Previous full CI is green on v6, not this revision.

Chromium and mocked viewport changes verify event ordering, ownership and delivered input. They do not verify OS keyboard presentation. Claude authentication and external remote smoke are unavailable. Latest source RCA: docs/reports/mobile-tab-keyboard/lifecycle-rca/dismiss-reopen-v7.md.

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3c23385b-9252-4ea3-9b28-8f205e76ae6e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@johoja12

Copy link
Copy Markdown
Owner Author

Repeated-dismiss candidate 2ded578a is committed locally. Validation completed:

  • 12 browser regressions passed, including 25 repeated show/hide/reopen cycles through the actual detector.
  • Full mobile keyboard suite: 56/56 passed.
  • Related unit regressions: 172/172 passed.
  • Full-page isolated cycle probe: 12 cycles / 36 states, no offscreen input or focus-state mismatch.
  • Build, typecheck, lint, format, lockfile and frontend syntax checks passed.
  • Realistic smoke: 84 passed, 2 failed, 1 remote skip. Both failures concern long shell scrollback after reload (return to late history; reveal text before divider). Browser page/serious console errors: none. Temporary sessions cleaned up.

Deployment readiness is NO while those runtime smoke checks fail. This follow-up has not been pushed or deployed; production remains on the previous frontend candidate. Native phone keyboard visibility remains unverified. Issue and draft PR remain open.

Artifacts: /tmp/codeman-35-repeat-keyboard.log, /tmp/codeman-35-repeat-browser.log, /tmp/codeman-35-repeat-fixed.log, /tmp/codeman-dev-smoke-artifacts-2026-10-31T18-02-59-903Z-p2720596-r635f64c5ba254a54ba4616068f3dc0b0/summary.json.

@johoja12 johoja12 changed the title fix: correct mobile focus lifecycle (RCA candidate) fix(mobile): restore keyboard focus across tab switches and repeated dismissals Oct 31, 2026
@johoja12

Copy link
Copy Markdown
Owner Author

Deployed the requested branch for phone testing at 2ded578a (frontend assets only). Production returns HTTP 200 and serves mobile-handlers.90de8d54.js, byte-for-byte matching the isolated build. Service is active. Existing backend artifact hashes and unrelated shared-checkout WIP hashes are unchanged; previous static assets are backed up at /opt/projects/.codeman-public-before35-repeat-20261031.

Fresh production-page probe: 12 simulated open/close cycles, 36 states, zero focus mismatches and zero offscreen helper inputs. Its disposable session was removed. This tests DOM focus and viewport handling, not native keyboard visibility. Actual phone confirmation is still needed; issue and PR stay open/draft.

The broader smoke remains unresolved: correcting the temporary runner's reversed fallback wheel direction did not clear its long-scrollback return failure. Deployment is an explicitly requested candidate for phone testing, not a claim of green runtime gates. GitHub PR checks are pending.

@johoja12

Copy link
Copy Markdown
Owner Author

Fixed and deployed a7a676a9 for native phone testing. The prior resize-triggered blur is removed, and foreground/focus-out recovery preserves the keyboard-free baseline. Old closing animations can no longer blur a newly focused input. v4 diagnostics observe follow-up focus/viewport state without retrying focus.

Completed validation:

  • Six new race/calibration regressions failed before the fix and pass after it.
  • Clean GitHub CI: 16,293 unit/integration tests passed, 15 standard skips; 308 xterm package tests passed. Static checks passed. Run: https://github.com/johoja12/Codeman/actions/runs/41248044676
  • 56 mobile keyboard tests, 14 browser regressions, 211 related tests passed.
  • Fresh realistic smoke: 96 passed, 0 failed, 1 unconfigured external-remote skip, no page or serious console errors. The first corrected run's live repaint timeout remains recorded; the fresh rerun passed every check. Test runner wheel direction and traversal limit were corrected without dropping assertions.
  • Isolated full-page input probe: 30 rapid reopen cycles / 30 executed commands / zero page errors.
  • Fresh production-page input probe: 30 rapid reopen cycles / 30 executed commands / zero page errors. Tests use actual touch events and verify printf-generated command output (not echoed input), with simulated viewport changes. Disposable sessions removed.

Deployment readback: HTTP 200, codeman-web active; app.d917d0ca.js, mobile-handlers.48401402.js, terminal-ui.d8c090bb.js byte-match the isolated build. All existing backend artifacts and unrelated checkout WIP hashes unchanged. Previous frontend retained at /opt/projects/.codeman-public-before35-v4-20261101.

Local full sweep initially had one checkpoint build-suite failure from my concurrent builds deleting an asset during minification; 16,283 tests passed in that run. The checkpoint's isolated rerun passed all 10 tests, and the subsequent clean GitHub run passed the entire suite. This execution collision was not a product fix or a waived test.

Native OS keyboard appearance is still unverified: Chromium touch emulation/viewport simulation cannot establish that result. Reload on the actual phone before checking repeated open/close. #35 remains open and #37 draft pending that confirmation.

Evidence: /tmp/codeman-35-v4-github-ci.log, /tmp/codeman-35-lifecycle-red.log, /tmp/codeman-35-lifecycle-v4-browser.log, /tmp/codeman-35-v4-mobile.log, /tmp/codeman-35-v4-prod-delivery.json, /tmp/codeman-dev-smoke-artifacts-2026-10-31T22-23-04-015Z-p3752264-rf4abeab0c45f4f30b384edd69c2835dd/summary.json.

@johoja12

johoja12 commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Ran the requested live production touch-device emulation against http://127.0.0.1:3002, with a 440x763 iPhone-UA/IOS-CSS Chromium context, hasTouch and isMobile enabled. No production code or settings were changed for this test.

Final run passed:

  • 28 open/dismiss/reopen cycles: 20 shell + 8 Codex.
  • 14 actual Codeman accessory dismiss-button cycles and 14 viewport-restoration/native-dismiss simulations.
  • Zero/one/two actual prompt touches while open before each dismissal.
  • Five real SSE disconnect/reconnects while the simulated keyboard was open.
  • All 20 shell commands executed; all eight Codex composer inputs reached the PTY.
  • Codex submitted a prompt and produced the requested unique response.
  • Actual mobile-sheet switches between the two disposable sessions; no page errors.

All temporary sessions/cases were removed. All 23 pre-existing sessions remain and their names/modes/directories/case links are unchanged. The test browser blocked terminal connections/activation for non-test IDs and did not forward shared preference or crash-diagnostic writes.

Important limit: this is Chromium touch emulation with simulated viewport resizes, not native iPhone keyboard presentation. The no-resize control acquired DOM focus while keyboardDetected stayed false. Thus these passing web/app checks do not establish that the real phone's keyboard opens reliably; #35 remains open.

Earlier attempts remain in the artifacts: startup notice blocked the first preflight; the floating Assistants launcher overlapped the first cursor point in another attempt (final test used an uncovered point on the same input row). A fast synthetic-typing/immediate-Enter attempt passed all 28 focus/input cycles but left a newline in Codex. The final run used 35ms per character and a 750ms pause before Enter, then submitted/responded successfully. No product patch or forced Enter retry was used to hide that result.

Private local artifacts: /tmp/codeman-touch-prod-20261101/report.json, native-keyboard-control.json, codex-typed.png, final.png, and retained attempt-1 through attempt-3 evidence. Screenshots were not uploaded publicly.

@johoja12

johoja12 commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Expanded live production touch test (v7 assets) completed November 1.

  • 24 scenarios across disposable shell and Codex sessions: switching with keyboard closed/open; accessory dismissal before switching; reselecting the current tab closed/open; 900 ms delayed terminal loads with immediate touch; search focus during delayed load; rapid A -> B -> A switching.
  • All 24 passed touch focus and input delivery, including dismissal followed by touch reopen. Each scenario verified two unique input markers reached the selected PTY and did not appear in the other session. Shell markers also verified actual execution.
  • No browser page errors. All 23 preexisting sessions and their checked metadata were preserved; both disposable sessions and their cases were removed.

Outstanding finding: all 6 keyboard-open switch/reselect cases retained terminal DOM focus after simulated native dismissal. Diagnostics show the focus generation advanced during tab selection but the keyboard owner still referred to the previous generation, so onKeyboardHide skipped release. This is a concrete ownership mismatch relevant to the real iPhone report; Chromium still accepts subsequent touch focus/input, so it does not establish the native keyboard failure or its resolution.

Harness correction: the initial run stopped at scenario 14 because a prompt tap happened while the mobile tab picker was still sliding closed. That attempt and screenshot are preserved. The completed rerun waits for the picker to physically clear the viewport before touching the terminal, independently of the injected terminal-load delay. No forced clicks or programmatic focus fallback were used.

Artifacts (private local): /tmp/codeman-touch-tabs-prod-20261101/report.json, run.cjs, run.log, final.png; first attempt in attempt-1/. Test engine was Chromium with iPhone UA, mobile CSS, real touch events, and simulated viewport shrink/restore. Native iPhone keyboard appearance remains unverified. No application code change or deployment was made for this test; issue remains open and PR remains draft.

@johoja12

johoja12 commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Deployed follow-up fix 5905558 (v8 diagnostics, package version remains 1.47.0).

RCA: tab/search selection advances the terminal focus generation while the existing keyboard remains visible. No new viewport shrink occurs to refresh dismissal ownership. Its next hide therefore skips release because the owner still refers to the old generation.

Fix: after an explicit session-tab request survives the 350 ms focus-settle interval, transfer dismissal ownership to that request only if its target, generation and keyboard-open viewport geometry remain unchanged. This timer never focuses or blurs. Closing geometry, another editor, another request, hidden page or cleared keyboard ownership prevents adoption. Late-close protection remains in place while settling.

Validation:

  • New browser regression failed before the fix; all 26 focused browser tests passed afterward. Includes settled transfers, close events at 0/80/200/349 ms, newer requests, another editor and changing geometry.
  • 102 related unit tests passed; typecheck, lint, format, lockfile, frontend syntax/public assets and build passed.
  • Isolated realistic smoke: 86 checks passed; real shell/Codex, streaming tab switches, long replay, follow-up and reload; sessions/server cleaned up.
  • The original smoke runner produced an initial-render false failure by matching command echo before output. The rerun used the existing corrected runner that waits for the generated history marker, sets adequate fixture tmux retention, emits actual newlines and uses the correct scroll direction. Both logs are retained.
  • Frontend deployed with rollback at /opt/projects/.codeman-public-before35-v8-20261101. HTTP 200 and served mobile-handlers.ccf1439e.js / terminal-ui.fb2d067b.js matched isolated build bytes. All backend deployment artifacts and five protected unrelated source files stayed byte-identical.
  • Production touch retest: 24/24 scenarios, 48 input receipts in the correct selected PTY, no cross-session leaks, no browser errors. Switching closed/open; reselecting current tab; dismiss-before-switch; delayed loads/search focus; rapid A -> B -> A. Stale focus after native-dismiss simulation went from 6/6 keyboard-open switch/reselect cases on v7 to 0/6 on v8 (0/24 overall).
  • All 23 original session IDs survived the deployment; checked metadata unchanged during retest. Exact owned sessions/cases removed.

Retest artifact: /tmp/codeman-touch-tabs-prod-v8-20261101/report.json (runner, logs and screenshots alongside). The first production attempt stopped on a transient post-resize history classification; retained in attempt-1/. The completed run polls for the actual valid touch target up to 1.8 s without forced scrolling, forced clicks or programmatic focus. It still asserts keyboard dismissal releases focus.

Native keyboard appearance remains unverified: Chromium touch events and viewport simulation prove web focus/state/input behavior, not the iPhone OS keyboard. Issue stays open and PR stays draft for device confirmation. Current GitHub Actions run 41298300037 is still in progress; local gates above passed. No upstream repository writes and no merge to local were made in this follow-up.

@johoja12

johoja12 commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Deployed candidate 8e2d26f, diagnostic build v9 (package 1.47.0).

The fresh real-phone v8 reproduction disproved native resolution: release:true on dismissal, then later terminal focus remains active/onscreen/editable without a keyboard show; menu reflow followed by a tap succeeds. This is distinct from the earlier generation mismatch. Native trace is preserved privately at /tmp/codeman-35-v8-reloaded-phone.json.

Candidate recovery: remember the exact input released on owned native dismissal. On a subsequent iOS opening gesture, while it is unfocused and the keyboard is hidden, temporarily remove and restore that input's display with synchronous layout reads before focus. Preserve the same DOM node, listeners, value, selection and original inline display/priority. A real keyboard show clears the marker. No focus retries from timers and no terminal resize, menu toggle or unrelated editor focus changes. This tests the layout-invalidation hypothesis suggested by the menu workaround; the native internal cause is not proven.

Validation:

  • 30 focused browser tests passed, including draft/selection/listener/style preservation, excluding active keyboards/non-iOS/other editor targets, and all prior closing-generation protections.
  • 102 related unit tests passed; typecheck, lint, format, lockfile, frontend syntax/public assets and build passed.
  • Isolated realistic smoke passed all 86 checks and cleaned up all sessions/server.
  • Frontend-only deployment: served mobile-handlers.4b36143e.js and terminal-ui.5095ca21.js byte-match the isolated build. Backend artifacts and five protected unrelated source files remained byte-identical. Rollback: /opt/projects/.codeman-public-before35-v9-20261101.
  • Production retest: 24/24 scenarios, 48 correct-session input receipts, zero cross-session leaks, zero retained focus after settled dismissal, zero page errors. The new iOS layout recovery path was explicitly instrumented and exercised at least 24 times. All 23 original session IDs survived; metadata unchanged during retest; exact owned sessions/cases removed.
  • First retest attempt stopped on a history classification after keyboard resize (viewportY=0/baseY=23). Preserved in attempt-1/. The completed runner allows input or history touch targets because _handleMobileTerminalTap explicitly permits keyboard focus on history; no forced scrolling/clicks or focus fallback. All focus-release and routing assertions remain. This is coverage of the supported history keyboard path, not proof of a viewport fix.

Artifacts: /tmp/codeman-touch-tabs-prod-v9-20261101/report.json, runner/log/screenshots alongside; /tmp/codeman-35-v9-{browser,unit,smoke,build}.log.

Native acceptance remains pending: asked user to reload into v9 and reproduce. No v9 native beacon has arrived yet. Do not mark this resolved from Chromium emulation. Issue remains open and PR remains draft. GitHub Actions run 41303431017 is still in progress. No merge to local or upstream writes in this follow-up.

@johoja12

johoja12 commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Captured native failure and deployed 5ee9a00 (diagnostic v10; package 1.47.0) to production.

Native evidence: fresh no-menu request-3 at 13:46:05 shows active, editable, rendered, clipped-in textarea, but document.hasFocus()=false immediately and at 350/1000 ms, with no keyboard show. In the same preserved trace request-2 from session-tab has pageFocus=true and does show the keyboard. Later menu-assisted request-19 also has pageFocus=true and a real viewport-show. Snapshot /tmp/codeman-35-v9-failure-no-menu.json is private (0600). This identifies missing page/window focus independently of activeElement; it does not prove a browser focus request will be accepted on the phone.

Change: request window.focus() before focusing the terminal input when iOS reports the page unfocused. Gated on visible page plus active user gesture, and skipped for already-focused pages/non-iOS/background/no-gesture paths. No timers or loops retry focus; browser rejection is logged and the input gesture proceeds. Removed the unsuccessful v9 display/layout-rebuild method, dismissal marker and its tests. Diagnostic builds/boxes consistently identify v10 and log the page-focus request outcome.

Validation:

  • 32 focused browser tests passed: page-before-input order with simulated lost page focus, preserving draft/selection/listeners, no background/no-gesture focus requests, browser refusal without looping, and prior native-close generation protections.
  • 102 related unit tests and all static checks/build passed.
  • Isolated real shell/Codex smoke: 86 checks passed; all temporary sessions/server cleaned up.
  • Frontend-only deployment: HTTP 200; served mobile-handlers.f55acd76.js and terminal-ui.870b4513.js match isolated build bytes. Existing backend artifacts and five unrelated source WIP files stayed byte-identical. Rollback at /opt/projects/.codeman-public-before35-v10-20261101.
  • Production touch matrix: 24/24 scenarios, 48 input receipts in the correct PTY, zero cross-session leaks, zero page errors, zero retained focus after settled dismissal. Eight cycles inject document.hasFocus()=false and verify an actual touch invokes window.focus() once before input, then delivers input. That injection validates the integrated branch, not iOS native focus or keyboard acceptance.
  • Original 23 session IDs preserved; metadata unchanged during retest; exact owned sessions/cases removed.

Artifacts: /tmp/codeman-touch-tabs-prod-v10-20261101/report.json, runner/log/screenshots alongside; /tmp/codeman-35-v10-{browser,unit,smoke,build}.log.

Asked user to reload into v10 and repeat without menu; if it fails, stop before menu to preserve the native page-focus outcome. Native acceptance pending; issue stays open and PR draft. GitHub Actions run 41313311244 is still in progress. No merge to local and no upstream writes.

@johoja12

johoja12 commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Deployed candidate 15dc142 (v11 diagnostics, package 1.47.0) after the native v10 failure.

Evidence: at 15:17:11.505/11.804 the phone logs KEYBOARD_PAGE_FOCUS requested=true after=false. Request-13 stays DOM-focused but pageFocus=false and visible=false at 350/1000 ms. Menu close at 15:17:12.833 has pageFocus=true; request-14 then produces a real keyboard viewport-show. Private snapshot /tmp/codeman-35-v10-native-failure.json (0600). Script window.focus was ineffective on this phone.

Change: remove script window.focus retries. For a visible iOS page that remains unfocused after an explicit terminal-input touch, arm a short-lived native-click recovery for the exact target, session and generation. Permit default browser activation on the following trusted compatibility mouse events while stopping propagation to xterm/CLI; refocus from its trusted click. Normal touch/selection mouse suppression remains. A new gesture, newer request, session switch, another editor, active selection, open keyboard or expiration prevents stale recovery. A content touch on an unfocused iOS page no longer pre-cancels that native activation path; actionable TUI taps still do not arm input recovery. OpenCode container capture skips only an armed recovery, retaining its ordinary click fallback.

Validation:

  • Controlled browser regression failed on v10: compatibility mousedown default was cancelled. On v11 native activation is allowed, input is refocused, and no mousedown/up/click reaches fake CLI handlers. This is a controlled model of default activation, not an iPhone keyboard simulation.
  • 33 focused browser tests passed, including newer-request/session/editor/expiry/selection fencing and all prior keyboard-generation protections.
  • 182 related unit tests passed, including OpenCode normal click fallback vs recovery; static gates, frontend syntax/public asset checks and final build passed.
  • Isolated shell/Codex realistic smoke: 86 checks passed; owned sessions/server cleaned up. A final targeted OpenCode guard refinement was additionally covered by the new unit test and rebuilt.
  • Frontend-only deployment: served mobile-handlers.ff32e70e.js / terminal-ui.407fa14b.js byte-match final isolated build; HTTP 200. Existing backend artifacts and five unrelated source WIP files stayed byte-identical. Rollback /opt/projects/.codeman-public-before35-v11-20261101.
  • Production touch retest: 24/24 scenarios; 48 correct-session input receipts; no cross-session leaks, page errors or retained focus after settled dismissal. Eight injected page-focus losses recover through the real trusted compatibility-click routing. The harness models page activation only when mousedown is NOT defaultPrevented; it never programmatically forces terminal focus or scrolls the terminal.
  • Original 23 session IDs preserved, metadata unchanged during retest, exact owned sessions/cases removed.

Artifacts: /tmp/codeman-touch-tabs-prod-v11-20261101/report.json and runner/log/screenshots; /tmp/codeman-35-v11-{red,browser-final,unit-final,smoke,build-final}.log.

User asked to reload into v11 and repeat without menu, stopping on failure if necessary. Native acceptance remains pending; issue stays open and PR stays draft. GitHub Actions run 41321387041 is still in progress. No merge to local and no upstream writes.

fix(cases): enable default groups for new and linked cases
@johoja12
johoja12 marked this pull request as ready for review November 2, 2026 11:50
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.

1 participant