Skip to content

Fix: gate forceKeyboardIfAlreadyFocused per-platform to remove Android discard-changes keyboard regression - #98808

Open
MelvinBot wants to merge 2 commits into
mainfrom
claude-androidKeyboardDiscardRegression
Open

Fix: gate forceKeyboardIfAlreadyFocused per-platform to remove Android discard-changes keyboard regression#98808
MelvinBot wants to merge 2 commits into
mainfrom
claude-androidKeyboardDiscardRegression

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

#97823 fixed the iOS bug where the description field lost focus/keyboard after canceling the "Discard changes?" modal, by passing forceKeyboardIfAlreadyFocused = true to focusComposerWithDelay in the discard-modal onCancel handler:

focusComposerWithDelay(inputRef.current)(true, undefined, true);

That third argument is a platform-agnostic flag gating a platform-divergent native action. requestKeyboardForFocusedComposer (called under that flag) only has an index.ts (web NOOP) and an index.native.ts, so Android silently inherited a fix that was reasoned about and QA-verified only for iOS — and it regressed: after tapping Cancel on Android Native the keyboard no longer returns.

Why the two platforms diverge under the same flag:

  • iOSKeyboardController.setFocusTo('current') is reloadInputViews + focus on the current first responder: cheap and idempotent. The input never lost first-responder status (only editable={false} suppressed the IME), so re-requesting it is exactly right.
  • Android — it resolves to ReactEditText.requestFocusProgrammatically(), whose showSoftKeyboard() is gated behind isInTouchMode. The discard modal is a separate Dialog window and the back path (useDiscardChangesConfirmation/index.native.ts wires it to hardwareBackPress) can leave the app window out of touch mode, so super.requestFocus() succeeds (caret returns) but showSoftKeyboard() is skipped — focus without keyboard. On Android the early-return was already correct: once the Dialog dismisses, the system restores focus and the IME on its own.

Fix: gate the flag at our call site only, via a platform-resolved helper — no shared file is touched.

This follows the same platform-resolved-file pattern as src/libs/shouldBreakAccessibilityGrouping/ rather than an inline Platform.OS check.

Why it's safe: Metro resolves the helper to index.ts on Android (no index.android.ts/index.native.ts exists) and to index.ios.ts on iOS. On Android the call becomes (true, undefined, false)argument-identical to pre-#97823 main, not just similar — so the regression is removed. iOS keeps (true, undefined, true). Web/mWeb are unaffected (requestKeyboardForFocusedComposer/index.ts is a NOOP), and ComposerWithSuggestions is untouched since no shared file changed.

Unit test: the one-liner was previously untestable, which is part of why this shipped unverified twice. tests/ui/IOURequestStepDescriptionTest.tsx now mocks the helper and pins both platform behaviors: helper truefocusComposerWithDelay called with (true, undefined, true); helper false(true, undefined, false). Either assertion fails loudly if the argument is ever hardcoded again during a cleanup.

Fixed Issues

$ #97604
PROPOSAL:

Tests

  1. Android: Native — 3-button nav, hardware back button. Manual expense → amount → Description, type text, press the hardware back button, tap Cancel on "Discard changes?". Verify the text is preserved, focus returns to the field, and the soft keyboard comes back. (This is the specific path the regression was reported on.)
  2. Android: Native — gesture nav, back swipe. Same flow via back swipe; verify identical behavior.
  3. Android: Native — header back arrow. Same flow via the in-app back arrow (a touch event); verify identical behavior.
  4. iOS: Native. Same flow; verify the Fix: return focus and soft keyboard to description input on iOS after canceling discard modal #97823 fix still works (focus + keyboard return) and was not gated off.
  5. ComposerWithSuggestions containment check (Android + iOS). Open a chat, tap an existing message to edit it; verify the keyboard appears. Should be unchanged (no shared file modified).
  6. Web / mWeb Safari. Same Description flow; text preserved, input refocused, no console errors.

Automated: tests/ui/IOURequestStepDescriptionTest.tsx pins both platform argument values (helper true(true, undefined, true); helper false(true, undefined, false)).

  • Verify that no errors appear in the JS console

Offline tests

Identical to the Tests above. This change only affects focus and keyboard behavior and never touches the network, so every step should behave the same with the connection off.

