Skip to content

fix(android): handle hardware Enter without breaking IME input#101360

Merged
joshavant merged 1 commit into
mainfrom
fix/android-hardware-enter-sequence
Jul 7, 2026
Merged

fix(android): handle hardware Enter without breaking IME input#101360
joshavant merged 1 commit into
mainfrom
fix/android-hardware-enter-sequence

Conversation

@joshavant

@joshavant joshavant commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Closes #101239

What Problem This Solves

Fixes Android hardware-keyboard Enter handling that could leak repeated key-downs into the cleared chat composer, inserting a newline after the message had already sent. The same one-event interception also risked claiming Enter while an IME was still composing text.

Why This Change Was Made

Both Android composer implementations now share one package-owned hardware-key state machine and String-to-TextFieldValue adapter. They intercept physical keys at Compose's pre-soft-keyboard boundary, preserve public composition state, account for IMEs that keep hardware preedit private, and track ownership for the full Enter sequence. Each composer still uses its existing send callback; no config, protocol, or gateway surface was added.

User Impact

Physical-keyboard users can press Enter to send once, including during key repeat, without leaving a newline in the next draft. Shift+Enter continues to insert a newline, blank Enter remains a no-op, on-screen keyboard input is unchanged, and composing IMEs get the first Enter needed to commit their input before a later Enter can send.

Evidence

  • Live current-main repro on Android 15 / API 35 with Gboard: holding physical Enter sent once and left \n in the cleared composer at 780ca1d25315b34d3118e1db2d8dcafcc16415f3.
  • Live patched Gboard proof: simple physical Enter sent and cleared; long-press sent once and stayed empty; blank Enter stayed empty; Shift+Enter produced line1\nline2; Gboard's on-screen Enter still inserted a newline rather than invoking hardware send.
  • Live patched Trime 3.3.11 proof: private nihao preedit remained IME-owned even with committed draft text already present; physical Enter committed text instead of sending the partial draft. Trime's raw-preedit commit matched its on-screen Enter behavior in the same environment.
  • Blacksmith Testbox tbx_01kwxeq31y8q66en8zhv16stym (tidal-krill): pnpm native:i18n:check, focused ChatHardwareKeyTest for Play and third-party variants, both debug assemblies, and both debug lint tasks passed.
  • Focused tests cover Enter/numpad Enter, repeat and paired key-up ownership, modifier changes during a claimed sequence, disabled sends, blank newline filtering, public composition, and private IME input tracking.
  • Final structured Codex autoreview: clean with no accepted or actionable findings.
  • AI-assisted implementation; behavior and Compose 1.11.2 dependency contracts were reviewed directly and validated on the emulator.

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: M maintainer Maintainer-authored PR labels Jul 7, 2026
@joshavant
joshavant requested a review from a team as a code owner July 7, 2026 05:02
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord channel: mattermost Channel integration: mattermost channel: signal Channel integration: signal channel: telegram Channel integration: telegram channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web app: web-ui App: web-ui gateway Gateway runtime scripts Repository scripts commands Command implementations agents Agent runtime and tooling channel: feishu Channel integration: feishu extensions: openai extensions: minimax extensions: qa-lab extensions: codex extensions: tts-local-cli plugin: azure-speech Azure Speech plugin size: XL and removed size: M labels Jul 7, 2026
@github-actions github-actions Bot added the dependencies-changed PR changes dependency-related files label Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Dependency graph guard cleared

This PR no longer has blocked dependency graph changes. A future dependency graph change requires a fresh /allow-dependencies-change comment after the guard blocks that new head SHA.

  • Current SHA: 88b233c543712e1dc2cbff95ea0f1ad232b2550e

@joshavant
joshavant force-pushed the fix/android-hardware-enter-sequence branch from f078cea to 0e2eb71 Compare July 7, 2026 05:02
@openclaw-barnacle openclaw-barnacle Bot removed docs Improvements or additions to documentation channel: discord Channel integration: discord labels Jul 7, 2026
@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 7, 2026, 1:13 AM ET / 05:13 UTC.

