Skip to content

[$175] Split-When splitting second expense in group, Amount field gets overlapped #102615

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.97-0
Reproducible in staging?: Yes
Reproducible in production?: No
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/folder/57602041/test-cases/41237802
Email or phone of affected tester (no customers): N/A
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: iPhone 15 iOS 26.6.1
App Component: Money Requests

Action Performed:

  1. Create a group chat with two other users
  2. In the chat tap "+" button - Split expense - Manual
  3. Enter an amount and split the expense
  4. Repeat step 2, tap the amount field to enter an amount

Expected Result:

Focused Amount field is visible when creating a second split in the group chat

Actual Result:

Focused Amount field gets overlapped with the Split expense button when creating a second split in the group chat

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7273108_1790737059859.Amount_in_split.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Mita Rachim
Bug ID: 7273108

Upwork Automation - Do Not Edit

Activity

  1. applause-bot commented on Sep 30, 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/13cbd8111cb40883a47681e57e6f917babd4d8130c4db93ea27280d8ddb74ad4

  2. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 30, 2026
  3. melvin-bot commented on Sep 30, 2026

    @melvin-bot

    Triggered auto assignment to @marcochavezf (DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.

  4. melvin-bot commented on Sep 30, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  5. github-actions commented on Sep 30, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  6. MelvinBot commented on Sep 30, 2026

    @MelvinBot
    Contributor

    ⚠️ Preliminary analysis (low confidence): use this as a starting point, not a conclusion.

    I didn't find an App PR in 9.4.97 that plausibly causes this, so I'm not recommending a revert. None of the code behind this screen changed between production (9.4.95-4, 07da298) and staging (9.4.97-0, c318e93).

    Recommendation: Ask QA to retest on production 9.4.95-4. Create two splits in a row in the same group chat and focus the amount field on the second one. Try it several times.

    • If it reproduces on production: demote it. This is an iOS-only overlap between the keyboard and the footer, with no data loss.
    • If it only reproduces on staging: an internal engineer should review the native submodule bump in this release for iOS keyboard changes. I couldn't access that repo.
    Investigation details

    The screen: In a group chat, "Split expense" > Manual shows the confirmation form directly. That form has an inline amount field and a "Split expense" button at the bottom (IOURequestStartPage.tsx:262). This flow is already in production.

    Why it might only happen the second time (a hypothesis I haven't verified):

    • The code notes that closing and reopening the pane often reuses the same mounted instance (AmountField.tsx:131).
    • The scroll-to-focused-input hook stores a scroll offset (useScrollToFocusedInput/index.native.ts:34). It also depends on when the keyboard event fires (L101-L111).
    • A reused instance plus that timing sensitivity would point to an older, intermittent bug rather than a regression.

    Unchanged between production and staging: src/pages/iou/request/, MoneyRequestAmountForm, NumberWithSymbolForm, MoneyRequestAmountInput, ScreenWrapper/, KeyboardAvoidingView/, FixedFooter, all of SelectionList/ (including the scroll hook), AmountField.tsx and MoneyRequestConfirmationFields/.

    Candidates ruled out:

    PR Why it's ruled out
    feat: use floating RHP style on web Its @react-navigation/stack patch doesn't run on iOS, which uses the native stack (index.native.tsx:18). Its overlay and modal changes only apply on wide layouts (RightModalNavigator.tsx:289).
    MenuItem decomposition - html value It only changes the read-only description row. A new split uses the editable input, which didn't change.

    The other PRs in the checklist don't touch this flow. I couldn't view the bug video, so these findings come from the issue text and the code only.


    Next Steps: Reply with @MelvinBot <your feedback> to refine this analysis.


    view run

  7. mitarachim commented on Sep 30, 2026

    @mitarachim

    Issue not reproduce in all PR's in this deploy, but reproduce in iOS v9.4.97-0 (staging)

  8. added
    ExternalAdded to denote the issue can be worked on by a contributor
    and removed
    DeployBlockerCashThis issue or pull request should block deployment
    on Sep 30, 2026
  9. melvin-bot commented on Sep 30, 2026

    @melvin-bot

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

  10. changed the title [-]Split-When splitting second expense in group, Amount field gets overlapped[/-] [+][$175] Split-When splitting second expense in group, Amount field gets overlapped[/+] on Sep 30, 2026
  11. melvin-bot commented on Sep 30, 2026

    @melvin-bot
  12. marcochavezf commented on Sep 30, 2026

    @marcochavezf
    Contributor

    Demoted since it might be fixed in the current PRs to deploy

  13. melvin-bot commented on Oct 5, 2026

    @melvin-bot

    @marcochavezf, @TaduJR Eep! 4 days overdue now. Issues have feelings too...

  14. yuvrajangadsingh commented on Oct 7, 2026

    @yuvrajangadsingh
    Contributor

    Proposal

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

    On iOS, tapping the inline Amount field on a group chat split lifts the Split expense button above the keyboard and the button lands on top of the focused field. The form under it never scrolls, so the user types into a field they can't see.

    What is the root cause of that problem?

    The split confirmation is the embedded confirmation list on the start page, inside a ScreenWrapper with keyboard avoiding turned on.

    <IOURequestStepConfirmationContent
    route={route}
    navigation={navigation}
    shouldHideHeader
    />

    <ScreenWrapper
    shouldEnableKeyboardAvoidingView
    shouldEnableMaxHeight={selectedTab === CONST.TAB_REQUEST.PER_DIEM || shouldEnableManualConfirmationMaxHeight}
    shouldEnableMinHeight={canUseTouchScreen()}
    testID="IOURequestStartPage"

    On iOS that wrapper is keyboard-controller's KeyboardAvoidingView with the padding behavior, so when the keyboard opens the whole screen gets bottom padding equal to the keyboard height.

    <KeyboardAvoidingView
    style={[styles.w100, styles.h100, !isBlurred ? {maxHeight} : undefined, isAvoidingViewportScroll ? [styles.overflowAuto, styles.overscrollBehaviorContain] : {}]}
    behavior={keyboardAvoidingViewBehavior}
    enabled={shouldEnableKeyboardAvoidingView}
    // Whether the mobile offline indicator or the content in general

    import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native-keyboard-controller';
    function BaseKeyboardAvoidingView(props: KeyboardAvoidingViewProps) {
    return <KeyboardAvoidingViewComponent {...props} />;
    }

    Inside it, the confirmation list renders the inline fields as the FlashList's footer content and the Split expense button as a sticky FixedFooter under the list. The padding shrinks the list viewport and the sticky footer rides up with it, but the FlashList keeps its content offset. Any inline input sitting below the new footer top is now covered.

    <SelectionListWithSections<MoneyRequestConfirmationListItem>
    ref={listRef}
    sections={sections}
    ListItem={BareUserListItem}
    onSelectRow={navigateToParticipantPage}
    onDismissError={dismissParticipantRowError}
    shouldSingleExecuteRowSelect
    shouldPreventDefaultFocusOnSelectRow
    shouldShowListEmptyContent={false}
    footerContent={footerContent}
    listFooterContent={listFooterContent}
    style={selectionListStyle}
    disableKeyboardShortcuts
    />

    {!!footerContent && (
    <Footer<ListItem>
    footerContent={footerContent}
    addBottomSafeAreaPadding={addBottomSafeAreaPadding}
    />
    )}

    if (footerContent) {
    return (
    <FixedFooter
    style={styles.mtAuto}
    addBottomSafeAreaPadding={addBottomSafeAreaPadding}
    >
    {footerContent}
    </FixedFooter>
    );

    The description field hit exactly this in #94274. PR #94398 fixed it by giving the list useScrollToFocusedInput: the field calls scrollFocusedInputIntoView from its onFocus, the hook waits for the keyboard-controller keyboardDidShow event, measures the field against the list container's top, and scrolls the list so the field sits at the top of the visible area.

    container.measureInWindow((containerX, containerY) => {
    input.measureInWindow((inputX, inputY) => {
    const target = containerY + EXTRA_SCROLL_PADDING;
    const delta = inputY - target;
    // The input is already at or above the target anchor, so there's nothing to do.
    if (delta <= 0) {
    return;
    }
    list.scrollToOffset({offset: scrollOffsetRef.current + delta, animated: true});
    });

    keyboardListenerRef.current = KeyboardEvents.addListener('keyboardDidShow', () => {
    cleanup();
    // Give the layout a moment to settle after the keyboard has fully appeared.
    scrollTimeoutRef.current = setTimeout(performScroll, CONST.ANIMATION_IN_TIMING);
    });

    ref={fieldContainerRef}
    style={[styles.mh4, styles.mv2]}
    >
    <TextInput
    value={iouComment ?? ''}
    readOnly={didConfirm}
    onChangeText={handleDescriptionInputChange}
    onFocus={() => scrollFocusedInputIntoView?.(fieldContainerRef.current)}

    The amount field never got that wiring. It renders NumberWithSymbolForm with no onFocus at all and doesn't read the scroll callback from the fields context, even though the context exposes it for every inline field.

    {!isAmountFieldDisabled ? (
    <View style={[styles.mh4, styles.mv2]}>
    <NumberWithSymbolForm
    ref={amountInputRef}
    displayAsTextInput
    autoFocus={false}
    value={transactionAmount}
    decimals={decimals}
    currency={effectiveCurrency}
    symbol={getLocalizedCurrencySymbol(preferredLocale, effectiveCurrency) ?? ''}
    label={translate('iou.amount')}
    errorText={amountFieldErrorText}
    onInputChange={handleAmountChange}
    allowNegativeInput={allowNegative}
    shouldShowFlipButton
    shouldShowCurrencyButton
    shouldShowBigNumberPad={false}
    onCurrencyButtonPress={showCurrencyPicker}
    disabled={isAmountFieldDisabled}
    />
    </View>

    /** Scrolls the surface so an inline field's input is not hidden behind the keyboard when focused (new manual expense flow). */
    scrollFocusedInputIntoView?: (input: MeasurableInput) => void;

    The merchant input has the same gap, its onFocus only flips a local focused flag.

    <View style={[styles.mh4, styles.mv2]}>
    <TextInput
    value={merchantInput}
    readOnly={didConfirm}
    onChangeText={handleMerchantInputChange}
    onFocus={() => {
    setIsMerchantInputFocused(true);
    }}
    onBlur={() => {
    setIsMerchantInputFocused(false);

    Why a group split and not a 1:1 expense: in a 1:1 manual expense the amount input sits near the top of the form, above where the footer lands. In a group split it sits under a Paid by row plus one row per participant, which is where the video shows it. The keyboard fully opens with the field still visible, then the footer lands on it and the rows above never move. I couldn't find App state that differs between the first and second split in the chat. The field is covered on any open where the footer lifts over it while the list is still at its top, and the fix below removes that ordering dependency instead of chasing it.

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

    Give the amount field the same opt-in the description field has: a ref on its outer container and an onFocus that asks the list to scroll the field into view. The container ref is what gets measured, so the label and top border come into view too, same as the description field.

    const {isEditingSplitBill, isReadOnly, didConfirm, transactionID, action, iouType, reportID, reportActionID} = useConfirmationFields();

    {!isAmountFieldDisabled ? (
    <View style={[styles.mh4, styles.mv2]}>
    <NumberWithSymbolForm
    ref={amountInputRef}
    displayAsTextInput
    autoFocus={false}
    value={transactionAmount}
    decimals={decimals}
    currency={effectiveCurrency}
    symbol={getLocalizedCurrencySymbol(preferredLocale, effectiveCurrency) ?? ''}
    label={translate('iou.amount')}
    errorText={amountFieldErrorText}
    onInputChange={handleAmountChange}
    allowNegativeInput={allowNegative}
    shouldShowFlipButton
    shouldShowCurrencyButton
    shouldShowBigNumberPad={false}
    onCurrencyButtonPress={showCurrencyPicker}
    disabled={isAmountFieldDisabled}
    />
    </View>

    const {scrollFocusedInputIntoView, isEditingSplitBill, isReadOnly, /* ...existing */} = useConfirmationFields();
    const fieldContainerRef = useRef<View>(null);
    ...
    <View
        ref={fieldContainerRef}
        style={[styles.mh4, styles.mv2]}
    >
        <NumberWithSymbolForm
            // ...existing props
            onFocus={() => scrollFocusedInputIntoView?.(fieldContainerRef.current)}
    • NumberWithSymbolForm already forwards onFocus to its text input in displayAsTextInput mode, so nothing changes there.

    onFocus={props.onFocus}

    • On current main the amount input already has an onFocus that sets the automatic-hint focused flag. The scroll call goes into that handler and the ref goes on the outer row view.
    • Same two lines on the merchant input, which sits even lower on the form.
    • The hook waits for keyboardDidShow plus 100 ms before measuring, so the padding and the shrunken viewport are already in place when the scroll runs. The scroll is clamped by the range that viewport allows, and since the merchant, description and date rows sit below the amount, even a clamped scroll leaves the amount above the footer.
    • Web keeps the no-op version of the hook, so nothing changes there.

    Tests: Not run yet, this is what I'd add with the PR:

    • iOS and Android, group chat with two other members, Split expense > Manual, tap Amount: the field scrolls up and stays visible above the Split expense button, typing shows.
    • Repeat in the same chat after splitting once, same result.
    • Tap Merchant with the keyboard already open from Amount: the list moves again, no wait for a keyboard event.
    • 1:1 manual expense and the standalone confirmation RHP: Amount still visible, no scroll jump when it already fits.

    What alternative solutions did you explore? (Optional)

    • Make useScrollToFocusedInput scroll whatever input is focused on keyboardDidShow, using TextInput.State.currentlyFocusedInput(). It would cover every inline field at once, but the hook is shared with Table, where the search bar opts in explicitly, and changing that contract for every list is more than this bug needs.
    • Set automaticallyAdjustKeyboardInsets on the FlashList. iOS only, and the screen already pads for the keyboard, so the list would get the space twice.
    • Turn off keyboard avoiding for this screen so the footer stays under the keyboard. The field would be visible, but the Split expense button wouldn't be, which is the state PR Android & iOS - Expense - Description field is not visible when focused #94398 moved away from.
  15. melvin-bot commented on Oct 7, 2026

    @melvin-bot

    @marcochavezf, @TaduJR Still overdue 6 days?! Let's take care of this!

  16. marcochavezf commented on Oct 8, 2026

    @marcochavezf
    Contributor

    I think this might be already fixed, feel free to re-open or comment if it's still reproducible

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2EngineeringExternalAdded to denote the issue can be worked on by a contributorNeeds ReproductionReproducible steps needed

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions