Skip to content

Spend - The newly added expense is not briefly highlighted when created via FAB #100788

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: 9.4.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: https://test-management.browserstack.com/projects/2219752/test-runs/73119149/folder/54000925/41237518/1742106064?sort=%2Bidentifier
Email or phone of affected tester (no customers): n/a
Issue reported by: Applause Internal Team
Bug source: Regression TC Execution
Device used: Mac/Safari, iPhone 13 iOS 26.6.1
App Component: Search

Action Performed:

  1. Go to https://staging.new.expensify.com
  2. Navigate to the Spend > Expenses page
  3. Click the global create button (FAB) > Create expense > Submit an expense

Expected Result:

The newly added expense appears in the table and the row is briefly highlighted to draw attention to it

Actual Result:

The newly added expense is not briefly highlighted

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7255728_1789011836881.Recording__2915.mp4

View all open jobs on GitHub

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

Activity

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

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/b354e220695c9655b93377076cb1114654485ba5eca5838f39731d5e8daf57ec

  3. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

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

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  4. melvin-bot commented on Sep 10, 2026

    @melvin-bot

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

  5. melvin-bot commented on Sep 10, 2026

    @melvin-bot

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

  6. MelvinBot commented on Sep 10, 2026

    @MelvinBot
    Contributor

    🔍 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: none found

    Recommendation: ROLL FORWARD

    The regression is a single guard in useAnimatedHighlightStyle that skips the highlight pulse if the transient shouldHighlight flag has already been retracted, and Search retracts that flag (300 ms) before the row's entry animation finishes (350 ms) — so the pulse is always skipped. A targeted fix to that guard is far smaller and safer than reverting a 50-file PR that also carries the wide-RHP width model rewrite and several other highlight fixes; revert only as a fallback if a fix can't land quickly.

    Assigned: @TaduJR (PR author), @abzokhattab and @blimpich (approving reviewers) from the highest-confidence causing PR only
    Labels: No changes. DeployBlockerCash is kept (App-side regression, App deploy is blocked). DeployBlocker was not present, so nothing was removed.

    📋 Detailed Analysis

    Evidence

    • Reproducible on staging, not on production — and src/hooks/useAnimatedHighlightStyle/index.ts differs between the two branches. Production still has the old useState-driven implementation that plays the pulse unconditionally from the entry animation's completion callback; staging has the rewritten phase-ref implementation from refactor: the new-transaction highlight rail and the wide RHP width model #98744.
    • #98744 is in the 9.4.75-0 checklist and merged 2026-09-09T00:07Z (merge commit 3aaa7f70ff1080404ce889ea6da46e558c60c358), just before the 9.4.75-0 staging cut (2b613353, 2026-09-09T19:18Z).
    • It is the only PR in this release that touches src/hooks/useAnimatedHighlightStyle/index.ts with logic changes (+83/-33). The only later commit on that file is from #100576, which is comment/whitespace-only.
    • The symptom matches the mechanism exactly: the entry (reveal) animation still runs, so the row appears, but the colour pulse never plays, so it is not highlighted — precisely what the report says.
    • Platform-independent (reported on iOS App and macOS Safari/Chrome), consistent with a pure JS timing bug rather than a platform issue.

    Verification

    Affected code path for the "new item highlight" on Spend › Expenses:

    1. src/hooks/useSearchHighlightAndScroll.ts#L264 — after the post-create refetch, the new transaction key is put in newSearchResultKeys.
    2. src/hooks/useSearchHighlightAndScroll.ts#L281-L292 — newSearchResultKeys is cleared after CONST.ANIMATED_HIGHLIGHT_START_DURATION = 300 ms.
    3. src/components/Search/hooks/useSearchSnapshot.ts#L328-L347 — stamps shouldAnimateInHighlight on the matching row.
    4. TransactionListItemWide.tsx#L147-L153 and TransactionListItemNarrow.tsx#L86-L92 — pass it as shouldHighlight into useAnimatedHighlightStyle with the default entry timings.
    5. src/hooks/useAnimatedHighlightStyle/index.ts — plays the entry then the pulse. This is the file refactor: the new-transaction highlight rail and the wide RHP width model #98744 rewrote.

    Confirmed #98744 modifies file 5 (and file 4's sibling TransactionGroupListExpanded.tsx), and that staging's copy of files 1–5 is byte-identical to the code analysed here.

    Root Cause

    useAnimatedHighlightStyle on staging defers the colour pulse until the entry animation completes, then re-checks whether the row is still flagged:

    useAnimatedHighlightStyle/index.ts#L120-L142 — pulseIfStillHighlighted() returns early unless prevShouldHighlightRef.current is true, and it is invoked from the entry animation's completion callback.

    The timings make that check fail every time:

    • entry = ANIMATED_HIGHLIGHT_ENTRY_DELAY (50) + ANIMATED_HIGHLIGHT_ENTRY_DURATION (300) = 350 ms before the pulse is even considered;
    • Search retracts shouldHighlight after ANIMATED_HIGHLIGHT_START_DURATION = 300 ms.

    So by the time the completion callback runs, prevShouldHighlightRef.current has already been set back to false at L100-L108, and the pulse is dropped. The reveal still runs, which is why the row shows up un-highlighted instead of not showing up at all.

    The FAB flow hits a second copy of the same mistake. Creating from the RHP means Search isn't focused, so the hook takes the awaitingFocus branch (L158-L167) and defers the pulse to the next focus event. But playPhaseRef is forced back to 'idle' the moment the flag retracts (L105-L106), so when focus finally arrives the listener's guard at L150 bails out. Both branches lose the pulse.

    The underlying assumption — that a still-set shouldHighlight means "this row wasn't recycled" — doesn't hold, because the flag's lifetime (300 ms) is much shorter than the full animation, which is durationHighlightItem = 3460 ms.

    Suggested fix (in src/hooks/useAnimatedHighlightStyle/index.ts): decide the pulse when the play is armed, not when the entry finishes. Latch the intent (e.g. a shouldPulseRef set at arm time) and have the entry-completion callback and the focus listener consult that latch rather than prevShouldHighlightRef.current / a phase that gets reset on retraction. Guard against the recycled-row case #98744 was defending against by keying the latch to the row's identity (item.keyForList) or clearing it on unmount, instead of on the transient flag flipping off. Note that two existing tests encode the current (buggy) behaviour and will need updating: tests/unit/useAnimatedHighlightStyleTest.tsx — "does not pulse when the highlight is cleared before the screen regains focus" and "does not pulse a row whose highlight was retracted while the entry was still playing".

    An alternative one-liner — widening Search's retract window from ANIMATED_HIGHLIGHT_START_DURATION to durationHighlightItem in useSearchHighlightAndScroll — would also unblock both branches, but it keeps hasQueuedHighlights / handleSelectionListScroll armed ~3.5 s longer and re-stamps rows for that whole window, so it carries more behavioural risk than fixing the guard.

  7. NikkiWines commented on Sep 10, 2026

    @NikkiWines
    Contributor

    #98744 was reverted #100810

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.EngineeringHourlyKSv2

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions