Skip to content

[$175] Rules - Description bar is not correctly centered after user focus back on #106059

Open

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.5.32-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: #105457
Email or phone of affected tester (no customers): winnaire@gmail.com
Issue reported by: Applause Internal Team
Bug source: Pull Request QA execution
Device used: Pixel 7 pro / Android 16
App Component: Workspace Settings

Action Performed:

  1. Open https://staging.new.expensify.com/
  2. Open Workspace, and Select 'Rules'
  3. Tap on 'Add rule' at top left
  4. Locate 'Or just describe it below' section and tap to expand keyboard
  5. Insert 'Text the approver's phone whenever a report is submitted' and tap on 'Next'
  6. While keyboard is still expanded swipe to top so the previous section is out of range
  7. Tap anywhere in keyboard to insert a keyword and focus again on previous section

Expected Result:

The user should be scrolled to where the caret position is

Actual Result:

The user is not correctly to where the caret is positioned

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7305366_1793839636267.PR_105457_fail.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7305366

Upwork Automation - Do Not Edit

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Nov 5, 2026
  2. MelvinBot commented on Nov 5, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    Medium confidence: the description restores its visibility on focus, but not when typing resumes in an already-focused field. Scrolling away with the keyboard open need not blur the input, so subsequent typing does not trigger the existing scroll handler. This explains why the caret can remain off-screen.

    Code evidence and alternatives checked

    What changes do you think we should make in order to solve the problem?

    Add local visibility recovery when the description is edited while focused, limited to the scrolling form. Reveal the caret when obscured, preserve intentional scrolling until editing resumes, and coalesce requests to avoid jumping on every keystroke. Keep the existing initial-focus and refusal scrolling; preserve form value/draft handling.

    Validation required
    • Add a regression test that edits without firing another focus event. The current input mock:123–145 forwards only focus and cannot exercise this scenario.
    • Verify the exact sequence on Android and iOS, both before submission and after refusal. Include long multiline text, rapid typing, and the non-scrolling Describe modal. Scrolling to the box’s top alone must not be assumed to reveal every caret position.
    • Web verification was blocked by a Control-plan upgrade dialog before reaching the description. No caret failure or fix was verified; desktop web also cannot establish native software-keyboard behavior. No code changes were made.

    What alternative solutions did you explore? (Optional)

    Another focus-only handler cannot cover retained-focus typing. Dismissing the keyboard changes the intended editing flow rather than fixing caret visibility. A shared scrolling overhaul is broader than this issue currently warrants.

    Related issues: New rule scrolls in two jumps when the describe box gets focus and App does not scroll down to Describe field when field is focused concern initial focus; they are related, not confirmed duplicates of this resumed-editing case.


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/106059) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  3. MelvinBot commented on Nov 5, 2026

    @MelvinBot
    Contributor

    🤖 Web test steps verification results: ⚠️ partial

    Verified app access and Workspace Rules navigation on dev NewDot web. Add rule opened a Control-plan upgrade dialog, blocking form and caret checks; native keyboard behavior cannot be verified on desktop web.

    Control-plan upgrade dialog displayed after clicking Add rule

    Step Status
    Open appAuthenticated Home screen rendered.
    ✅
    Workspace > RulesCreated a workspace, enabled Rules, and opened the Rules page.
    ✅
    Add ruleClicked Add rule, but reached a Control-plan upgrade dialog rather than the rule form. No upgrade was authorized.
    Control-plan upgrade dialog displayed after clicking Add rule
    ⚠️
    Focus 'Or just describe it below'Blocked by the upgrade dialog; description field was not reached.
    ⚠️
    Enter "Text the approver's phone whenever a report is submitted" and press NextBlocked because the rule form was not reached.
    ⚠️
    Scroll away while preserving focus and keeping the keyboard expandedBlocked before description entry. Native keyboard expansion is also unverifiable on desktop web.
    ⚠️
    Type again and verify caret visibilityBlocked before description entry; caret behavior was not observed.
    ⚠️

    view run · view recording

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

    @melvin-bot

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

  6. changed the title [-]Rules - Description bar is not correctly centered after user focus back on[/-] [+][$175] Rules - Description bar is not correctly centered after user focus back on[/+] on Nov 5, 2026
  7. melvin-bot commented on Nov 5, 2026

    @melvin-bot
  8. khizzcr7 commented on Nov 5, 2026

    @khizzcr7

    Problem: In Workspace > Rules > Add rule, after typing a description and scrolling away while the keyboard stays open, tapping back into the description and typing again leaves the caret off-screen instead of scrolling the form to it.

    Root cause: In src/pages/workspace/rules/DescribeRuleForm.tsx, scrolling is attached only to focus: the description input has onFocus={scrollToInput}, and scrollToInput scrolls the form to the input container's top (inputTopOffsetRef from the container's onLayout). Scrolling away with the keyboard open does not blur the input, so typing again fires no focus event and no scroll runs — the caret can sit hidden below the keyboard or above the viewport. Even on focus, the scroll targets the container top, not the caret line.

    Fix:

    1. Reuse the existing scroll machinery on edit as well as focus: call scrollToInput from onChangeText/onSelectionChange on the description input, coalesced (e.g. via requestAnimationFrame or a short debounce) so it does not jump on every keystroke.
    2. Scroll to the caret, not just the container top: use the selection start from onSelectionChange to compute the caret line's offset and scroll that line above the keyboard, falling back to the existing container-top scroll when no selection is available.
    3. Only scroll when the caret is actually obscured (below the keyboard top or above the viewport), so intentional manual scrolling is preserved until editing resumes. Keep the existing initial-focus scroll and the refusal scroll untouched.

    Verification: Reproduce the exact steps (add rule, type a description, scroll away with the keyboard open, tap back in and type) and confirm the caret stays visible; confirm normal typing does not jump; confirm the refusal flow still scrolls to the refused rule. Add a regression test that edits the description without firing another focus event and asserts the scroll runs.

  9. camesenin commented on Nov 5, 2026

    @camesenin
  10. melvin-bot commented on Nov 5, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. vanshshah11 commented on Nov 5, 2026

    @vanshshah11

    Proposal for Issue #106059 — Rules description bar not centered after focus ($175)

    Please re-state the problem that we are trying to solve in this issue

    In Workspace → Rules → "Create a rule" form, when the user taps on a text input (e.g. the rule name or description field), the form scrolls to make the input visible (scrollToInput). However, the description bar does not remain centered/near the input — after the field gains focus and the form scrolls, the field's label/title bar is positioned off-screen or mis-aligned vertically, so the user can't see what they're typing into.

    What is the root cause of that problem?

    In src/pages/workspace/rules/DescribeRuleForm.tsx:

    • Line 301: const scrollToInput = () => formRef.current?.scrollTo(inputTopOffsetRef.current);
    • Line 363: onFocus={scrollToInput}

    The scrollToInput function is called only on onFocus. The problem is that onFocus fires once — when the input first gains focus. But the form may be scrolled by the system (keyboard avoidance), or the user may scroll manually. When the user taps another input lower on the form, the onFocus fires for that field, BUT:

    1. The scroll offset stored in inputTopOffsetRef (line 297) is set by onLayout of the input's container — but onLayout gives coordinates relative to the parent ScrollView, not accounting for the current scroll position of the keyboard avoidance view.

    2. The scrollTo(inputTopOffsetRef.current) uses the raw layout offset. If the keyboard is already visible (from a previously focused field), the form has already been scrolled by the keyboard-avoidance system. Scrolling to the raw inputTopOffset value without adjusting for the keyboard height leads to the input being positioned at the top of the visible area (or even partially hidden behind the keyboard) rather than centered.

    The core issue: scrollToInput does not account for the visible viewport height after keyboard avoidance has already shifted the scroll position. It scrolls to a fixed offset calculated from the field's position within the form, not from the field's actual visible position.

    What changes do you think we should make in code to solve the problem?

    Use scrollToInput correctly — instead of scrolling to a fixed offset, use formRef.current?.scrollTo({y: inputTopOffsetRef.current, animated: true}) and additionally ensure the input is centered in the visible area by accounting for the keyboard height.

    Specifically:

    1. Track the keyboard height (via KeyboardAvoidingView behavior or a useKeyboardHeight hook which is already available in the codebase).

    2. In scrollToInput, compute the target scroll position so that the input field is vertically centered in the area above the keyboard:

    const scrollToInput = () => {
        const keyboardHeight = keyboardHeightRef.current;
        const inputTop = inputTopOffsetRef.current;
        const formHeight = formRef.current?.getScrollHeight?.() ?? 0;
        const visibleHeight = Dimensions.get('window').height - keyboardHeight;
        const targetOffset = Math.max(0, inputTop - (visibleHeight / 2));
        formRef.current?.scrollTo({y: targetOffset, animated: true});
    };
    1. Also call scrollToInput on onContentSizeChange or onKeyPress so that if the field is already focused (no new onFocus fires) the form re-scrolls correctly.

    What specific scenarios should be covered by automated tests?

    • When an input gains focus and keyboard appears, the input should be visible and centered in the viewport above the keyboard
    • When switching between inputs in the form, each one should be properly scrolled into view
    • When the keyboard is dismissed and a field is tapped again, the input should be centered

    What alternative solutions did you explore?

    1. Using KeyboardAvoidingView with behavior="padding" — Already in use but alone doesn't provide centering; it only ensures the field is visible. The issue specifically asks for better positioning than just "visible."

    2. Using scrollToFocusedInput from React Native's ScrollViewTextInput — Not available in this codebase's setup.

    PR

    PR will be linked here once created.

  12. zaidrain06 commented on Nov 7, 2026

    @zaidrain06

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On mobile (Rules > Add expense rule > Describe your rule), when the user submits an impossible rule (for example, delete all expenses) so the assistant renders a long refusal alert below the input, then swipes up to read the refusal and starts typing again in the still-focused description input, the screen does not scroll the description input back into the visible center area above the keyboard. The user ends up typing blindly while the input bar remains off-screen or partially hidden.

    What is the root cause of that problem?

    1. In src/pages/workspace/rules/DescribeRuleForm.tsx:301, FormProvider sets shouldScrollChildToFocus={false} and relies on a manual scrollToInput callback (lines 255-261):
      const scrollToInput = useCallback(() => {
          setTimeout(() => {
              formRef.current?.scrollTo({y: promptInputBottomRef.current, animated: true});
          }, 100);
      }, []);
    2. On line 365, scrollToInput is only passed to onFocus={scrollToInput} on the rulePrompt InputWrapper:
      <InputWrapper
          InputComponent={TextInput}
          inputID={INPUT_IDS.RULE_PROMPT}
          value={promptText}
          onChangeText={setPromptText}
          onFocus={scrollToInput}
    3. When the rule generation returns a refusal (response.reason), the useEffect at lines 195-201 runs formRef.current?.scrollToEnd({animated: true}) while the TextInput retains focus and the soft keyboard stays open (FormScrollView uses keyboardShouldPersistTaps="handled").
    4. When the user manually scrolls up to inspect the refusal message and then types on the already-open keyboard (or taps inside the already-focused TextInput), onFocus does not fire again because the input never blurred. As a result, scrollToInput() is never triggered when typing resumes.

    What changes do you think we should make in order to solve the problem?

    In src/pages/workspace/rules/DescribeRuleForm.tsx, trigger scrollToInput() when the user edits text in the prompt input (in addition to onFocus), so typing after manually scrolling away immediately centers/scrolls the description input back into view above the keyboard:

    const handlePromptChange = useCallback(
        (text: string) => {
            setPromptText(text);
            scrollToInput();
        },
        [scrollToInput],
    );

    And pass onChangeText={handlePromptChange} (and onPress={scrollToInput} if supported / onFocus={scrollToInput}) to the INPUT_IDS.RULE_PROMPT InputWrapper at line 360-366:

    <InputWrapper
        InputComponent={TextInput}
        inputID={INPUT_IDS.RULE_PROMPT}
        value={promptText}
        onChangeText={handlePromptChange}
        onFocus={scrollToInput}

    What alternative solutions did you explore? (Optional)

    We also considered adding keyboardDismissMode="on-drag" on FormScrollView, which would blur the input when the user drags the scroll view so tapping it again fires onFocus. However, keeping the keyboard open while scrolling and calling scrollToInput() in handlePromptChange avoids unnecessary keyboard close/reopen animations and works even when the user types directly on the open keyboard after scrolling.

  13. jeffrey-graham commented on Nov 7, 2026

    @jeffrey-graham
  14. melvin-bot commented on Nov 7, 2026

    @melvin-bot
  15. melvin-bot commented on Nov 7, 2026

    @melvin-bot

    📣 @ronanchnlps-bit! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  16. TassioSales commented on Nov 8, 2026

    @TassioSales

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On mobile devices (Android and iOS) under Workspace > Rules > Add rule, when a user enters a description into the "Or just describe it below" input and taps Next, refocusing or typing in the field while the keyboard is up leaves the description input out of center or partially obscured behind the soft keyboard.

    What is the root cause of that problem?

    In DescribeRuleForm.tsx, the FormProvider retains input focus after submission (SUBMIT_ONLY behavior). When validation messages or helper text appear beneath the input, the scroll offset within FormWrapper / ScrollView shifts, but the component does not trigger a scroll-into-view adjustment when the input is refocused, causing the cursor and text box to remain misaligned with the visible viewport above the keyboard.

    What changes do you think we should make in order to solve the problem?

    1. In DescribeRuleForm.tsx:
      • Attach an onFocus handler to the description TextInput that invokes scrollIntoView or scrollToFocusedInput on the parent ScrollView ref.
      • When the form transitions states or displays feedback below the input, ensure the active input's bounding layout is recentered within the visible viewport above the virtual keyboard.
      • Adjust keyboardShouldPersistTaps / dismiss behavior so that keyboard transitions gracefully adjust scroll offsets.

    What alternative solutions did you explore? (Optional)

    Dismissing the keyboard unconditionally on Next, which disrupts fluid keyboard-driven input and violates the intended mobile rule creation UX.

  17. melvin-bot commented on Nov 10, 2026

    @melvin-bot

    @situchan Eep! 4 days overdue now. Issues have feelings too...

  18. situchan commented on Nov 11, 2026

    @situchan
    Contributor
  19. MelvinBot commented on Nov 11, 2026

    @MelvinBot
    Contributor

    🤖 Web test steps verification results: ⚠️ partial

    Baseline only on unmodified dev NewDot web; no fix was implemented or verified. Implementation is blocked: Add rule requires a paid upgrade, and the existing form scrolling API cannot establish obscured-caret visibility; simply scrolling on every edit would not safely fulfill the proposal’s multiline requirement.

    Step Status
    Open appAuthenticated Home displayed.
    ✅
    Open Workspace > RulesRules page displayed Add rule.
    ✅
    Add ruleBlocked by the Control-plan upgrade dialog; no upgrade accepted. Screenshot upload was unavailable.
    ⚠️
    Focus descriptionField inaccessible behind the upgrade gate.
    ⚠️
    Enter description and press NextForm inaccessible.
    ⚠️
    Scroll away while retaining focusBlocked; desktop web also cannot establish native software-keyboard behavior.
    ⚠️
    Resume typing and verify multiline caret visibilityNot verified. A Rules-enabled test workspace and native keyboard/caret validation are needed before safely completing this fix. No PR created.
    ⚠️

    view run · view recording

  20. situchan commented on Nov 13, 2026

    @situchan
    Contributor

    @MelvinBot do not block implementation on testing. I'll test myself.

  21. MelvinBot commented on Nov 13, 2026

    @MelvinBot
    Contributor

    Implemented in draft PR: Keep the rule description visible when editing resumes, assigned to you.

    27 targeted tests, typecheck, lint, and Storybook smoke passed. Full-suite timeout and pre-existing compiler diagnostics are documented in the PR. Manual testing is left to you.


    view run

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

Metadata

Metadata

Assignees

Labels

BugSomething 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