Summary
The PR replaces Android chat composers' one-shot preview Enter handler with a shared pre-soft-keyboard hardware-key state machine and TextFieldValue adapter, plus i18n line updates and focused Robolectric tests.

PR surface: Other +273. Total +273 across 5 files.

Reproducibility: yes. The linked issue has a live Android 15/API 35 Gboard repro after the first fix, and current main source confirms repeated Enter returns false to the text field after the initial send.

Review metrics: 1 noteworthy metric.

  • Android composer coverage: 2 updated, 0 old chat handlers left in the touched surfaces. The earlier overlapping contributor PR missed the active ChatScreen path; this patch covers both the active composer and sheet composer with one handler.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #101239
Summary: This PR is the current candidate fix for the canonical Android hardware-keyboard Enter issue after the first merged fix left repeat and IME sequence handling incomplete.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] The PR is marked behind current main, so maintainers should refresh the branch or verify the merge result before landing even though GitHub reports it mergeable.

Maintainer options:

  1. Decide the mitigation before merge
    Land this PR or an equivalent shared Android hardware-key handler after exact-head CI, preserving IME ownership, Shift+Enter, and both composer surfaces.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No ClawSweeper repair job is needed because the patch has sufficient behavior proof and no concrete review finding; remaining work is ordinary maintainer review, rebase, and exact-head CI gating.

Security
Cleared: No concrete security or supply-chain concern found; the diff is limited to Android Kotlin UI code, tests, and generated i18n line metadata, and dependency/security guards are green.

Review details

Best possible solution:

Land this PR or an equivalent shared Android hardware-key handler after exact-head CI, preserving IME ownership, Shift+Enter, and both composer surfaces.

Do we have a high-confidence way to reproduce the issue?

Yes. The linked issue has a live Android 15/API 35 Gboard repro after the first fix, and current main source confirms repeated Enter returns false to the text field after the initial send.

Is this the best way to solve the issue?

Yes. The shared Android UI handler is the narrow owner-boundary fix because it covers both composer surfaces at Compose's pre-soft-keyboard hardware-key boundary without adding config, gateway, or protocol behavior.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6192b037bbb5.

Label changes

Label changes:

  • add P3: This is a contained Android hardware-keyboard chat ergonomics fix with the visible Send button and normal touch input still available.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes live Android 15/API 35 before/after proof for Gboard and Trime plus a Blacksmith Testbox run for the Android-focused validation, which directly exercises the changed input path.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes live Android 15/API 35 before/after proof for Gboard and Trime plus a Blacksmith Testbox run for the Android-focused validation, which directly exercises the changed input path.

Label justifications:

  • P3: This is a contained Android hardware-keyboard chat ergonomics fix with the visible Send button and normal touch input still available.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes live Android 15/API 35 before/after proof for Gboard and Trime plus a Blacksmith Testbox run for the Android-focused validation, which directly exercises the changed input path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes live Android 15/API 35 before/after proof for Gboard and Trime plus a Blacksmith Testbox run for the Android-focused validation, which directly exercises the changed input path.
Evidence reviewed

PR surface:

Other +273. Total +273 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 5 386 113 +273
Total 5 386 113 +273

What I checked:

  • Current main active handler still leaks repeats: Current main wires ChatInputPill through onPreviewKeyEvent, and handlePhysicalChatSend returns false for repeated Enter key-downs and key-up events, matching the linked issue's live repro of a newline leaking after send. (apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt:1572, 6192b037bbb5)
  • PR owns full Enter sequences: The new PhysicalChatSendKeyHandler tracks an owned Enter key through repeats and key-up, preserves modified/composing input, and filters blank hardware-newline updates without adding config or protocol surface. (apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatHardwareKey.kt:56, 0e2eb71e85fe)
  • Both Android composer surfaces use the shared handler: The PR updates both the active ChatScreen ChatInputPill and the sheet ChatComposer to use ChatTextFieldValueAdapter and onPreInterceptKeyBeforeSoftKeyboard before the text field inserts a newline. (apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt:1519, 0e2eb71e85fe)
  • Focused regression tests cover the new state machine: The PR tests simple Enter, numpad Enter, repeat and key-up ownership, disabled sends, blank newline filtering, modifiers, public composition, and private IME input tracking. (apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatHardwareKeyTest.kt:18, 0e2eb71e85fe)
  • Dependency contract supports the hook choice: AndroidX Compose documents onPreInterceptKeyBeforeSoftKeyboard as a hardware-key callback that runs before software keyboard handling and stops propagation when true; the PR uses that boundary instead of the later preview-key path.
  • Adapter mirrors Compose String TextField behavior: The PR's adapter follows Compose BasicTextField's String overload pattern of keeping TextFieldValue state to preserve selection/composition while forwarding String changes.