QA Steps

Same as tests. The Android: Native, hardware back button path (test 1) is the critical one — QA on a physical device / release build is required to confirm the soft keyboard returns after Cancel, since this native touch-mode behavior can't be verified in a dev simulator.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I verified that similar component doesn't exist in the codebase
  • I verified that all props are defined accurately and each prop has a /** comment above it */
  • I verified that each file is named correctly
  • I verified that each component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
  • I verified that the only data being stored in component state is data necessary for rendering and nothing else
  • In component if we are not using the full Onyx data that we loaded, I've added the proper selector in order to ensure the component only re-renders when the data it is using changes
  • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
  • I verified that component internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
  • I verified that all JSX used for rendering exists in the render method
  • I verified that each component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions

Screenshots/Videos

Android: Native

Automated smoke check of the Description discard-changes Cancel flow on standalone Android NewDot (Melvin app-android-tester). Text is preserved and focus returns to the field with no crash. Keyboard visibility could not be independently confirmed by the harness — it injects text via its own IME helper rather than a rendered soft keyboard — so the hardware-back keyboard-return behavior still needs QA on a device build (Tests step 1).

Description field with 'test note' typed, before pressing back Discard changes prompt shown after system back press Description field after tapping Cancel, showing 'test note' preserved and field focused
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

MelvinBot and others added 2 commits August 18, 2026 00:18
…d discard-keyboard regression

Co-authored-by: Kevin Brian Bader <ikevin127@users.noreply.github.com>
Co-authored-by: Kevin Brian Bader <ikevin127@users.noreply.github.com>
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
.../request/step/DynamicIOURequestStepDescription.tsx 79.45% <ø> (ø)
...s/shouldForceKeyboardIfAlreadyFocused/index.ios.ts 0.00% <0.00%> (ø)
.../libs/shouldForceKeyboardIfAlreadyFocused/index.ts 0.00% <0.00%> (ø)
... and 9 files with indirect coverage changes

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No code fix is needed — every real CI check on this PR is now green.

When you filed this request, several checks were still pending (all test (job N), typecheck, branch-perf-tests, baseline-perf-tests). They have all since finished and passed. The only remaining red is Check independent approval, which is not a code/test failure — it's an approval-gate workflow that stays failing until the PR has an independent human approval. Pushing code can't clear it.

Current check status
  • Passing: Bun tests, all test (job 1–8), typecheck, ESLint, Oxfmt, React Compiler Compliance, Storybook tests, spellcheck, check, dryRun, branch-perf-tests, baseline-perf-tests, all build (...) jobs, CLA, verifySignedCommits, etc.
  • Failing: Check independent approval only — requires a reviewer's approval, not a code change.

Next step: get an independent reviewer to approve the PR and that check will clear. Nothing to push here.

@ikevin127

This comment was marked as resolved.

@MelvinBot
MelvinBot marked this pull request as ready for review August 18, 2026 00:54
@MelvinBot
MelvinBot requested a review from a team as a code owner August 18, 2026 00:54
@melvin-bot
melvin-bot Bot requested a review from ikevin127 August 18, 2026 00:54
@melvin-bot

melvin-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

@ikevin127 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from joekaufmanexpensify and removed request for a team August 18, 2026 00:54
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done, ikevin127:

  1. ✅ Updated the PR description with the full markdown you provided (all checklist boxes checked, Explanation/Tests/Offline/QA sections and the Android: Native screenshots).
  2. ✅ Marked the PR as Ready for review (no longer a draft).

@ikevin127

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
screen-20260817-170441-1787011447337.mp4
Android: mWeb Chrome
screen-20260804-170521-1785888299711.mp4
iOS: HybridApp
Simulator.Screen.Recording.-.iPhone.17.Pro.Max.-.2026-08-04.at.16.53.57.mov
iOS: mWeb Safari
ScreenRecording_08-04-2026.16-49-16_1.MP4
MacOS: Chrome / Safari
Screen.Recording.2026-08-04.at.16.42.03.mov

@ikevin127 ikevin127 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LGTM - Follow-up PR to fix Android specific regression by gating previous PRs logic specifically to iOS, making sure Android is not affected by the change

@melvin-bot
melvin-bot Bot requested a review from mountiny August 18, 2026 00:58
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