Skip to content

Redirect the expense of a single-expense report to the report view - #99811

Draft
MelvinBot wants to merge 3 commits into
mainfrom
claude-redirectSingleExpenseReportThread
Draft

Redirect the expense of a single-expense report to the report view#99811
MelvinBot wants to merge 3 commits into
mainfrom
claude-redirectSingleExpenseReportThread

Conversation

@MelvinBot

Copy link
Copy Markdown
Contributor

Explanation of Change

A report that holds exactly one expense already renders that expense inline — shouldDisplayReportTableView skips the table view for it, and the report's action list is merged with the transaction thread's actions. That makes the expense's own transaction thread a duplicate of the report, so landing on it is the wrong destination.

Several call sites already encode that rule individually — Search does it in getReportIDForTransaction and createAndOpenSearchTransactionThread, and the Home "review flagged expenses" row does it inline — but nothing enforces it for a route that arrives some other way (deep link, push notification, a stale history entry, or any navigation that resolved the IOU action's childReportID without checking the transaction count).

This PR adds OneTransactionThreadRedirectHandler, a render-nothing handler mounted in ReportScreen next to the existing route handlers. When the route lands on the transaction thread of a single-expense report it replaces the route with the report itself, so every entry point ends on the single-expense report view.

The redirect deliberately does not fire when:

  • a reportActionID is in the route — that deep link points at a specific message and replacing the route would drop its anchor;
  • the parent action is a "send money" action — isOneTransactionThread excludes those too, because the report and the thread are not interchangeable there;
  • the parent report's transactionCount is not 1 — the action-based derivation reads whatever report actions are in Onyx, so a multi-expense report that is still paginating in could otherwise look like a single-expense one for a moment.

It replaces (forceReplace) rather than pushes so back navigation doesn't return to the thread, and it reuses the route's own backTo for the same reason. In the Search RHP it stays in the RHP by rebuilding the SEARCH_REPORT route instead of the inbox one.

Fixed Issues

$ #99356
PROPOSAL:

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review".

Suggested steps:

  1. Submit a single expense to a workspace so it lands in an expense report holding exactly one expense.
  2. Open that expense from the chat. Verify you land on the expense report (/r/<expenseReportID>), not the standalone expense view.
  3. Copy the expense's own transaction thread URL, open it directly, and verify you are redirected to /r/<expenseReportID>.
  4. Verify the report still shows the expense details and that adding a comment works.
  5. Add a second expense to the same report, open one of the two expenses, and verify it still opens that expense's own thread (no redirect).
  6. Open a link to a specific message inside the transaction thread of a single-expense report and verify the message is still highlighted (no redirect).

Automated coverage: tests/navigation/OneTransactionThreadRedirectHandlerTest.tsx.

  • Verify that no errors appear in the JS console

Offline tests

// TODO: The human co-author must confirm the offline behavior before marking this PR as "ready for review".

Offline expectation: the redirect reads Onyx data that is already cached, so opening the transaction thread of a single-expense report while offline should still land on the report.

QA Steps

// TODO: The human co-author must fill out the QA steps before marking this PR as "ready for review". These must be filled out, or the issue title must include "[No QA]."

  • 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 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 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

MelvinBot and others added 3 commits August 29, 2026 02:46
…ort view

Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Result: pass — all 6 test steps verified on Web (dev NewDot).

Created a workspace, submitted a €25 "Test Merchant" expense into a single-expense report (R00erFLmAaqR / 8921121751637139), then drove each step. Opening the only expense — from the chat preview or from its own thread entry point — always landed on the report. A message-anchored deep link into that thread was left alone and highlighted the message. After adding a second expense, each expense opened its own thread again.

Step Status Observation
1. Submit a single expense to a workspace so it lands in an expense report holding exactly one expense pass Submitted €25.00 "Test Merchant"; Inbox shows "Expense Report 2026-08-29" with a "1 expense" badge, Report ID R00erFLmAaqR / 8921121751637139.
2. Open that expense from the chat — verify you land on the expense report, not the standalone expense view pass Clicking the money-request preview opened a view whose header, breadcrumb, and Report field all read "Expense Report 2026-08-29"; View Details confirmed Report ID R00erFLmAaqR, identical to opening the report directly.
3. Open the expense's own transaction thread URL directly — verify redirect to /r/<expenseReportID> pass Clicking "1 Reply" (the transaction-thread entry point) landed on the full report view. Network trace showed the pre-redirect route /r/1823360041683454/8047992094615092167 replaced by /r/8921121751637139.
4. Verify the report still shows the expense details and that adding a comment works pass Report displayed Amount/Merchant/Category/Date rows; sent "Testing comment on the report" and it appeared in the activity feed immediately.
5. Add a second expense, open one of the two — verify it still opens that expense's own thread (no redirect) pass Added "Second Merchant" €15 to the same report (2 expenses, €40, "2 Replies"). Clicking the "Test Merchant" row opened "€25.00 for Test Merchant" with breadcrumb "From Expense Report 2026-08-29" — its own thread.
6. Open a link to a specific message inside the transaction thread of a single-expense report — verify the message is still highlighted (no redirect) pass While the report held 1 expense, opened /r/1823360041683454/8047992094615092167: the target action was highlighted in yellow with no redirect, matching the reportActionID bypass.
7. Verify that no errors appear in the JS console pass (with caveat) Console capture is unsupported on the web agent-device platform, so this could not be read directly. No error banners, broken screens, or non-200 API calls were observed across the run.
Evidence (5)

Clicking the single expense from chat opens the expense report view

Clicking the single expense from chat opens the expense report view

Transaction thread entry point redirects to the full Expense Report view

Transaction thread entry point redirects to the full Expense Report view

Expense report showing expense details plus a newly added comment

Expense report showing expense details plus a newly added comment

With 2 expenses, clicking one opens its own transaction thread (no redirect)

With 2 expenses, clicking one opens its own transaction thread (no redirect)

Message deep link highlights the message without redirecting

Message deep link highlights the message without redirecting

view run

@MelvinBot

MelvinBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Android: Native — Result: pass — all 6 test steps verified on standalone Android NewDot.

Created a €25.00 "Test Merchant" expense on "Melvin Bot's Workspace" and submitted it into "Expense Report 2026-08-29" (report 6593282062794587, thread 8038705863313887). Both the chat tap and a direct transaction-thread link redirected to the report. A message-anchored deep link into that thread was left alone and highlighted the message. After adding a second expense, each expense opened its own thread again.

Step Status Observation
1. Submit a single expense to a workspace so it lands in an expense report holding exactly one expense pass FAB → Create expense → Manual, €25.00 "Test Merchant" (Other Business Expenses), then "Mark as done" → "Expense Report 2026-08-29", reportID 6593282062794587.
2. Open that expense from the chat — verify you land on the expense report, not the standalone expense view pass Tapping "View" on the transaction row in "Melvin Bot's expenses" opened header "Expense Report 2026-08-29" with breadcrumb "From Melvin Bot's expenses in Melvin Bot's Workspace" — the report view, not a bare transaction title.
3. Open the expense's own transaction thread URL directly — verify redirect to /r/<expenseReportID> pass Got the thread reportID 8038705863313887 via "Copy link" on a thread-native message, sent .../r/8038705863313887 as a chat message and tapped it: landed on "Expense Report 2026-08-29", not "€25.00 for Test Merchant".
4. Verify the report still shows the expense details and that adding a comment works pass Redirected report showed Receipt / Amount €25.00 / Description / Merchant / Date / Category rows; sent "Verify comment works on redirected report view" and it appeared immediately.
5. Add a second expense, open one of the two — verify it still opens that expense's own thread (no redirect) pass Retracted the report, added €15.00 "Second Merchant" (2 expenses, €40.00). Tapping the first row opened "€25.00 for Test Merchant"; the second opened "€15.00 for Second Merchant" — neither redirected.
6. Open a link to a specific message inside the transaction thread of a single-expense report — verify the message is still highlighted (no redirect) pass Sent .../r/8038705863313887/1172905027780974765 and tapped it: opened the standalone thread (breadcrumb "From Expense Report 2026-08-29") with the target message highlighted in yellow — the reportActionID bypass held.
7. Verify that no errors appear in the JS console pass adb logcat ReactNativeJS review showed AddComment, RequestMoney, ReopenReport, OpenReport, and ReadNewestAction all returning jsonCode:200. No FATAL/AndroidRuntime crashes or unhandled JS exceptions.
Evidence (7)

Single-expense report view after submission

Single-expense report view after submission

Tapping expense from chat lands on Expense Report view

Tapping expense from chat lands on Expense Report view

Transaction thread root URL redirects to Expense Report view

Transaction thread root URL redirects to Expense Report view

Report shows expense details plus a working added comment

Report shows expense details plus a working added comment

First expense in a 2-expense report opens its own transaction thread

First expense in a 2-expense report opens its own transaction thread

Second expense in a 2-expense report opens its own transaction thread

Second expense in a 2-expense report opens its own transaction thread

Message-specific thread link opens thread and highlights message, no redirect

Message-specific thread link opens thread and highlights message, no redirect

view run

Evidence (1)

Recordings

- [▶ Session recording of the verification run](https://melvinbot-assets.exops.io/session-33230351756-1-e7d63f40-0919-4bed-a567-d2c798ba13b6.mp4)

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

Labels

Melvin-Test-Android Melvin-Test-Web Triggers Melvin to run the testing steps of the PR on web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants