Skip to content

Fix keyboard dismiss semantics across iOS and Android#291

Merged
thymikee merged 5 commits into
mainfrom
codex/issue-270-keyboard-dismiss
Mar 27, 2026
Merged

Fix keyboard dismiss semantics across iOS and Android#291
thymikee merged 5 commits into
mainfrom
codex/issue-270-keyboard-dismiss

Conversation

@thymikee

@thymikee thymikee commented Mar 26, 2026

Copy link
Copy Markdown
Member

Summary

Closes #270.

Implement issue #270 by making keyboard dismiss platform-correct on Android and iOS.
Replace Android back-navigation dismissal with a non-navigation keyboard dismiss path and return an explicit unsupported error when the IME cannot hide without navigating.
Add an iOS runner-backed keyboard dismiss path, update command/docs text, and cover the behavior with tests.
Touched files: 15. Scope expanded beyond the session command family into Android/iOS keyboard backends, the iOS runner, and docs because the command contract crosses those boundaries.

Validation

pnpm typecheck
pnpm build:xcuitest
pnpm test:unit
pnpm test:smoke
pnpm format

@github-actions

github-actions Bot commented Mar 26, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstackincubator.github.io/agent-device/pr-preview/pr-291/

Built to branch gh-pages at 2026-03-27 10:37 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be22029b79

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +527 to +531
if result.wasVisible && !result.dismissed {
return Response(
ok: false,
error: ErrorPayload(
message: "Unable to dismiss the iOS keyboard without a native dismiss gesture or control"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Surface iOS keyboard-dismiss failures as unsupported operations

When iOS keyboard dismissal fails (keyboard was visible but no native dismiss gesture/control works), this branch returns ok: false, but parseRunnerResponse in src/platforms/ios/runner-session.ts maps all runner failures to COMMAND_FAILED. That means callers do not receive the explicit UNSUPPORTED_OPERATION signal now documented for non-dismissible keyboards, and automation that branches on unsupported capability will behave differently on iOS vs Android for the same scenario.

Useful? React with 👍 / 👎.

Comment thread src/core/dispatch.ts
Comment on lines +486 to +489
const result = await runIosRunnerCommand(
device,
{ command: 'keyboardDismiss', appBundleId: context?.appBundleId },
runnerCtx,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require app context for selector-only iOS keyboard dismiss

This call only forwards context?.appBundleId, so selector-only invocations without an active session pass undefined and the runner falls back to its own host app target (executeOnMain resets currentApp when no bundle is provided). In that path, keyboard dismiss --platform ios ... can succeed while never dismissing the AUT keyboard, which is a functional mismatch for explicit-selector usage on iOS.

Useful? React with 👍 / 👎.

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Clean PR. The switch from KEYCODE_BACK (4) to KEYCODE_ESCAPE (111) on Android is the right call to avoid unintended navigation side effects. The explicit UNSUPPORTED_OPERATION error when dismiss fails is a good contract improvement over silently no-oping.

A few observations:

1. Redundant guard in RunnerTests+Interaction.swift. After guard wasVisible returns early, you check if keyboard.exists again on the same app.keyboards.firstMatch. Since isKeyboardVisible already confirmed keyboard.exists && !keyboard.frame.isEmpty, this inner if will always be true. Drop it and call keyboard.swipeDown() directly — removes a nesting level and makes the flow clearer.

2. iOS keyboard status/get throws UNSUPPORTED_OPERATION at dispatch time, but the capability matrix marks iOS as fully supported for keyboard. An agent calling keyboard status on iOS will get past the capability gate and hit the dispatch error. The mismatch between "capability says yes" and "dispatch says no for status" could confuse callers. A comment in the capability matrix noting iOS only supports the dismiss subcommand would help.

3. Minor: hardcoded sleepFor(0.2) calls in the Swift dismiss flow. If XCTest keyboard animation timing varies (iPad vs iPhone, accessibility settings), these could be flaky. A waitForExistence-style poll would be more robust, but low-risk for now.

The redundant guard (#1) is the only thing I'd clean up before merging; the rest are optional.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed review feedback:

  • Removed redundant keyboard.exists guard in iOS runner dismiss path.
  • Added capability note that iOS only supports keyboard dismiss (status/get remains Android-only).

Tests: pnpm typecheck; pnpm test:unit -- --test-name-pattern "keyboard"; pnpm build:xcuitest:macos

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

All three items from the previous review are addressed:

  1. Redundant keyboard.exists guard removed
  2. Capability matrix now has a comment explaining the iOS-only-dismiss asymmetry
  3. sleepFor(0.2) kept as-is — low risk, agreed

LGTM. Clean to merge.

@thymikee

Copy link
Copy Markdown
Member Author

Follow-up fixes pushed on top of the review pass:

  • Preserve iOS runner UNSUPPORTED_OPERATION error codes for keyboard-dismiss failures.
  • Require an active iOS session for keyboard dismiss so selector-only calls cannot target the runner host app.
  • Added regression coverage for both paths.

Validation: pnpm typecheck, pnpm test:unit, pnpm test:smoke, pnpm build:xcuitest, pnpm format

@thymikee thymikee force-pushed the codex/issue-270-keyboard-dismiss branch from 643ea98 to c39f0ca Compare March 27, 2026 09:10
@thymikee thymikee force-pushed the codex/issue-270-keyboard-dismiss branch from c39f0ca to 1cddf87 Compare March 27, 2026 10:02
@thymikee thymikee merged commit 11b47a2 into main Mar 27, 2026
12 checks passed
@thymikee thymikee deleted the codex/issue-270-keyboard-dismiss branch March 27, 2026 10:49
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.

Fix keyboard dismiss semantics across iOS and Android

1 participant