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-09-17] Report - Second expense duplicated offline, is higlighted again after returning online. #100787
Version Number: 9.4.75-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:#98744 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: Money Requests
Action Performed:
Prerequisite: Account has a workspace.
Open the staging.new.expensify.com website.
Navigate to workspace chat.
Create two manual expenses.
Open the report of the just created expenses.
Turn off internet connection.
Select one of the expenses.
Click on dropdown menu > "Duplicate Expense"
Note that created duplicate is highlighted.
Select the expense that wasn´t duplicated.
Click on dropdown menu > "Duplicate Expense"
Note that new duplicate is also duplicated.
Turn on internet connection.
Note that second duplicate is highlighted again.
Expected Result:
Duplicates should only be displayed highlighted after being created.
Actual Result:
Second duplicate created while in offline mode, gets highlighted again after returning to online mode.
👋 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.
Classification: Frontend bug Causing PR: #98744 - "refactor: the new-transaction highlight rail and the wide RHP width model" by @TaduJR (High confidence) Related Issues: #100757, #100761, #100763, #100770, #100771, #100782 — six other DeployBlockerCash issues opened the same day, all highlight regressions, all auto-attributed to the same PR. Revert PR #100778 is already open.
Recommendation: REVERT
PR #98744 rewrote the entire "which transaction is new / when does its highlight play" state machine and is the only change on staging touching that machinery; it has produced seven simultaneous deploy blockers, so the fastest safe path is the already-open revert #100778 rather than seven individual roll-forward fixes.
Assigned: @TaduJR (PR author), @blimpich and @abzokhattab (approving reviewers) from the highest-confidence causing PR — already assigned by melvin-bot, no change made. Labels: No changes. DeployBlockerCash stays (an App PR caused this and it does not reproduce on production). DeployBlocker was never applied, so nothing to remove.
It is the only staging change to the highlight machinery. Every commit on staging touching src/hooks/useNewTransactions.ts and src/hooks/useAnimatedHighlightStyle/index.ts since production comes from this PR's branch (gh api repos/Expensify/App/commits?path=...&sha=staging). No other PR in the checklist can produce a "row highlighted again" regression.
The symptom class matches the diff exactly. The PR replaced the usePrevious-based new-transaction diff with a latched diff state machine, and replaced the useState-driven one-shot animation trigger in useAnimatedHighlightStyle with a ref-based phase machine that re-arms on every shouldHighlight false→true edge.
Path confirmation for this specific repro: "Duplicate expense" routes through duplicateExpenseTransaction → requestMoney → MoneyRequestBuilder.ts#L705-L718, which is where this PR now stamps the highlight-rail flag ({transactionID}:{Date.now()}) into reportMetadata.pendingNewTransactionIDs as optimistic data — i.e. the flag is written offline, at duplication time, with an offline timestamp.
Root Cause
Under #98744 a row's highlight is re-playable, where before it was effectively one-shot:
The highlight state now survives list churn.useNewTransactions no longer diffs against the previous render; it latches the added IDs into diffState.addedIDs and re-derives the highlighted set from the current list on every render (useNewTransactions.ts#L90-L102), explicitly carrying the latch across removals and re-orders (#L74-L79). A transaction that leaves the list and comes back is deliberately treated as new again — the PR's own test asserts this: useNewTransactionsTest.ts#L1329"highlights a transaction again when it is removed and later re-added while the consumer stays mounted".
A flag written offline can still be "active" a minute later. The new selector keeps a flag on the rail for PENDING_TRANSACTION_FRESHNESS_WINDOW = 60s from its flaggedAt stamp (ReportMetaData.ts#L81), and the sweep that clears it only runs when the flagged row is actually present in newTransactions and the report is visible (#L104-L131). So the most recently duplicated row — the second one in these steps — is the one whose flag/latch can still be alive when the network returns.
The animation re-arms on any false→true edge or remount.useAnimatedHighlightStyle now tracks the previous value in a ref and re-arms whenever shouldHighlight transitions to true (#L98-L110); refs reset on remount, so a recycled/remounted row whose flag is still active replays the full entry + pulse.
Coming back online is exactly the moment that produces that edge: the reconnect burst (queued RequestMoney responses clearing pendingAction, the report-actions refetch, and the resulting recomputes of the derived REPORT_TRANSACTIONS_AND_VIOLATIONS value that feeds MoneyRequestReportActionsList.tsx#L144-L149, where isOffline is itself an input) rewrites the transaction list several times in quick succession. Any intermediate list in which the newest duplicate is momentarily absent and then present re-enters it into newTransactions, flipping shouldHighlight back to true and replaying the pulse — the reported behaviour.
I have pinned the mechanism to this rewrite with high confidence; which precise intermediate Onyx update drops and restores the row during the reconnect burst is not something I could confirm statically, which is a further argument for reverting rather than patching one edge.
changed the title [-]Report - Second expense duplicated offline, is higlighted again after returning online.[/-][+][Due for payment 2026-09-17] Report - Second expense duplicated offline, is higlighted again after returning online.[/+]on Sep 10, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.75-3 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-09-17. 🎊
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]
Activity
applause-bot commentedon Sep 10, 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/f008b71432a8eb54e8a28cb54fcd7273f93efc1dbd6dee048f82a582a1e1e784
melvin-bot commentedon Sep 10, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #98744
melvin-bot commentedon Sep 10, 2026
@blimpich @abzokhattab you have been assigned to this deploy blocker because you reviewed the offending PR: #98744
github-actions commentedon Sep 10, 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:
melvin-bot commentedon Sep 10, 2026
@TaduJR you have been assigned to this deploy blocker because you authored the offending PR: #98744
melvin-bot commentedon Sep 10, 2026
💬 A slack conversation has been started in #expensify-open-source
MelvinBot commentedon Sep 10, 2026
🔍 Investigation Summary
Classification: Frontend bug
Causing PR: #98744 - "refactor: the new-transaction highlight rail and the wide RHP width model" by @TaduJR (High confidence)
Related Issues: #100757, #100761, #100763, #100770, #100771, #100782 — six other
DeployBlockerCashissues opened the same day, all highlight regressions, all auto-attributed to the same PR. Revert PR #100778 is already open.Recommendation: REVERT
PR #98744 rewrote the entire "which transaction is new / when does its highlight play" state machine and is the only change on staging touching that machinery; it has produced seven simultaneous deploy blockers, so the fastest safe path is the already-open revert #100778 rather than seven individual roll-forward fixes.
Assigned: @TaduJR (PR author), @blimpich and @abzokhattab (approving reviewers) from the highest-confidence causing PR — already assigned by melvin-bot, no change made.
Labels: No changes.
DeployBlockerCashstays (an App PR caused this and it does not reproduce on production).DeployBlockerwas never applied, so nothing to remove.📋 Detailed Analysis
Evidence
StagingDeployCashchecklist for9.4.75-0, unreleased to production — matching "Reproducible in production: No".stagingtouchingsrc/hooks/useNewTransactions.tsandsrc/hooks/useAnimatedHighlightStyle/index.tssince production comes from this PR's branch (gh api repos/Expensify/App/commits?path=...&sha=staging). No other PR in the checklist can produce a "row highlighted again" regression.usePrevious-based new-transaction diff with a latched diff state machine, and replaced theuseState-driven one-shot animation trigger inuseAnimatedHighlightStylewith a ref-based phase machine that re-arms on everyshouldHighlightfalse→true edge.Verification
Files that own this behaviour, all modified by #98744 (
gh pr view 98744 --json files):src/hooks/useNewTransactions.ts— decides which transactions are "new" (rail + diff).src/selectors/ReportMetaData.ts#L59-L106— new flag-instance classification and freshness window.src/libs/PendingNewTransactionFlags.ts— newtransactionID:flaggedAtflag keys.src/hooks/useAnimatedHighlightStyle/index.ts#L98-L110— plays the pulse.src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx#L157— the consumer in the reported screen (the report table), whose visibility gate this PR also swapped fromisFocusedto the newuseIsReportVisible.Path confirmation for this specific repro: "Duplicate expense" routes through
duplicateExpenseTransaction→requestMoney→MoneyRequestBuilder.ts#L705-L718, which is where this PR now stamps the highlight-rail flag ({transactionID}:{Date.now()}) intoreportMetadata.pendingNewTransactionIDsas optimistic data — i.e. the flag is written offline, at duplication time, with an offline timestamp.Root Cause
Under #98744 a row's highlight is re-playable, where before it was effectively one-shot:
useNewTransactionsno longer diffs against the previous render; it latches the added IDs intodiffState.addedIDsand re-derives the highlighted set from the current list on every render (useNewTransactions.ts#L90-L102), explicitly carrying the latch across removals and re-orders (#L74-L79). A transaction that leaves the list and comes back is deliberately treated as new again — the PR's own test asserts this:useNewTransactionsTest.ts#L1329"highlights a transaction again when it is removed and later re-added while the consumer stays mounted".PENDING_TRANSACTION_FRESHNESS_WINDOW= 60s from itsflaggedAtstamp (ReportMetaData.ts#L81), and the sweep that clears it only runs when the flagged row is actually present innewTransactionsand the report is visible (#L104-L131). So the most recently duplicated row — the second one in these steps — is the one whose flag/latch can still be alive when the network returns.useAnimatedHighlightStylenow tracks the previous value in a ref and re-arms whenevershouldHighlighttransitions to true (#L98-L110); refs reset on remount, so a recycled/remounted row whose flag is still active replays the full entry + pulse.Coming back online is exactly the moment that produces that edge: the reconnect burst (queued
RequestMoneyresponses clearingpendingAction, the report-actions refetch, and the resulting recomputes of the derivedREPORT_TRANSACTIONS_AND_VIOLATIONSvalue that feedsMoneyRequestReportActionsList.tsx#L144-L149, whereisOfflineis itself an input) rewrites the transaction list several times in quick succession. Any intermediate list in which the newest duplicate is momentarily absent and then present re-enters it intonewTransactions, flippingshouldHighlightback to true and replaying the pulse — the reported behaviour.I have pinned the mechanism to this rewrite with high confidence; which precise intermediate Onyx update drops and restores the row during the reconnect burst is not something I could confirm statically, which is a further argument for reverting rather than patching one edge.
melvin-bot commentedon Sep 10, 2026
If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.
If a regression has occurred and you are the assigned CM follow the instructions here.
If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.
IuliiaHerets commentedon Sep 10, 2026
This issue is not repro on Windows 10/Chrome Build v9.4.75-1
Recording.1575.mp4
[-]Report - Second expense duplicated offline, is higlighted again after returning online.[/-][+][Due for payment 2026-09-17] Report - Second expense duplicated offline, is higlighted again after returning online.[/+]melvin-bot commentedon Sep 10, 2026
@abzokhattab / @TaduJR
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.75-3 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-09-17. 🎊
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]