Skip to content

Fix scroll/reorder jump when selecting in From/To/Attendee/Assignee search filters#94609

Merged
marcaaron merged 15 commits into
mainfrom
melvin-userselector-no-jump-scroll
Jun 26, 2026
Merged

Fix scroll/reorder jump when selecting in From/To/Attendee/Assignee search filters#94609
marcaaron merged 15 commits into
mainfrom
melvin-userselector-no-jump-scroll

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

The From / To / Attendee / Assignee people pickers in the Search Filters popover are all rendered by UserSelector (SearchFiltersParticipantsSelector is being deprecated). Two things made the list feel unstable while selecting:

  1. The list scrolled to the top on every selection — selectUser called selectionListRef.current.scrollToIndex(0) after toggling.
  2. The selected row jumped to the toplistData rendered availableOptions.selectedOptions as a prefix, so a freshly-toggled row left its sorted position and floated up.

This PR keeps a toggled row exactly where it is, while still surfacing the items that were already selected when the filter first opened at the top of the list:

  • Pass shouldKeepSelectedInAvailableOptions: true to usePersonalDetailSearchSelector, so selected options stay inside availableOptions.personalDetails in their natural sorted position (with their isSelected flag) instead of being filtered out and re-prepended. This is the same approach already shipped for DynamicWorkspaceInvitePage (PR Stop scroll jump on workspace invite member selection #91250) for this exact issue.
  • Remove the scrollToIndex(0) call and the now-unused selectionListRef / imports.
  • Pin only the pre-selected rows to the top on first render, via the shared moveInitialSelectionToTop util. The initial selection is snapshotted once with useInitialValue(() => value), so the ordering keys on what was selected when the filter opened — rows toggled afterwards keep their sorted position and are not re-pinned. The current user (excluded from personalDetails) is prepended to the base list before ordering, then each option's keyForList is mapped onto value so the util can match it.
  • Switch moveInitialSelectionToTop's threshold from the bespoke MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD (8) constant — now removed — to the existing STANDARD_LIST_ITEM_LIMIT (12), the same point at which the search input appears. Short, fully-visible lists are left in their natural order. Because the util is shared, this also updates its existing consumers' tests (SelectionListOrderUtilsTest, PushRowModalTest, CountrySelectionListTest, ValueSelectionListTest).
  • Add shouldUpdateFocusedIndex to the SelectionList so keyboard focus tracks the reordered rows.

Fixed Issues

$ #61414
PROPOSAL:

Tests

  1. Go to Reports (Spend) → Filters pill → From (also repeat for To / Attendee / Assignee).
  2. Scroll down the list and select an item near the bottom.
  3. Verify the selected item stays in place (it does not jump to the top) and the scroll position does not change.
  4. Verify you can continue multi-selecting smoothly without the list reordering or jumping.
  5. Deselect items and verify they also stay in place.
  6. Go to another filter and then back to From filter.
  7. Verify that pre-selected items are moved to the top.
  • Verify that no errors appear in the JS console

Offline tests

Same as Tests

QA Steps

Same as Tests

  • 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: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • 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 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 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)
  • 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)
  • 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 new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • 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.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user 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.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
- [x] I verified that similar component doesn't exist in the codebase - [x] I verified that all props are defined accurately and each prop has a `/** comment above it */` - [x] I verified that each file is named correctly - [x] I verified that each component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone - [x] I verified that the only data being stored in component state is data necessary for rendering and nothing else - [x] In component 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 - [x] 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) - [x] I verified that component 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`) - [x] I verified that all JSX used for rendering exists in the render method - [x] I verified that each 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

Screenshots/Videosundefined

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

Copy link
Copy Markdown
Contributor Author

The failing check is unrelated to this PR — no fix pushed.

Failing test: test (job 7)tests/unit/SubscriptionUtilsTest.ts:193SubscriptionUtils › isUserOnFreeTrial › should return true if the current date is on the same date of free trial start date.

This is a date-sensitive flaky test that relies on new Date() for the trial start/end dates. It has nothing to do with this PR, which only modifies src/components/Search/FilterComponents/UserSelector.tsx — a Search filter component with no relation to subscription/free-trial logic.

