Skip to content

[Due for payment 2026-08-27] iOS - Expense - No focus on description field when cancel changes #97604

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


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:

  1. Go to iOS app
  2. Start creating an expense.
  3. Choose Manual, enter an amount, and go to the Description step.
  4. Type anything as description.
  5. Press Back to trigger the discard changes modal.
  6. Click Cancel.

Expected Result:

When modal closes focus should return to the description input

Actual Result:

Focus does not return to the description input

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

Bug7222917_1785519273170.ScreenRecording_07-31-2026_19-21-57_1.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7222917

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Jul 31, 2026
  2. applause-bot commented on Jul 31, 2026

    @applause-bot
    Author

    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

  3. MelvinBot commented on Jul 31, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    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.

    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 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.

    Investigation details

    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.

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Jul 31, 2026
  5. melvin-bot commented on Jul 31, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (External)

  6. melvin-bot commented on Jul 31, 2026

    @melvin-bot
  7. ikevin127 commented on Aug 1, 2026

    @ikevin127
    Contributor

    🟢 Accepting Proposal 1 (MelvinBot)

    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:

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

    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.

    ⚠️ Version context — please read before implementing

    I traced this across builds and there are a few moving pieces the assignee should be aware of:

    Build onCancel code Contains #96638? Repro
    9.4.46-x (production / App Store) inputRef.current?.focus() ❌ no reproduces on real iPhone
    9.4.47-0 (QA-reported build) focusComposerWithDelay(inputRef.current)(true) ✅ yes reproduces (QA)
    9.4.47-5 inputRef.current?.focus() (reverted) ↩️ reverted does not reproduce
    latest main focusComposerWithDelay(inputRef.current)(true) (re-applied) ✅ re-applied root cause present in code
    • 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.

    🎀👀🎀 C+ reviewed

  8. melvin-bot commented on Aug 1, 2026

    @melvin-bot

    Triggered auto assignment to @mountiny, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

  9. mountiny commented on Aug 4, 2026

    @mountiny
    Contributor

    @ikevin127 please go ahead

  10. 18 remaining items

  11. ikevin127 commented on Aug 20, 2026

    @ikevin127
    Contributor

    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):

    1. Start creating an expense: choose Manual, enter an amount, and continue to the Description step.
    2. Type any text in the Description input.
    3. Press Back to trigger the "Discard changes?" modal.
    4. Tap Cancel.
    5. Verify that when the modal closes, focus returns to the Description input and the soft keyboard is shown again.

    Regression check (All other platforms: Web / Android):

    1. Repeat steps 1–4 above.
    2. Verify that after Cancel the Description input still contains the typed text, focus is restored.

    Do we agree 👍 or 👎.

    Zapier Logs Run ID: 00040eee-5044-a5c7-2139-5325b9474fe4
  12. melvin-bot commented on Aug 21, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  13. melvin-bot commented on Aug 26, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  14. mallenexpensify commented on Aug 27, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @ikevin127 due $125 via NewDot

  15. ikevin127 commented on Aug 28, 2026

    @ikevin127
    Contributor

    @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 🙌

  16. mallenexpensify commented on Aug 28, 2026

    @mallenexpensify
    Contributor

    Thanks @ikevin127 , price updated that one slipped under my radar (even though you mentioned it above).

  17. quinthar commented on Sep 2, 2026

    @quinthar
    Contributor

    Approved $125 to @ikevin127

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions