Skip to content

Fix Pronouns page Save button hidden by Android navigation bar#93773

Merged
lakchote merged 3 commits into
mainfrom
claude-pronounsSaveButtonSafeArea
Jun 17, 2026
Merged

Fix Pronouns page Save button hidden by Android navigation bar#93773
lakchote merged 3 commits into
mainfrom
claude-pronounsSaveButtonSafeArea

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

The Save button on the Pronouns page renders inside a FixedFooter (via SelectionList's confirmButtonOptions). The page's ScreenWrapper uses includeSafeAreaPaddingBottom={false}, and the SelectionList was not passing addBottomSafeAreaPadding (which defaults to false). With no bottom safe-area inset from either source, the Save button sat flush against the bottom edge and was hidden behind the Android device navigation bar.

This adds addBottomSafeAreaPadding to the SelectionList, routing the safe-area inset through FixedFooter so the Save button clears the navigation bar. This matches the established pattern used by RulesReimbursableDefaultPage (changed in the same PR that introduced this regression).

Fixed Issues

$ #93738
PROPOSAL: #93738 (comment)

Tests

Precondition: Set device navigation to bar instead of gestures.

  1. Launch Expensify app.
  2. Go to Account > Profile > Pronouns.
  3. Dismiss the keyboard.
  4. Verify: Save button on Pronouns page will be above device navigation bar.
  • Verify that no errors appear in the JS console

Offline tests

N/A — UI-only safe-area padding change.

QA Steps

Precondition: Set device navigation to bar instead of gestures.

  1. Launch Expensify app.
  2. Go to Account > Profile > Pronouns.
  3. Dismiss the keyboard.
  4. Verify: Save button on Pronouns page will be above device navigation bar.
  • 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 / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • 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 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 the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • 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 shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • 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 approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • 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)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • 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
    • 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
      • 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(themeColors.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 a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • 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 author checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

Co-authored-by: truph01 <truph01@users.noreply.github.com>
@MelvinBot MelvinBot requested a review from a team June 17, 2026 07:03
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I created this PR from an upstream branch since I don't have push access to your fork.

To take ownership of this branch and be able to push updates, run:

git remote add upstream https://github.com/Expensify/App.git
git fetch upstream claude-pronounsSaveButtonSafeArea
git checkout -b claude-pronounsSaveButtonSafeArea upstream/claude-pronounsSaveButtonSafeArea
git push -u origin claude-pronounsSaveButtonSafeArea

Then you can close this PR and open a new one from your fork.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only failing check is PR Author Checklist / checklist — all other checks are pending or passing.

This check fails because the PR template requires a human co-author to:

  • Fill out the ### Tests and ### QA Steps sections (currently // TODO placeholders)
  • Check the boxes in the PR Author Checklist (0 of 49 completed)

These items can't be completed by MelvinBot — they need the human co-author to verify and tick. Once you fill those out, the checklist check will pass.

There's nothing to fix in the code: the actual code/lint/typecheck/test jobs are not failing.

@truph01

truph01 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Mark this PR as ready for review

@MelvinBot MelvinBot marked this pull request as ready for review June 17, 2026 07:08
@MelvinBot MelvinBot requested a review from a team as a code owner June 17, 2026 07:08
@melvin-bot melvin-bot Bot requested review from flaviadefaria and truph01 and removed request for a team June 17, 2026 07:08
@melvin-bot

melvin-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

@truph01 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 removed the request for review from a team June 17, 2026 07:08
@truph01

truph01 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot The current implementation works well when the keyboard is not open. However, while the keyboard is opening, an extra unnecessary space is added below the button.

Co-authored-by: truph01 <truph01@users.noreply.github.com>
@MelvinBot MelvinBot requested a review from a team as a code owner June 17, 2026 08:35
@melvin-bot melvin-bot Bot requested review from lakchote and removed request for a team June 17, 2026 08:35
@melvin-bot

melvin-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

@lakchote 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]

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed in d04f839.

Cause: The Save button lives in a FixedFooter rendered by SelectionList's Footer. The footer's bottom safe-area padding was applied unconditionally, but when the keyboard opens, the footer is pushed above the keyboard and the navigation-bar inset is no longer relevant — so that inset became an extra gap below the button.

Fix: Skip the footer's safe-area padding while the keyboard is shown, mirroring the existing logic the list already uses for its non-footer container at BaseSelectionList.tsx:127:

src/components/SelectionList/BaseSelectionList.tsx:658

addBottomSafeAreaPadding={!isKeyboardShown && addBottomSafeAreaPadding}

When the keyboard is closed, behavior is unchanged — the button still clears the Android navigation bar (the original fix). When the keyboard is open, the extra space is gone.

Checks run locally
  • prettier — clean
  • lint-changed — clean
  • typecheck (tsgo) — clean
  • react-compiler-compliance-check — the one reported error is at line 95 and pre-exists this PR (present on the parent commit before my change), so it is not a regression from this fix.
  • Could not run the Jest suite in this environment (interactive approval unavailable). This is a UI-only, keyboard-state change best verified on an Android device per the QA steps.

@truph01

truph01 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot What about adding includeSafeAreaPaddingBottom to:

        <ScreenWrapper
            includeSafeAreaPaddingBottom
            testID="PronounsPage"
        >

in that page?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Good question, but includeSafeAreaPaddingBottom on the ScreenWrapper is not the right fix here — it would pad the wrong layer.

Why the current approach is correct: The Save button renders inside a FixedFooter (via SelectionList's confirmButtonOptions), and FixedFooter applies the bottom safe-area inset to the footer itself through addBottomSafeAreaPaddinguseBottomSafeSafeAreaPaddingStyle. That's exactly the element being clipped by the Android nav bar, so the inset has to land there.

includeSafeAreaPaddingBottom instead pads the outer ScreenWrapper container. With a SelectionList (scrollable list + sticky footer) that manages its own bottom layout, padding the container is the wrong layer — it pads the whole content region rather than lifting the sticky Save button, and it doesn't compose with the FixedFooter's own inset handling. This is why the established pattern (e.g. RulesReimbursableDefaultPage, changed in the same PR that caused this regression) routes the inset through addBottomSafeAreaPadding on the list, not the wrapper.

Two more reasons
  • Keyboard handling: the second commit gates the footer inset on !isKeyboardShown in BaseSelectionList so the Save button doesn't get extra padding while the keyboard covers the nav bar. The ScreenWrapper prop gives no equivalent per-footer, keyboard-aware control.
  • Edge-to-edge migration: the legacy includeSafeAreaPaddingBottom prop is explicitly force-disabled once a screen opts into edge-to-edge mode (ScreenWrapper/index.tsx:138). The granular addBottomSafeAreaPadding props are the forward-compatible path; leaning on the wrapper prop moves against that migration.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The failing checks are not caused by this PR's changes, so I won't push a code fix.

1. test (job 2) — unrelated test failure

The failing test is:

ReportUtils › pushTransactionAutoSelectionsOnyxData › auto-selects the sole remaining enabled category when the transaction category is disabled (tests/unit/ReportUtilsTest.ts)

This PR only changes two UI files — src/components/SelectionList/BaseSelectionList.tsx and src/pages/settings/Profile/PronounsPage.tsx (safe-area padding). The failing test exercises IOU transaction category auto-selection logic in ReportUtils, which has no code path connection to either file. The log also shows A worker process has failed to exit gracefully (test teardown/timing), a common flaky-test signature.

Recommendation: Merge the latest main into the branch and re-run, or re-run the failed job. Per policy I don't modify unrelated/flaky tests.

2. checklist — requires the human co-author

This is the PR Author Checklist / checklist check. It fails because the ### Tests / ### QA Steps sections are still // TODO placeholders and the author-checklist boxes are unchecked. These can only be completed by the human co-author, not by MelvinBot.

@truph01

truph01 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot However, the current change modifies src/components/SelectionList/BaseSelectionList.tsx, which is a widely used component across the app. We can't be confident that this change won't introduce regressions or break other UI flows without broader validation.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

You're right to scrutinize a shared-component change, but the blast radius here is narrow and the change actually makes BaseSelectionList more internally consistent rather than introducing novel behavior.

What the change affects: The new gate only changes a render when all three conditions hold at once:

  1. the list renders a footer (footerContent or confirmButtonOptions.showButton), and
  2. the caller passed addBottomSafeAreaPadding={true}, and
  3. the soft keyboard is open.

In every other case (addBottomSafeAreaPadding falsy, no footer, or keyboard closed) the expression !isKeyboardShown && addBottomSafeAreaPadding evaluates exactly as before. Keyboard-closed behavior — the original Android nav-bar fix — is unchanged.

Why it's low-risk: BaseSelectionList already applies this exact !isKeyboardShown gate to its own (non-footer) bottom padding at BaseSelectionList.tsx:127. The footer was the one element not following that rule, which is why the extra gap you reported appeared above the keyboard. The change at BaseSelectionList.tsx:658 brings the footer in line with the container. When the keyboard is open it floats above the soft keyboard, which covers the bottom inset region — so dropping the bottom safe-area padding there is the correct, desired behavior for any consumer, not just the Pronouns page.

Other consumers that hit this path

Screens passing addBottomSafeAreaPadding into a SelectionList/SelectionScreen with a confirm footer (e.g. CardSelectionStep, SpendRuleSelectionPage, the state/country selection pages, WorkspaceMembersSelectionList, ApproverSelectionList). For each, the only behavioral delta is: if its keyboard is open while the confirm footer is visible, the footer no longer carries an extra bottom inset above the keyboard — the same correction being made here. None change when the keyboard is closed.

On validation: This is a native safe-area concern (Android nav bar / iOS home indicator). On web there is no bottom safe-area inset, so addBottomSafeAreaPadding resolves to ~0 and a web run wouldn't meaningfully exercise it — the QA needs to be on a device. If you'd prefer to keep BaseSelectionList completely untouched, the alternative is to revert BaseSelectionList.tsx:658 and accept the extra space above the keyboard that you originally flagged — but that re-introduces the bug, and pushing the keyboard logic into each consumer would be less DRY than gating it once where the footer is rendered.

My recommendation is to keep the change as-is and validate on Android/iOS native per the QA steps. Happy to adjust if you'd rather take the scoped-but-buggier route.

@truph01

truph01 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot After testing your solution, I noticed that the Save button's position behaves oddly. When the keyboard transitions from closed to open, the button briefly appears in the wrong position before moving to the correct one.

So I think it'd be safe if we switch to this approach #93773 (comment)

Switch from passing addBottomSafeAreaPadding through SelectionList/FixedFooter
to enabling includeSafeAreaPaddingBottom on the ScreenWrapper, which integrates
with the wrapper's keyboard avoidance and avoids the Save button briefly jumping
to the wrong position on keyboard open. Reverts the SelectionList keyboard hack.

Co-authored-by: truph01 <truph01@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — switched to this approach in aa51a3aef.

The page's ScreenWrapper doesn't pass enableEdgeToEdgeBottomSafeAreaPadding, so it's on the legacy safe-area path (ScreenWrapper/index.tsx:129, :138). That means includeSafeAreaPaddingBottom applies the bottom inset at the wrapper level, which already participates in keyboard avoidance — so the Save button no longer briefly jumps on keyboard open.

Changes:

  • PronounsPage: includeSafeAreaPaddingBottom={false}includeSafeAreaPaddingBottom, and dropped addBottomSafeAreaPadding from the SelectionList.
  • Reverted the !isKeyboardShown workaround in BaseSelectionList, so the net diff there is now empty.

Verification: npm run typecheck-tsgo passes. I couldn't fully verify the Android navigation-bar + keyboard behavior — that's device-specific and not reproducible on the web build — so please re-test on Android Native / mWeb to confirm the Save button clears the nav bar and stays put through the keyboard transition.

@truph01

truph01 commented Jun 17, 2026

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 shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • 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)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named ""index.js"". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • 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)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • 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.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • 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.Recording.2026-06-17.at.16.21.15.mov
Android: mWeb Chrome
iOS: HybridApp
Screen.Recording.2026-06-17.at.16.23.13.mov
iOS: mWeb Safari
MacOS: Chrome / Safari

@truph01

truph01 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Update the tests and QA tests sections:

Precondition: Set device navigation to bar instead of gestures.
1. Launch Expensify app.
2. Go to Account > Profile > Pronouns.
3. Dismiss the keyboard.
4. Verify: Save button on Pronouns page will be above device navigation bar.

@arekm213

Copy link
Copy Markdown

📱 Mobile QA — Before/After verification

Verdict: ✅ PASS — the fix resolves the bug. On the PR branch the Save button on the Pronouns page clears the Android navigation bar, while on main it is overlapped/hidden by it.

Context

  • Issue: #93738Android - Save button on Pronouns page is hidden by device navigation bar
  • Platforms reproduced (per issue): Android: App ✅ (only)
  • Fix: src/pages/settings/Profile/PronounsPage.tsx — flips the ScreenWrapper prop includeSafeAreaPaddingBottom={false}includeSafeAreaPaddingBottom (true), routing the bottom safe-area inset through FixedFooter so the Save button clears the nav bar.

Repro steps (from the issue)

Precondition: device navigation set to 3-button bar (not gestures).

  1. Launch the app → Account → Profile → Pronouns.
  2. Dismiss the keyboard.
  3. Expected: Save button sits above the device navigation bar. Actual (bug): Save button is hidden by the navigation bar.

Environment

  • Device: Android emulator (Pixel, API 3-button nav forced on via settings put secure navigation_mode 0), 1080×2424.
  • Account: disposable test account (expensifytestinglogin+pr93200@gmail.com), staging.
  • Branches: main (bug) vs claude-pronounsSaveButtonSafeArea @ aa51a3aef60 (fix). JS-only change → app reloaded from Metro, no native rebuild.
  • Same 3-button nav configuration on both branches → fair comparison. (A Gboard floating mini-toolbar appears mid-left in both captures; it does not touch the Save button.)

❌ Before — main (bug)

The Save button runs to the very bottom edge; the system nav-bar icons (◀ ● ■) overlap its lower half and the "Save" label is cut off. Measured bounds: Save y ≈ 0.922–0.979, nav bar y ≈ 0.950–0.998they overlap.

main — Save hidden behind nav bar

main — before GIF

▶️ Full video — main, ~53s, repro with lead-in & aftermath (compressed)

✅ After — PR branch (fix)

The Save button is fully visible with clear spacing above the nav bar; the "Save" label reads cleanly. Measured bounds: Save y ≈ 0.870–0.927, nav bar y ≈ 0.950–0.998clean gap, no overlap.

PR — Save visible above nav bar

PR — after GIF

▶️ Full video — PR branch, ~53s, repro with lead-in & aftermath (compressed)


Result

Save button vs nav bar Outcome
main overlaps nav bar (0.950–0.979) — label cut off ❌ bug reproduced
PR branch clears nav bar (gap 0.927→0.950) ✅ fixed

Actual → Expected delta confirmed: the Save button moves up by the bottom safe-area inset and is no longer hidden by the device navigation bar. PASS.

🤖 Automated mobile QA run (Android native, before/after on main vs PR via Argent). Web/mWeb out of scope (issue reproduces on Android App only). Inline GIFs are the tight repro action; the linked MP4s carry ~20s of lead-in/aftermath context (compressed ~540p).

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

@lakchote lakchote merged commit 525197a into main Jun 17, 2026
37 of 40 checks passed
@lakchote lakchote deleted the claude-pronounsSaveButtonSafeArea branch June 17, 2026 15:54
@github-actions

Copy link
Copy Markdown
Contributor

🚧 @lakchote has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/lakchote in version: 9.4.10-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 I reviewed the changes in this PR and no help site updates are required.

This is a UI-only fix: it flips includeSafeAreaPaddingBottom on the Pronouns page's ScreenWrapper so the Save button clears the Android device navigation bar. There are no changes to features, workflows, settings, labels, copy, or user-facing behavior — nothing that's documented in App/docs/articles. The Pronouns flow (Account > Profile > Pronouns) works exactly as before; only the button's vertical position on Android is corrected.

Since there's nothing to document, I did not create a draft docs PR.

@truph01, no linked help site PR was created because this change doesn't affect any documented behavior. Let me know if you'd still like a docs PR for anything specific.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/lakchote in version: 9.4.12-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Help site review: no changes required.

This PR is a UI-only safe-area fix — it changes includeSafeAreaPaddingBottom on PronounsPage so the Save button clears the Android device navigation bar. It does not alter any feature behavior, setting, label, tab, or workflow that the public help-site articles under App/docs/articles describe.

Help-site articles document product behavior and workflows, not pixel-level layout/safe-area adjustments, so there's nothing to update here. No draft docs PR was created.

@truph01, please confirm you agree no help site changes are needed. If you believe an article should be updated, let me know which behavior is affected and I'll create the draft PR.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/lakchote in version: 9.4.14-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

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.

5 participants