All PR-relevant checks passed (ESLint, Prettier, typecheck, React Compiler, all build variants, the other 7 test jobs). The remaining PR Author Checklist / checklist failure is the standard human checklist, not a code issue.

Recommendation: Re-run test (job 7); the flaky date test should pass on a re-run.

@mkhutornyi

Copy link
Copy Markdown
Contributor

@MelvinBot pre-selected items should be at the top on first render

…lters

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

Copy link
Copy Markdown
Contributor Author

Done — pre-selected items now float to the top on first render. Pushed in a75e24e.

I capture the pre-selected accountIDs once when the filter opens (via a useState initializer) and order personalDetails so those rows render first, each group keeping its natural sorted order. Because the ordering keys off the initial selection set (not the live one), rows you toggle afterwards still stay exactly in place — preserving the no-jump fix from this PR.

Change

UserSelector.tsx:55-71:

const [initiallySelectedAccountIDs] = useState(() => new Set(value));

const orderedPersonalDetails =
    initiallySelectedAccountIDs.size === 0
        ? availableOptions.personalDetails
        : [
              ...availableOptions.personalDetails.filter((option) => initiallySelectedAccountIDs.has(option.accountID.toString())),
              ...availableOptions.personalDetails.filter((option) => !initiallySelectedAccountIDs.has(option.accountID.toString())),
          ];

Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler compliance (✅ COMPILED).

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/CONST/index.ts 94.07% <ø> (ø)
...omponents/Search/FilterComponents/UserSelector.tsx 96.42% <100.00%> (+96.42%) ⬆️
src/libs/SelectionListOrderUtils.ts 100.00% <100.00%> (ø)
... and 208 files with indirect coverage changes

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the failing checklist check.

What was wrong: The check wasn't a normal "incomplete checklist" failure — it was crashing with TypeError: Cannot read properties of undefined (reading 'split') in authorChecklist.ts. partitionWithChecklist splits the PR body on ### PR Author Checklist; the generated PR body was missing that section (and ### Screenshots/Videos), so the split returned undefined and the next .split() threw.

