You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-11-05] Chat - Action pill appears and disappears when sending messages after creating expense. #92663
Version Number: 9.3.99-0 Reproducible in staging?: Yes Reproducible in production?: N/A - new feature, doesn't exist in prod If this was caught during regression testing, add the test name, ID and link from BrowserStack:#87212 Email or phone of affected tester (no customers):ibellicotest+2766@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Motorola MotoG60 / Android 12 (Hybrid app) - Windows 11 / Chrome App Component: Chat Report View
Action Performed:
Prerequisite: Account has at least one workspace.
Open the staging.new.expensify.com wesbite.
Open any workspace chat.
Create a manual expense.
Send a message below the just created expense.
Note that a "Submit" pill appears and disappears on top of chat.
Keep sending messages.
Note that "Submit" pill keeps appearing and disappearing until expense is no longer visible. At that point, pill remains displayed.
Expected Result:
"Submit" pill should appear and remain visible when messages are sent below a created report.
Actual Result:
When sending messages below a created expense, "Submit" pill appears and disappears until expense is no longer visible. Pill remains displayed only when expense can´t be seen anymore.
👋 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:
Identify the pull request that introduced this issue and revert it.
The "Submit" floating pill (added in #87212) is shown when isActionBadgeAboveViewport is true, i.e. when the target expense action sits above the visible window. That flag is computed in useReportUnreadMessageScrollTracking.ts:146-155 by comparing the target's numeric index against the max visible index:
The flicker comes from a stale-index recompute. Every time you send a message, a new action is prepended at index 0 of the inverted list, so every older action — including the expense — shifts up by one. actionBadgeTargetIndex is recomputed (+1) in ReportActionsList.tsx:311-317. That change fires this effect, which manually recomputes visibility using the previous viewable items:
At that moment previousViewableItems still holds the old indexes (FlatList hasn't re-reported viewability yet), so maxIndex is the pre-shift value while badgeTargetIndex is the post-shift (+1) value. Once the expense reaches the top edge of the viewport, badgeTargetIndex (old+1) > maxIndex (old) evaluates true → pill appears. A beat later FlatList fires the real onViewableItemsChanged with fresh indexes; the expense is still visible, so it flips back to false → pill disappears. Repeat on every message. When the expense finally scrolls off-screen for real, the steady-state value is correctly true, so the pill stays — exactly the reported behavior.
Why the index comparison is the wrong primitive here
The unread-marker path has the same manual-recompute pattern but is index-based too; the action-badge path is more fragile because the target is a fixed, old action whose index monotonically increases as you chat, guaranteeing it eventually lines up with the viewport edge where the off-by-one transient triggers. ViewToken indexes are inherently unstable across list mutations, while ViewToken key/item (the reportActionID) is stable and survives index shifts.
What changes do you think we should make in order to solve the problem?
Stop deriving "is the target above the viewport" from raw numeric indexes that go stale on every message. Instead determine visibility by the target reportActionID (stable) against the viewable items' keys:
Pass actionTargetReportActionID into the hook instead of (or alongside) actionBadgeTargetIndex.
In onViewableItemsChanged, set isActionBadgeAboveViewport = true only when the target ID is not among the currently viewable item keys (viewableItems.some(v => v.key === targetID) is false). Because the target's ID is unaffected by index shifts, the manual recompute with stale previousViewableItems stays consistent with the freshly computed target, eliminating the transient.
A lighter-touch alternative: in the actionBadgeTargetIndex effect, skip the manual onViewableItemsChanged recompute and let FlatList's own viewability callback own the value — but that still leaves an index-based comparison that can misfire on other mutations, so the ID-based approach is the more robust fix.
What alternative solutions did you explore? (Optional)
I could not fully rule out churn in the ONYXKEYS.DERIVED.REPORT_ATTRIBUTES value (actionTargetReportActionID briefly going undefined would also toggle the pill), but the index-shift mechanism above is directly evidenced by the code and matches every detail of the repro (flicker on each send; pill stable once the expense leaves the viewport), so it is the primary root cause.
Confidence: medium-high (code-evidenced, not yet reproduced in a browser). The feature is gated behind !isProduction (ReportActionsList.tsx:675-676), consistent with "Reproducible in staging: Yes / N/A in prod".
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/92663) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Chat - Action pill appears and disappears when sending messages after creating expense.[/-][+][Due for payment 2026-11-05] Chat - Action pill appears and disappears when sending messages after creating expense.[/+]on Oct 29, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.24-8 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-11-05. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to the comment on the PR:
Link to the comment on the Issue:
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion:
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
Regression Test Proposal
Test:
Create new account and new workspace
Open workspace chat.
Create a manual expense.
Send a message below the just created expense.
Keep sending messages.
Verify that "Submit" pill doesn't briefly appear until expense preview is no longer visible.
Verify that pill shows and remains displayed after preview is no longer visible.
Reproducible in production?: N/A - new feature, doesn't exist in prod
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #87212
False positive because this bug was reported after #87212 was already deployed to production
Activity
applause-bot commentedon Jun 4, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/27027a0cf1e0d0cd392eba0ecc077e4bd8b229879a27f73e5c9ada54b0648104
melvin-bot commentedon Jun 4, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #87212
melvin-bot commentedon Jun 4, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Jun 4, 2026
👋 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:
MelvinBot commentedon Jun 4, 2026
Proposal
What is the root cause of that problem?
The "Submit" floating pill (added in #87212) is shown when
isActionBadgeAboveViewportistrue, i.e. when the target expense action sits above the visible window. That flag is computed inuseReportUnreadMessageScrollTracking.ts:146-155by comparing the target's numeric index against the max visible index:The flicker comes from a stale-index recompute. Every time you send a message, a new action is prepended at index
0of the inverted list, so every older action — including the expense — shifts up by one.actionBadgeTargetIndexis recomputed (+1) inReportActionsList.tsx:311-317. That change fires this effect, which manually recomputes visibility using the previous viewable items:useReportUnreadMessageScrollTracking.ts:173-176At that moment
previousViewableItemsstill holds the old indexes (FlatList hasn't re-reported viewability yet), somaxIndexis the pre-shift value whilebadgeTargetIndexis the post-shift (+1) value. Once the expense reaches the top edge of the viewport,badgeTargetIndex (old+1) > maxIndex (old)evaluatestrue→ pill appears. A beat later FlatList fires the realonViewableItemsChangedwith fresh indexes; the expense is still visible, so it flips back tofalse→ pill disappears. Repeat on every message. When the expense finally scrolls off-screen for real, the steady-state value is correctlytrue, so the pill stays — exactly the reported behavior.Why the index comparison is the wrong primitive here
The unread-marker path has the same manual-recompute pattern but is index-based too; the action-badge path is more fragile because the target is a fixed, old action whose index monotonically increases as you chat, guaranteeing it eventually lines up with the viewport edge where the off-by-one transient triggers. ViewToken indexes are inherently unstable across list mutations, while ViewToken
key/item(the reportActionID) is stable and survives index shifts.What changes do you think we should make in order to solve the problem?
Stop deriving "is the target above the viewport" from raw numeric indexes that go stale on every message. Instead determine visibility by the target reportActionID (stable) against the viewable items' keys:
actionTargetReportActionIDinto the hook instead of (or alongside)actionBadgeTargetIndex.onViewableItemsChanged, setisActionBadgeAboveViewport = trueonly when the target ID is not among the currently viewable item keys (viewableItems.some(v => v.key === targetID)isfalse). Because the target's ID is unaffected by index shifts, the manual recompute with stalepreviousViewableItemsstays consistent with the freshly computed target, eliminating the transient.A lighter-touch alternative: in the
actionBadgeTargetIndexeffect, skip the manualonViewableItemsChangedrecompute and let FlatList's own viewability callback own the value — but that still leaves an index-based comparison that can misfire on other mutations, so the ID-based approach is the more robust fix.What alternative solutions did you explore? (Optional)
I could not fully rule out churn in the
ONYXKEYS.DERIVED.REPORT_ATTRIBUTESvalue (actionTargetReportActionIDbriefly going undefined would also toggle the pill), but the index-shift mechanism above is directly evidenced by the code and matches every detail of the repro (flicker on each send; pill stable once the expense leaves the viewport), so it is the primary root cause.Related
!isProduction(ReportActionsList.tsx:675-676), consistent with "Reproducible in staging: Yes / N/A in prod".Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/92663)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
aimane-chnaif commentedon Jun 4, 2026
This feature is gated behind production so we can demote this.
And PR was already deployed to production.
App/src/pages/inbox/report/ReportActionsList.tsx
Lines 675 to 676 in 1188ed4
34 remaining items
[-]Chat - Action pill appears and disappears when sending messages after creating expense.[/-][+][Due for payment 2026-11-05] Chat - Action pill appears and disappears when sending messages after creating expense.[/+]melvin-bot commentedon Oct 29, 2026
@aimane-chnaif / @mkhutornyi
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.24-8 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-11-05. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
mkhutornyi commentedon Oct 30, 2026
Contributor+ Checklist:
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to the comment on the PR:
Link to the comment on the Issue:
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion:
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
Regression Test Proposal
Test:
Do we agree 👍 or 👎
mkhutornyi commentedon Oct 30, 2026
False positive because this bug was reported after #87212 was already deployed to production