Likely related people:

  • steipete: Authored and merged the earlier current-main hardware Enter handler in ChatScreen and its focused test via the merged initial fix for the linked issue. (role: introduced current main handler; confidence: high; commits: 780ca1d25315; files: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt, apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatHardwareKeyTest.kt)
  • vincentkoc: Recent current-main Android chat work owns the sheet composer hardware Enter path and the underlying chat composer surfaces this PR now shares. (role: adjacent recent area contributor; confidence: medium; commits: de8db7405850, 62e5d4406d; files: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatComposer.kt, apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. labels Jul 7, 2026
@joshavant
joshavant force-pushed the fix/android-hardware-enter-sequence branch from 0e2eb71 to fea9a5b Compare July 7, 2026 05:15
@joshavant
joshavant force-pushed the fix/android-hardware-enter-sequence branch from fea9a5b to 88b233c Compare July 7, 2026 05:24
@joshavant

Copy link
Copy Markdown
Contributor Author

Land-ready at prepared head 88b233c543712e1dc2cbff95ea0f1ad232b2550e.

Evidence:

  • Live baseline on Android 15/API 35 at 780ca1d25315b34d3118e1db2d8dcafcc16415f3: plain physical Enter sent, but holding Enter sent once and leaked a repeated key-down newline into the cleared composer.
  • Patched Gboard matrix: plain Enter sends and clears; long-press/repeat sends once and stays empty; blank Enter remains empty; Shift+Enter inserts a newline; on-screen Enter remains text-input owned.
  • Patched Trime 3.3.11 matrix: active private preedit remains IME-owned instead of OpenClaw sending partial text. Trime's raw-preedit commit result matched its on-screen Enter behavior and is intentionally not normalized by the app.
  • Blacksmith Testbox tbx_01kwxeq31y8q66en8zhv16stym (tidal-krill), hydration run https://github.com/openclaw/openclaw/actions/runs/28842447981:
    corepack pnpm native:i18n:check && cd apps/android && ./gradlew :app:testPlayDebugUnitTest --tests ai.openclaw.app.ui.chat.ChatHardwareKeyTest :app:testThirdPartyDebugUnitTest --tests ai.openclaw.app.ui.chat.ChatHardwareKeyTest :app:assemblePlayDebug :app:assembleThirdPartyDebug :app:lintPlayDebug :app:lintThirdPartyDebug
  • Hosted CI run https://github.com/openclaw/openclaw/actions/runs/28843326331 passed all jobs on patch-identical head fea9a5b5cb791ce7e829776467f07222366ea595, including both Android test variants, Android build, Android lint, and native i18n. The repository prepare verifier reused that fresh evidence after rebasing onto unrelated main commits and completed for 88b233c543712e1dc2cbff95ea0f1ad232b2550e.
  • Final structured autoreview: no actionable findings.

Known proof gap: Trime's native raw-preedit text differs from transliterated commit behavior in this emulator setup, but physical and on-screen Enter match; the app-specific event ownership and composition behavior are covered.

@joshavant
joshavant merged commit 531c7a5 into main Jul 7, 2026
79 checks passed
@joshavant
joshavant deleted the fix/android-hardware-enter-sequence branch July 7, 2026 05:25
@joshavant

Copy link
Copy Markdown
Contributor Author

Merged via squash.

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

Labels

app: android App: android maintainer Maintainer-authored PR P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: Enter key should send message when physical keyboard is connected

1 participant