Fix: Restored the full PULL_REQUEST_TEMPLATE.md structure to the PR description (added the ### PR Author Checklist and ### Screenshots/Videos sections). The action now parses the body correctly and runs normally instead of crashing.

⚠️ Remaining action for the human co-author: the checklist will stay red until the boxes are completed (0 of 41 currently). I intentionally left the author-checklist boxes, Tests, and QA Steps for mkhutornyi to fill out — these confirm a human actually performed the testing, so I won't check them on your behalf.

All other checks are greentypecheck, test (jobs 1-8), all build variants, ESLint, Prettier, and Bun tests pass. The code change itself is untouched.

Comment on lines +60 to +67
// Order personalDetails so pre-selected rows render at the top, each group keeping its natural sorted order.
const orderedPersonalDetails =
initiallySelectedAccountIDs.size === 0
? availableOptions.personalDetails
: [
...availableOptions.personalDetails.filter((option) => initiallySelectedAccountIDs.has(option.accountID.toString())),
...availableOptions.personalDetails.filter((option) => !initiallySelectedAccountIDs.has(option.accountID.toString())),
];

@mkhutornyi mkhutornyi Jun 26, 2026

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.

@MelvinBot Use existing moveInitialSelectionToTop util function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — replaced the manual pinning loop with moveInitialSelectionToTop in UserSelector.tsx:62-68. Since the util keys on value and these options key on keyForList (= the accountID), I map value: option.keyForList onto each item before passing it in — matching how the other consumers (e.g. PushRowModal) feed the util.

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

Copy link
Copy Markdown
Contributor Author

Done — switched to the existing moveInitialSelectionToTop util. Pushed in a9abc98.

The util keys on each item's value, and personalDetails options identify by keyForList (the stringified accountID), so I map them to {...option, value: option.keyForList} before passing them in — the same pattern ValueSelectionList uses. The pre-selected values are still captured once at mount, so toggling rows afterwards keeps them in place.

Change

UserSelector.tsx:56-66:

const [initialSelectedValues] = useState(() => [...value]);

const orderedPersonalDetails = moveInitialSelectionToTop(
    availableOptions.personalDetails.map((option) => ({...option, value: option.keyForList})),
    initialSelectedValues,
);

Note: the util only reorders when the list is longer than CONST.MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD (8) — consistent with how it behaves everywhere else it's used.

Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler compliance (✅ COMPILED).


// The current user is excluded from personalDetails, so render it (when present) at the top, followed by
// the rest of the contacts. Selected rows already live within personalDetails in their sorted position.
const listData = availableOptions.currentUserOption ? [availableOptions.currentUserOption, ...orderedPersonalDetails] : orderedPersonalDetails;

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.

@MelvinBot current user should be below selected items, not top

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The current user is now included in the list passed to moveInitialSelectionToTop (right after currentUserOption), so pre-selected rows float above it and the current user renders below the selected items — see UserSelector.tsx:66.

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

Copy link
Copy Markdown
Contributor Author

Done — the current user now renders below the pinned pre-selected rows, not at the top. Pushed in c064150.

The order on first render is now: pinned pre-selected rows → current user → remaining contacts. When nothing is pre-selected (or the list is under the move-to-top threshold), the current user stays at the top as before, since there's nothing pinned above it.

Change

UserSelector.tsx:61-76:

const personalDetailsWithValue = availableOptions.personalDetails.map((option) => ({...option, value: option.keyForList}));
const orderedPersonalDetails = moveInitialSelectionToTop(personalDetailsWithValue, initialSelectedValues);

const selectedValues = new Set(initialSelectedValues);
const pinnedCount =
    orderedPersonalDetails === personalDetailsWithValue ? 0 : personalDetailsWithValue.reduce((count, option) => (selectedValues.has(option.value) ? count + 1 : count), 0);

const listData = availableOptions.currentUserOption
    ? [...orderedPersonalDetails.slice(0, pinnedCount), availableOptions.currentUserOption, ...orderedPersonalDetails.slice(pinnedCount)]
    : orderedPersonalDetails;

pinnedCount is the number of pre-selected rows moveInitialSelectionToTop pinned to the top, so the current user is spliced in right after them. Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler (✅ COMPILED).

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Looked at the current failures on a9abc98. None are caused by this PR's code — here's the breakdown:

test (job 8) — infrastructure flake, not a code failure. The job's only annotation is:

The self-hosted runner lost communication with the server… Anything in your workflow that terminates the runner process, starves it for CPU/Memory, or blocks its network access can cause this error.

The Jest step never emitted a test failure — the Blacksmith runner died mid-run (OOM/network). Evidence it's unrelated to the change:

  • The other 7 of 8 test shards passed on this exact commit.
  • typecheck passes (verified locally too).
  • The change is isolated to UserSelector.tsx, which has no dedicated unit test; SelectionListOrderUtils (which has a test) is only consumed here, not modified.

Fix: just re-run the failed job (Re-run failed jobs on the run, or push any new commit). I can't trigger the re-run myself — my token gets 403 Resource not accessible on the rerun API — so mkhutornyi will need to click it.

checklist (×2) — human-owned, expected to be red. These are PR Author Checklist (0/41 boxes) and PR Reviewer Checklist (No PR Reviewer Checklist was found). They stay red until the author completes the checklist and a reviewer posts theirs — I won't auto-fill these on a human's behalf. (The earlier TypeError crash in the author-checklist action, caused by the PR body missing the template sections, is already fixed.)

Everything else is green — all builds, ESLint, Prettier, Bun tests, perf tests, Storybook, and test shards 1–7.

@mkhutornyi

Copy link
Copy Markdown
Contributor

Done — the current user now renders below the pinned pre-selected rows, not at the top. Pushed in c064150.

The order on first render is now: pinned pre-selected rows → current user → remaining contacts. When nothing is pre-selected (or the list is under the move-to-top threshold), the current user stays at the top as before, since there's nothing pinned above it.

Change

@MelvinBot Your last commit is quite complex (i.e. introducing pinnedCount). If it's because of using moveInitialSelectionToTop, I am fine not using it.
Please simplify logic as much as possible, not causing performance impact.

…pinnedCount

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

Copy link
Copy Markdown
Contributor Author

Simplified — pinnedCount is gone. Pushed in 7098148.

pinnedCount only existed because the old code ordered the whole list with moveInitialSelectionToTop and then had to re-derive how many rows ended up pinned (including a reference-equality check) just to know where to slot the current user. Instead I now partition the contacts directly in one pass and assemble the list explicitly:

UserSelector.tsx:55-72:

const [initiallySelectedAccountIDs] = useState(() => new Set(value));

const shouldPinSelectedRows = initiallySelectedAccountIDs.size > 0 && availableOptions.personalDetails.length > CONST.MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD;
const pinnedPersonalDetails: OptionData[] = [];
const remainingPersonalDetails: OptionData[] = [];
for (const option of availableOptions.personalDetails) {
    const isPinned = shouldPinSelectedRows && initiallySelectedAccountIDs.has(option.accountID.toString());
    (isPinned ? pinnedPersonalDetails : remainingPersonalDetails).push(option);
}

const listData = availableOptions.currentUserOption
    ? [...pinnedPersonalDetails, availableOptions.currentUserOption, ...remainingPersonalDetails]
    : [...pinnedPersonalDetails, ...remainingPersonalDetails];

This drops pinnedCount, the .map that grafted a value field onto every option, the slice gymnastics, and the moveInitialSelectionToTop import — you were right that it was the source of the awkwardness here. Behavior is unchanged: same pinned → current user → remaining order, same move-to-top threshold (so short, fully-visible lists are left untouched and the current user stays at the top). Performance is the same single pass over personalDetails — no extra allocations vs. before.

Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler compliance (✅ COMPILED).

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ MelvinBot CI Fix Loop Detected

MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR.

Next steps: A human should review the recent commits and resolve the conflicting CI errors manually.

@mkhutornyi

Copy link
Copy Markdown
Contributor

@MelvinBot we should NOT use MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD. It should be deprecated.
We should use STANDARD_LIST_ITEM_LIMIT

…_LIST_THRESHOLD

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

Copy link
Copy Markdown
Contributor Author

Done — dropped MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD and switched everything to STANDARD_LIST_ITEM_LIMIT (12). Pushed in e2a6ea8.

This also lines the pinning threshold up with the search-input threshold (UserSelector.tsx:83), so pinning kicks in exactly when the list becomes searchable.

Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler (✅ COMPILED), and the 3 affected Jest suites (8/8 passed).

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

Copy link
Copy Markdown
Contributor Author

Done — passed shouldUpdateFocusedIndex on the SelectionList. Pushed in 2dca0bb.

It defaults to false, so on a row select the focused index wasn't moved to the tapped row. With it true, BaseSelectionList calls setFocusedIndexWithoutScrollOnChange(indexToFocus), keeping keyboard focus on the just-selected row (no scroll) after the reorder.

UserSelector.tsx:109

Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler (✅ COMPILED).

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

@MelvinBot > >= CONST.STANDARD_LIST_ITEM_LIMIT. It should match search input visibility. When 12, we show search so should be pinnable

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — changed the pin gate from > to >= so it matches search-input visibility. Pushed in 3c0f28b.

Since STANDARD_LIST_ITEM_LIMIT is 12, the search input shows at totalOptionsCount >= 12 (UserSelector.tsx:83), but pinning was gated on > 12 (only 13+). Now both trigger at exactly 12, so when the list is searchable the pre-selected rows are also pinned: UserSelector.tsx:63. I also updated the comment, which no longer matched the shared moveInitialSelectionToTop util (that one still uses >).

Verified locally: Prettier (clean), ESLint (✔ Lint done), typecheck-tsgo (pass), React Compiler (✅ COMPILED).

Comment thread src/libs/SelectionListOrderUtils.ts Outdated

function moveInitialSelectionToTop<T extends {value: string}>(items: T[], initialSelectedValues: string[]): T[] {
if (initialSelectedValues.length === 0 || items.length <= CONST.MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD) {
if (initialSelectedValues.length === 0 || items.length <= CONST.STANDARD_LIST_ITEM_LIMIT) {

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.

@MelvinBot

Suggested change
if (initialSelectedValues.length === 0 || items.length <= CONST.STANDARD_LIST_ITEM_LIMIT) {
if (initialSelectedValues.length === 0 || items.length < CONST.STANDARD_LIST_ITEM_LIMIT) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied — changed the guard to items.length < CONST.STANDARD_LIST_ITEM_LIMIT so the list reorders once it's long enough to show the search input (>= STANDARD_LIST_ITEM_LIMIT), consistent with shouldShowSearchInput. See SelectionListOrderUtils.ts:4.

@melvin-bot melvin-bot Bot requested review from mkhutornyi and parasharrajat and removed request for a team June 26, 2026 17:11
@melvin-bot

melvin-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

@parasharrajat @ChavdaSachin @mkhutornyi One of you needs to 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 requested review from flaviadefaria and removed request for a team June 26, 2026 17:11
@melvin-bot melvin-bot Bot requested review from francoisl and marcaaron June 26, 2026 17:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 750ccd4ed2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


function moveInitialSelectionToTop<T extends {value: string}>(items: T[], initialSelectedValues: string[]): T[] {
if (initialSelectedValues.length === 0 || items.length <= CONST.MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD) {
if (initialSelectedValues.length === 0 || items.length < CONST.STANDARD_LIST_ITEM_LIMIT) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve pinning for 9–11 item picker lists

When this shared helper is called by existing pickers with 9–11 rows, this new threshold now returns the list unchanged, whereas the previous MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD behavior pinned those saved selections. That regresses callers such as PushRowModal and ValueSelectionList that rely on moveInitialSelectionToTop to surface the saved value on reopen; the pre-existing PushRowModal test covered a 10-option list and had to be changed because this selected row now stays at the bottom. Please keep the old visibility threshold for the shared helper or make the new threshold opt-in for the search-filter use case.

Useful? React with 👍 / 👎.

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.

Expected outcome

@marcaaron marcaaron merged commit 4969781 into main Jun 26, 2026
38 of 41 checks passed
@marcaaron marcaaron deleted the melvin-userselector-no-jump-scroll branch June 26, 2026 20:16
@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.

@github-actions

Copy link
Copy Markdown
Contributor

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

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/marcaaron in version: 9.4.22-1 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 No help site changes required.

I reviewed the changes in this PR against the help articles under App/docs/articles, applying HELPSITE_NAMING_CONVENTIONS.md, HELP_AUTHORING_GUIDELINES.md, and TEMPLATE.md.

This PR is a UI interaction bug fix — it stops the From / To / Attendee / Assignee people-picker lists in the Search Filters popover from scrolling to the top and reordering when you toggle a selection, while still pinning pre-selected rows to the top on first open. It changes list scroll/ordering behavior only.

It does not:

  • Add, remove, or rename any feature, tab, setting, or button
  • Change any documented workflow or user-facing instruction

The help site does not document the scroll/reorder behavior of these picker lists (the closest article, Use Search Operators to Filter and Analyze, covers search operators/syntax, not picker interaction mechanics), so there is nothing to update. No draft PR was created.

@mkhutornyi, since no help site changes are needed, there's no linked docs PR to review. Let me know if you'd still like me to document anything specific here.

@jponikarchuk

Copy link
Copy Markdown

Deploy Blocker #94843 was identified to be related to this PR.

@jponikarchuk

Copy link
Copy Markdown

This PR failing because of the issue #94843
This issue is reproducible in: Web

@mkhutornyi

Copy link
Copy Markdown
Contributor

This PR failing because of the issue #94843 This issue is reproducible in: Web

Please see #94843 (comment)

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/marcaaron in version: 9.4.24-0 🚀

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

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/cristipaval in version: 9.4.24-0 🚀

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

Bundle Size Analysis (Sentry):

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/marcaaron in version: 9.4.24-0 🚀

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

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