You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-08-27] iOS - Expense - No focus on description field when cancel changes #97604
Version Number: 9.4.47-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:#96638 Email or phone of affected tester (no customers):gocemate+buzz1916@gmail.com Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: iphone 11/26.2 App Component: Other
Action Performed:
Go to iOS app
Start creating an expense.
Choose Manual, enter an amount, and go to the Description step.
Type anything as description.
Press Back to trigger the discard changes modal.
Click Cancel.
Expected Result:
When modal closes focus should return to the description input
When you press Back on the Description step, the useDiscardChangesConfirmation hook opens the discard modal and disables the input, and when you tap Cancel it tries to restore focus by calling focusComposerWithDelay(inputRef.current)(true) at IOURequestStepDescription.tsx:178-180.
The problem is that call omits the third argument, forceKeyboardIfAlreadyFocused. On iOS, after the modal closes the description TextInput still reports isFocused() === true, so focusComposerWithDelay early-returns without doing anything — RN's input.focus() is a no-op when the field is already focused, and the only way to re-open the soft keyboard in that state (requestKeyboardForFocusedComposer → KeyboardController.setFocusTo('current')) is gated behind that flag. Since the flag is never set, the keyboard/focus never comes back.
What changes do you think we should make in order to solve the problem?
In the discard-modal onCancel at IOURequestStepDescription.tsx:179, pass forceKeyboardIfAlreadyFocused = true so the keyboard is re-requested even when iOS still considers the input focused. This reuses the existing native re-focus path and only changes behavior on native (web helper stays a NOOP), so it targets the iOS regression without affecting web.
Early-return: focusComposerWithDelay/index.ts:37-49 — when getIsFocused() is true, it only re-requests the keyboard if forceKeyboardIfAlreadyFocused is set.
Related issue:#97127 ("After discarding changes and tapping Description composer jumps up, field not opened") stems from the same iOS focus/keyboard-restoration handling around this modal — worth validating any fix against it.
Confidence: High (code evidence is a deterministic client-side logic gap). Not verified on-device, since the forced-web test session can't reproduce an iOS-only keyboard behavior; the analysis actually predicts it won't repro on web.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/97604) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Root cause is accurate. In IOURequestStepDescription.tsx, the discard-modal onCancel calls:
focusComposerWithDelay(inputRef.current)(true);
This passes shouldDelay = true but leaves forceKeyboardIfAlreadyFocused at its default false. In focusComposerWithDelay/index.ts, when getIsFocused() returns true the helper only re-requests the keyboard if that flag is set, otherwise it early-returns. On iOS the description TextInput still reports isFocused() === true after the modal closes, so the call is a no-op and the soft keyboard never returns. This is iOS-App specific: the web requestKeyboardForFocusedComposer is a NOOP, while native calls KeyboardController.setFocusTo('current').
Fix is correct and minimal: pass the third argument so the native keyboard is re-requested even when iOS still considers the field focused:
PR Fix markdown cursor restore crash on IOU description focus #96638 introduced the focusComposerWithDelay(...)(true) change; it shipped in 9.4.47-0 (the build this issue was filed against). It was then reverted, then re-applied on main — so the buggy path is live on latest main and this fix is still needed.
Because this is a soft-keyboard timing behavior, it is environment-sensitive: it may not reproduce on a local dev / simulator HybridApp build (dev-mode JS timing, simulator software-keyboard differences, and a local native binary that differs from the shipped release), even though it reproduces in release on a real device. A "works locally" result should not be treated as authoritative here.
✅ Merge requirement
Given the above, we should ad-hoc test the PR on a release-mode build on a real iOS device (matching how QA caught it) to confirm the keyboard/focus is restored before merge, rather than relying on simulator verification.
There was 1 regression which the second PR ☝️ resolved - so payment should be half here when regression period of 2nd PR is over (27th).
BugZero Checklist:
[Contributor] The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to comment: N/A - Result of the original design
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
@mallenexpensify Small correction for me as previously mentioned in the note here, payment should be $125 since there was 1 regression, and that's the amount I'll request 🙌
Activity
applause-bot commentedon Jul 31, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/27e3d4401d771c8f01de9f0ce1e7655f3e36712f3752827b4987e6285355c8a2
MelvinBot commentedon Jul 31, 2026
Proposal
What is the root cause of that problem?
When you press Back on the Description step, the
useDiscardChangesConfirmationhook opens the discard modal and disables the input, and when you tap Cancel it tries to restore focus by callingfocusComposerWithDelay(inputRef.current)(true)atIOURequestStepDescription.tsx:178-180.The problem is that call omits the third argument,
forceKeyboardIfAlreadyFocused. On iOS, after the modal closes the descriptionTextInputstill reportsisFocused() === true, sofocusComposerWithDelayearly-returns without doing anything — RN'sinput.focus()is a no-op when the field is already focused, and the only way to re-open the soft keyboard in that state (requestKeyboardForFocusedComposer→KeyboardController.setFocusTo('current')) is gated behind that flag. Since the flag is never set, the keyboard/focus never comes back.This is iOS-App specific: the web version of that keyboard helper is a NOOP and DOM focus works regardless of keyboard state, which is why it doesn't reproduce on web.
What changes do you think we should make in order to solve the problem?
In the discard-modal
onCancelatIOURequestStepDescription.tsx:179, passforceKeyboardIfAlreadyFocused = trueso the keyboard is re-requested even when iOS still considers the input focused. This reuses the existing native re-focus path and only changes behavior on native (web helper stays a NOOP), so it targets the iOS regression without affecting web.Investigation details
useDiscardChangesConfirmation/index.native.ts:45-83— on a non-CONFIRMresult it callsonVisibilityChange(false)(re-enabling the input viaeditable={!isDiscardModalVisible}) and thenonCancel().focusComposerWithDelay/index.ts:37-49— whengetIsFocused()is true, it only re-requests the keyboard ifforceKeyboardIfAlreadyFocusedis set.requestKeyboardForFocusedComposer/index.native.ts(callsKeyboardController.setFocusTo('current')) vs the web NOOP — confirms iOS-specificity.Related issue: #97127 ("After discarding changes and tapping Description composer jumps up, field not opened") stems from the same iOS focus/keyboard-restoration handling around this modal — worth validating any fix against it.
Confidence: High (code evidence is a deterministic client-side logic gap). Not verified on-device, since the forced-web test session can't reproduce an iOS-only keyboard behavior; the analysis actually predicts it won't repro on web.
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/97604)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
melvin-bot commentedon Jul 31, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (
External)melvin-bot commentedon Jul 31, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022083279094934942330
ikevin127 commentedon Aug 1, 2026
🟢 Accepting Proposal 1 (MelvinBot)
Root cause is accurate. In
IOURequestStepDescription.tsx, the discard-modalonCancelcalls:This passes
shouldDelay = truebut leavesforceKeyboardIfAlreadyFocusedat its defaultfalse. InfocusComposerWithDelay/index.ts, whengetIsFocused()returnstruethe helper only re-requests the keyboard if that flag is set, otherwise it early-returns. On iOS the descriptionTextInputstill reportsisFocused() === trueafter the modal closes, so the call is a no-op and the soft keyboard never returns. This is iOS-App specific: the webrequestKeyboardForFocusedComposeris aNOOP, while native callsKeyboardController.setFocusTo('current').Fix is correct and minimal: pass the third argument so the native keyboard is re-requested even when iOS still considers the field focused:
It reuses the existing native re-focus path and leaves web untouched (NOOP), so it targets the iOS regression without side effects on other platforms.
I traced this across builds and there are a few moving pieces the assignee should be aware of:
onCancelcode9.4.46-x(production / App Store)inputRef.current?.focus()9.4.47-0(QA-reported build)focusComposerWithDelay(inputRef.current)(true)9.4.47-5inputRef.current?.focus()(reverted)mainfocusComposerWithDelay(inputRef.current)(true)(re-applied)focusComposerWithDelay(...)(true)change; it shipped in9.4.47-0(the build this issue was filed against). It was then reverted, then re-applied onmain— so the buggy path is live on latestmainand this fix is still needed.✅ Merge requirement
Given the above, we should ad-hoc test the PR on a release-mode build on a real iOS device (matching how QA caught it) to confirm the keyboard/focus is restored before merge, rather than relying on simulator verification.
🎀👀🎀 C+ reviewed
melvin-bot commentedon Aug 1, 2026
Triggered auto assignment to @mountiny, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
mountiny commentedon Aug 4, 2026
@ikevin127 please go ahead
18 remaining items
ikevin127 commentedon Aug 20, 2026
Note
There was 1 regression which the second PR ☝️ resolved - so payment should be half here when regression period of 2nd PR is over (27th).
BugZero Checklist:
[Contributor] The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to comment: N/A - Result of the original design
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue: https://github.com/Expensify/Expensify/issues/676218
Regression Test Proposal
Verify the fix (iOS App):
Regression check (All other platforms: Web / Android):
Do we agree 👍 or 👎.
Zapier Logs
Run ID: 00040eee-5044-a5c7-2139-5325b9474fe4melvin-bot commentedon Aug 21, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Aug 26, 2026
Payment Summary
Resolving PRs:
Fix: return focus and soft keyboard to description input on iOS after canceling discard modal #97823
Fix: gate forceKeyboardIfAlreadyFocused per-platform to remove Android discard-changes keyboard regression #98808
Reviewer: @ikevin127 owed $250 via NewDot
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Aug 27, 2026
Payment Summary
Contributor+: @ikevin127 due $125 via NewDot
ikevin127 commentedon Aug 28, 2026
@mallenexpensify Small correction for me as previously mentioned in the note here, payment should be $125 since there was 1 regression, and that's the amount I'll request 🙌
mallenexpensify commentedon Aug 28, 2026
Thanks @ikevin127 , price updated that one slipped under my radar (even though you mentioned it above).
quinthar commentedon Sep 2, 2026
Approved $125 to @ikevin127