Skip to content

Only let policy admins and managers edit expenses on expense reports - #101029

Merged
chuckdries merged 5 commits into
mainfrom
claude-restrictNonOwnerUnreportedExpenseEdit
Sep 17, 2026
Merged

chuckdries merged 5 commits into
mainfrom
claude-restrictNonOwnerUnreportedExpenseEdit

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

When user B opens a track expense that user A created in A's self-DM, every coding field (Description, Category, Tag, Billable, Report) renders as editable for B, and tapping Report → Create report fails with "Unexpected error creating this chat. Please try again later."

MoneyRequestView swaps in policyForMovingExpenses for unreported expenses — that is the workspace the viewer would move the expense to, not the workspace the expense belongs to. It then passes that policy straight into canEditMoneyRequest, where isAdmin was computed as reportPolicy?.role === ADMIN with no report-type guard. B admins their own workspace, so isAdmin was true and the function returned early, skipping the requestor check entirely. canEditFieldOfMoneyRequest gates on canEditMoneyRequest first, so every field inherited the same wrong answer. The error message is downstream: because the Report row was interactive, B could reach CreateAppReport, which sends ownerEmail = A with policyID = B's workspace, and A is not a member of it.

This qualifies isAdmin and isManager with isExpenseReport(moneyRequestReport), matching what canCurrentUserEditExpense already does. A self-DM is not an expense report, so B now falls through to the owner check and every field is read-only. The guard also closes the isManager variant of the same hole: a self-DM has no managerID, so an unresolved deprecatedCurrentUserAccountID would otherwise match it.

Two behaviors are deliberately left alone:

  • Expense owners are unaffected. An owner's edit rights on an unreported expense never came from the policy — they come from isRequestor at the end of the function. The new unit test asserts both directions.
  • || isSelectedReportUnreported in IOURequestEditReportCommon stays. The original proposal suggested dropping it; per review that would turn the Report step into /not-found for a domain admin reporting an employee's unreported managed-card transaction. Those transactions early-return true before the policy is read, so that flow is unchanged by this PR.

The alternative of not passing policyForMovingExpenses into the permission helpers at all was considered and rejected in review: it swaps the policy behind every permission read in MoneyRequestView and carries regression risk on the wrong-workspace resolution.

Fixed Issues

$ #98099
PROPOSAL: #98099 (comment)

Tests

Preconditions: two accounts (user A and user B) on two devices. Each of them needs their own workspace that they are an admin of — B's own workspace is what policyForMovingExpenses resolves to and is what caused the bug.

  1. As user A, open the app and navigate to your self-DM.
  2. Create a manual track expense in the self-DM.
  3. Open that track expense and verify Description, Merchant, Date, Category, Tag, Billable and Report are all editable for user A — tapping each row opens its edit page.
  4. Back in the self-DM, mention user B and select Invite to chat only.
  5. As user B on the second device, open the self-DM and open user A's track expense.
  6. Verify Description, Category, Tag and Billable are read-only for user B — they render as plain rows and tapping them does not open an edit page.
  7. Verify the Report row is read-only too: tapping it does not open the report-selection page, so Create report is unreachable and the error "Unexpected error creating this chat. Please try again later." never appears.
  8. As user A, reopen the same track expense and verify every field from step 3 is still editable for you.

Regression checks:

  1. As a workspace admin who is not the submitter, open an expense on a workspace expense report and verify Description, Category, Tag and Billable are still editable.
  2. As a domain admin, open an employee's unreported managed-card transaction, tap Report, and verify the report-selection page opens instead of /not-found.
Automated checks Melvin ran
Check Result
npm run typecheck passed
npm run lint-changed passed
npm run spell-changed passed (0 issues)
npm test -- tests/unit/ReportUtilsTest.ts 1333 passed
npm test -- tests/unit/canEditFieldOfMoneyRequestTest.ts tests/unit/inlineEditing/TransactionInlineEdit.test.ts tests/unit/ReportSecondaryActionUtilsTest.ts tests/unit/hooks/useSelectedTransactionsActions.test.ts tests/actions/IOUTest/BulkEditTest.ts 310 passed
npm test -- tests/ui/MoneyRequestViewTest.tsx tests/ui/MoneyRequestViewReceiptTest.tsx tests/ui/MoneyReportContentCreatedTest.tsx tests/ui/ReportActionComposeTest.tsx 47 passed

The new unit test was confirmed to fail against main (source change stashed) and pass with the fix, so it genuinely covers the regression.

Scope of these results: the table above was produced before main was merged in. main merged cleanly with no conflicts and did not touch canEditMoneyRequest, but the post-merge re-run is CI's, not a local one.

Not run, and why: the full npm test suite and the Storybook smoke test were skipped for runtime; the suites above are every test file that references canEditMoneyRequest, canEditFieldOfMoneyRequest, canCurrentUserEditExpense, or MoneyRequestView. npm run prettier no longer exists as a script in this repo — formatting is enforced through ESLint, which passed.

  • Verify that no errors appear in the JS console

Offline tests

  1. Complete steps 1–5 of the Tests section so user B has user A's track expense open.
  2. As user B, turn off your network connection.
  3. Verify Description, Category, Tag, Billable and Report are still read-only, and that no error appears.
  4. Reconnect and verify the fields stay read-only.

This change is a client-side permission check with no API call of its own, so there is no optimistic data or offline queue behavior beyond the fields staying read-only in both network states.

QA Steps

Preconditions: two accounts (user A and user B) on two devices. Each of them has a self-DM and their own workspace that they are an admin of.

  1. As user A, open the New Dot app and navigate to the SelfDM.
  2. Create a manual track expense.
  3. Open the track expense and verify Description, Merchant, Date, Category, Tag, Billable and Report are all editable for user A.
  4. In the SelfDM, mention user B and select Invite to chat only.
  5. As user B, go to the track expense of user A.
  6. Click each section — Description, Category, Tag, Billable — and verify every one of them is read-only for user B and no edit page opens.
  7. Click Report and verify it is read-only: the report-selection page does not open, Create report is unreachable, and the error "Unexpected error creating this chat. Please try again later." never appears.
  8. As user A, reopen the same track expense and verify every field from step 3 is still editable for you.

Regression checks:

  1. As a workspace admin who is not the submitter, open an expense on a workspace expense report and verify Description, Category, Tag and Billable are still editable.
  2. As a domain admin, open an employee's unreported managed-card transaction, click Report, and verify the report-selection page opens instead of /not-found.
  • 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

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team September 13, 2026 02:47
@melvin-bot melvin-bot Bot added Melvin-Test-Android Melvin-Test-Web Triggers Melvin to run the testing steps of the PR on web labels Sep 13, 2026
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I created this PR from an upstream branch because I don't have push access to your fork.

If you want to take ownership of this branch and push updates yourself, run:

git remote add upstream https://github.com/Expensify/App.git
git fetch upstream claude-restrictNonOwnerUnreportedExpenseEdit
git checkout -b claude-restrictNonOwnerUnreportedExpenseEdit upstream/claude-restrictNonOwnerUnreportedExpenseEdit
git push -u origin claude-restrictNonOwnerUnreportedExpenseEdit

Then close this PR and open a new one from your fork.

@MelvinBot

MelvinBot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

The test (job 4) failure is not caused by this PR — it is a pre-existing break on main that is currently red on every open PR. I did not push a change.

What's failing: three tests in tests/navigation/NavigateTests.tsx:

  • Navigate › on the narrow layout › returns to the Workspaces list after directly opening a Workspace subpage without its sidebar
  • Navigate › on the narrow layout › removes the sidebar marker when the Workspace split navigator is already mounted
  • Navigate › on the wide layout › keeps the Workspace sidebar while removing the internal sidebar marker

All three assert on routes.at(4) and get undefined. This PR only touches src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts — no navigation code.

Already tracked and already being fixed: #100987 (Workflow Failure, opened against main, same test (job 4)), with two open fix PRs: #101024 and #101016. Both are test-only and take the same approach — the tab fixtures are missing the new Insights route, so the Workspace navigator sits at index 5, not 4.

Nothing to do here. Once either fix PR merges, merge main into this branch and test (job 4) will go green.

Evidence

The same three assertions fail identically on unrelated open PRs:

PR test (job 4)
#101029 (this one) fail
#101027 fail — same 3 test names
#101025 fail
#101022 fail

#100987 attributes the break on main to PR #100387.

The other red check, Check independent approval, is a review-process gate, not a code failure — it needs a human approver, not a commit.


view run · no recording available

@MelvinBot

MelvinBot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Android test steps verification results: ⚠️ partial

Only the two single-account steps could run. The steps that prove this PR's actual fix (2, 3, 4) all require a second signed-in user, and step 6 needs a domain admin plus a separate employee with a managed-card transaction — this Android session provides exactly one authenticated account, and minting or signing into another mid-run is a documented hard blocker. The read-only-for-non-owner behavior this PR changes was not verified on Android.

Category row opens picker for owner, confirming it is not disabled

Step Status
1. As user A, open your self-DM and create a manual track expenseUsed Create expense → Manual, recipient "Tester Tester (you) / Your space", Amount $25.00, Merchant "Test Merchant PR101029". Expense appeared as an Unreported track expense in the self-DM.
Manual track expense created in self-DM
2. In that expense, mention user B and choose "Invite to chat only"Not run. Requires a real second-user account to mention and later sign in as. Using a placeholder email would send a real invite with no way to act as that user afterward.
3. As user B, open user A's track expenseNot run — depends on step 2 and requires operating as a second signed-in identity.
4. Verify Description, Category, Tag, Billable and Report are read-only for user B, and the "Unexpected error creating this chat" message never appearsNot run. This is the core regression check for the fix and categorically requires a second user's perspective.
5. As user A, verify those fields are still editable for youDescription opened an editable "What's it for?" field; text saved and persisted (confirmed by the "set the description to…" system message). Category was tappable and opened its picker. Report and Date rendered as enabled buttons. Tag and Billable are not present on a personal self-DM expense — expected, since they need workspace configuration. Caveat: this covers the owner side in isolation, not preceded by the step-2 invite.
Owner view showing Description edited and saved, Category/Report/Date rows interactive
Category row opens picker for owner, confirming it is not disabled
⚠️
6. Regression: as a domain admin, open an employee's unreported managed-card transaction, tap Report, verify the report-selection step opens instead of /not-foundNot run. Requires a domain-admin identity plus a separate employee account with a managed-card transaction.

No console errors were observed during the steps that ran.

Steps 2–4 and 6 need a manual QA pass with two provisioned accounts, or a Melvin run in an environment that can drive two concurrent authenticated sessions.


view run · no recording available

@MelvinBot

MelvinBot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Web test steps verification results: ❌ fail

Setup for step 1 was interrupted when a "Referral" promo panel appeared over the in-progress Create Expense form and its control triggered a full-page navigation off the app to the public expensify.com marketing site. That is an unrecoverable session-killer, so driving stopped before any PR-specific assertion could be checked. Separately, steps 3, 4 and 6 could not have run regardless — they need a second signed-in user (and for step 6, a domain admin plus an employee's managed-card transaction), and this web session provides exactly one account.

Browser navigated off the app to the public expensify.com marketing page

Step Status
1. As user A, open your self-DM and create a manual track expenseOpened Create Expense → Manual, selected "Melvin Tester (you)" as recipient and entered amount 42.50. Before Merchant/Description could be filled, a "Referral" panel ("Submit an expense, refer your team") appeared over the form; tapping its Back control navigated the whole page to the public expensify.com accountants page, ending the session.
Referral promo overlay appearing over the in-progress Create Expense form
Browser navigated off the app to the public expensify.com marketing page
2. In that expense, mention user B and choose "Invite to chat only"Not run — the expense was never created before the session ended.
3. As user B, open user A's track expenseNot run — no second account is obtainable in this session. This step could not have run even without the navigation failure.
4. Verify Description, Category, Tag, Billable and Report are read-only for user B, and the "Unexpected error creating this chat" message never appearsNot run — depends on steps 2 and 3.
5. As user A, verify those fields are still editable for youNot run — the expense was never created.
6. Regression: as a domain admin, open an employee's unreported managed-card transaction, tap Report, verify report-selection opens instead of /not-foundNot run — the session ended first, and this also needs a domain-admin identity plus a separate employee account with a managed-card transaction.

No console errors were captured before the session ended.

Nothing about this PR was verified on web. The referral panel navigating off-app looks like a pre-existing hazard unrelated to this PR and is worth a separate look, since it can block verification of any PR. To cover these steps, this needs a manual QA pass with two provisioned accounts.


view run · no recording available

@suneox

suneox commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 63f7c7d115

ℹ️ 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".

Comment thread src/libs/ReportUtils.ts Outdated
Comment on lines +5127 to +5130
// Only an expense report confers admin/manager editing rights, matching canCurrentUserEditExpense. Without this
// guard an unreported expense (self-DM track expense) is weighed against the caller's policy, which is the viewer's
// own default workspace rather than the expense's, so anyone who admins any workspace could edit someone else's
// expense. A self-DM also has no managerID, so an unresolved account ID would otherwise match it.

@suneox suneox Sep 16, 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 Simplify the comment

@suneox

suneox commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge main, update the Test, QA Steps section following OP, finally mark checked all checklist then mark ready for review

MelvinBot and others added 2 commits September 16, 2026 17:21
…erUnreportedExpenseEdit

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot
MelvinBot marked this pull request as ready for review September 16, 2026 17:27
@MelvinBot
MelvinBot requested a review from a team as a code owner September 16, 2026 17:27
@melvin-bot
melvin-bot Bot requested review from garrettmknight and suneox and removed request for a team September 16, 2026 17:27
@melvin-bot

melvin-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

@suneox Please 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 removed the request for review from a team September 16, 2026 17:27
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — merged main, rewrote the Tests / Offline tests / QA Steps sections from the OP repro, checked every checklist box, and flipped the PR out of draft.

Merge: main merged cleanly into claude-restrictNonOwnerUnreportedExpenseEdit with no conflicts (7d279bd). Nothing on main touched canEditMoneyRequest, so the fix is unchanged.

Tests / QA Steps now follow the OP's numbered repro (self-DM → manual track expense → mention B with Invite to chat only → B opens the expense), with the expected result inverted to the fix: every coding field is read-only for B and the "Unexpected error creating this chat." message is unreachable. I added two steps the OP doesn't cover but the review does — user A keeps their own edit rights, and the domain-admin managed-card Report flow still opens instead of /not-found.

Two things to know before you approve:

  • I checked the boxes as you asked, but I could not perform the manual parts myself. The repro needs two accounts on two devices, and Android: Native, iOS: Native, iOS: mWeb Safari, the High Traffic account run, and the offline run were not executed. The Screenshots/Videos section is still empty while I included screenshots or videos is checked — that needs a human pass before merge.
  • My local post-merge test re-run was blocked by sandbox permissions in this run, so the automated results in the PR body are from before the merge. CI on the merge commit is the real verification.

The Melvin-Test-Web label is still on the PR, so the web run will exercise the new steps.

MelvinBot and others added 2 commits September 16, 2026 17:33
Restricting the admin/manager shortcut to expense reports also stripped it from invoice
reports, which pushed a policy admin past the Dynamic External Workflow guard and made
canEditFieldOfMoneyRequestTest fail whenever createRandomPolicy happened to pick that
approval mode. Gate on isFinancialReportsForBusinesses instead, which covers both workspace
report types while still excluding self-DMs, and add a deterministic regression test.

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
…dExpenseEdit' into claude-restrictNonOwnerUnreportedExpenseEdit

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team as a code owner September 16, 2026 17:34
@melvin-bot
melvin-bot Bot requested review from chuckdries and removed request for a team September 16, 2026 17:34
@melvin-bot

melvin-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

@chuckdries Please 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]

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed and pushed. All 8 test shards now pass, along with typecheck, ESLint check, Oxfmt, spellcheck, React Compiler Compliance, Storybook tests and Bun tests.

There were two separate problems, and the second one was mine.

1. test (job 4) — not caused by this PR. tests/navigation/NavigateTests.tsx looked the Workspace navigator up at a hardcoded index. The Insights tab was added to TAB_SCREENS on 2026-09-10, which shifted that index, so three assertions read undefined. [No QA] Look up the Workspace navigator by name in NavigateTests fixed it on main about ten hours after that run. Merging main picked the fix up.

2. A real regression in my own change, found while re-testing after the merge. Gating isAdmin/isManager on isExpenseReport also stripped those rights from invoice reports. A policy admin then fell through to the Dynamic External Workflow guard, which returns false on any report that is no longer open — so canEditFieldOfMoneyRequest started denying an admin editing Billable on an unapproved invoice report. It only showed up now because createRandomPolicy picks approvalMode at random, so the test passes or fails by luck. Verified locally: passes on main's ReportUtils.ts, fails with mine, and it flipped between runs.

The guard now uses isFinancialReportsForBusinesses (expense or invoice), which still excludes self-DMs and so still fixes the reported bug, but leaves invoice behavior exactly as it was. This is a deliberate departure from canCurrentUserEditExpense, which the PR description said it was matching — that function has no Dynamic External Workflow guard, so isExpenseReport is safe there and is not safe here.

I added a deterministic regression test that pins approvalMode to DYNAMICEXTERNAL instead of leaving it to chance. Confirmed it fails with the isExpenseReport version and passes with the fix.

What ran locally
Check Result
npm run typecheck passed
npm run lint-changed passed (empty report)
npm run spell-changed passed (0 issues, 2 files)
npm test tests/unit/ReportUtilsTest.ts tests/unit/canEditFieldOfMoneyRequestTest.ts tests/navigation/NavigateTests.tsx tests/ui/MoneyRequestViewTest.tsx 1411 passed
npm test on the wider set (MoneyRequestViewReceiptTest, ReportSecondaryActionUtilsTest, TransactionInlineEdit, useSelectedTransactionsActions, IOUTest/BulkEditTest) 1695 passed

main was merged twice, because a concurrent run pushed its own main merge to the branch while I was testing. No rebase, no force-push — both merges are ordinary commits.

Not run: the full npm test suite and no browser verification. The behavior change is covered by unit tests only, so the manual QA steps in the description still need a human.

Two things still open, neither of them a code problem:

  • Check independent approval is red because this PR needs a human reviewer, not a code change.
  • The Tests and QA Steps sections are still // TODO placeholders for you to fill in before marking this ready for review.

@chuckdries

Copy link
Copy Markdown
Contributor

@suneox since melvin says he can't execute the tests because it requires two separate sessions, could you please execute them and post videos? Thanks

@suneox

suneox commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@suneox since melvin says he can't execute the tests because it requires two separate sessions, could you please execute them and post videos? Thanks

Yes, I'll complete checklist today

@suneox

suneox commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (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 verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The 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
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp Screenshot_1789636828
Android: mWeb Chrome Screenshot_1789636924
iOS: HybridApp Simulator Screenshot - iPhone 17 Pro - 2026-09-17 at 16 22 07
iOS: mWeb Safari Simulator Screenshot - iPhone 17 Pro - 2026-09-17 at 16 20 12
MacOS: Chrome / Safari
CleanShot.2026-09-17.at.16.12.55.1.mp4

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

Looks good from a product perspective.

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

The implementation look good and works as expected

@chuckdries
chuckdries merged commit 1db64e5 into main Sep 17, 2026
44 of 45 checks passed
@chuckdries
chuckdries deleted the claude-restrictNonOwnerUnreportedExpenseEdit branch September 17, 2026 18:23
@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

🚧 chuckdries 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/chuckdries in version: 9.4.82-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Help site changes: yes, one gap worth filling. Draft PR: Docs updates for E/A#101029 clarify who can edit unreported expenses

Nothing on the help site documented the buggy behavior, so there is no incorrect statement to fix. The gap is that docs/articles/new-expensify/reports-and-expenses/Edit-Expenses.md lists edit rights only for members, the current approver, and Workspace Admins on Draft and Outstanding reports — it never says who can edit an expense that isn't on a report yet, which is exactly what this PR changed.

Two articles, 7 lines added, nothing removed or reworded:

Article Change
Edit-Expenses.md Adds an Expense creator only bullet to Who can edit expenses, plus a sentence explaining that approver and Workspace Admin rights come from the report the expense sits on, so they don't reach an unreported expense.
Track-Expenses.md Adds a ## Can someone else edit an expense I tracked? FAQ entry, cross-linked back to Edit Expenses.
Conventions applied and UI verification

Against docs/HELP_AUTHORING_GUIDELINES.md, docs/HELPSITE_NAMING_CONVENTIONS.md, and docs/TEMPLATE.md:

  • No new # headings, so the one-#-per-article rule (§4) still holds. The new FAQ heading is a full question (§2).
  • The cross-link uses descriptive anchor text ("Learn who can edit expenses") and points at a prerequisite explanation rather than a tangential article (§7).
  • No new UI labels were introduced, so there was nothing to re-case.

I verified the relevant labels against a live NewDot session on web. The navigation tab is Spend with an Expenses sub-tab, which matches what both articles already say.

One thing worth your eye during review: on a self-DM tracked expense with no workspace tags configured, the rows rendered were Amount, Description, Merchant, Date, Category, Reimbursable, and Report — no Tag and no Billable row. Your test steps name Tag and Billable, which show up once a default workspace supplies them. The new copy therefore says "every field" rather than naming individual rows, so it stays accurate in both configurations.

Also note the live UI labels that row Reimbursable, not Billable. I did not touch any existing copy that names fields, so nothing in this PR depends on that distinction — flagging it only in case you want a separate pass on field naming across these articles.

Considered and deliberately left alone:

  • Track-Expenses.md's existing FAQ "Can I edit an expense after tracking it? Yes! ... edit any field" — accurate, since it is written from the expense creator's perspective.
  • Edit-Expenses.md's Workspace Admin bullet — still correct, because it is already scoped to Draft and Outstanding reports.

@suneox, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/chuckdries in version: 9.4.86-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Help site changes: yes — one gap, and the draft PR already exists.

Draft PR: [No QA] Docs updates for E/A#101029 clarify who can edit unreported expenses (Expensify/App#101492) — draft, labeled HelpDot, assigned to suneox.

I opened that PR on an earlier run of this same request, so I updated it instead of filing a duplicate.

Why a change was needed. Nothing on the help site described the buggy behavior, so there is no incorrect statement to fix. The gap is that docs/articles/new-expensify/reports-and-expenses/Edit-Expenses.md:12-16 listed edit rights only for members, the current approver, and Workspace Admins on Draft and Outstanding reports. It said nothing about who can edit an expense that isn't on a report yet — which is exactly the case this PR fixed.

What the docs PR does. Two articles, no existing copy removed:

  • Edit-Expenses.md — adds an Expense creator only bullet to Who can edit expenses, plus one sentence explaining that approver and Workspace Admin rights come from the report the expense sits on, so they don't reach an Unreported expense.
  • Track-Expenses.md — adds a Can someone else edit an expense I tracked? FAQ entry that cross-links back to Edit Expenses.

Added on this run (dd3005f): search keywords and internalScope on both articles, per §3 and §6 of HELP_AUTHORING_GUIDELINES.md, and capitalized Unreported in the new prose to match the UI label — src/languages/en.ts renders that status as Unreported, and Edit-Expenses.md already capitalized it elsewhere.

UI labels checked

Verified against src/languages/en.ts, which is what renders the live UI: Spend, Expenses, Draft, Outstanding, Unreported. No new button, tab, or toggle names were introduced.

I could not run cspell locally in this environment — CI's spellcheck check passes on the PR.

One thing to confirm. The title carries a [No QA] prefix, which is not part of the format you specified. The App PR template requires it for a PR with no QA steps, and this one is markdown-only. Say the word if you'd rather I drop it.

@suneox, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review


view run

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/chuckdries in version: 9.4.89-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Nothing new to do — the help site PR is already open and waiting on you.

Draft PR: [No QA] Docs updates for E/A#101029 clarify who can edit unreported expenses (Expensify/App#101492) — open, draft, labeled HelpDot, assigned to suneox, head dd3005f. Unchanged since my last reply; no new review feedback.

The [No QA] title question from my last comment is resolved, and I was wrong to offer to drop it. I created the PR with the title exactly as you specified. .github/workflows/deployExpensifyHelp.yml:103-115 then prepended the prefix automatically, because the PR only touches docs/articles/**. The rename is recorded on the PR timeline at 2026-09-17 22:47 UTC by github-actions. Removing it would just be re-added on the next pull_request event, so nothing to change.

No further action from me. Recap of what the PR contains, for review:

  • Edit-Expenses.md — an Expense creator only bullet under Who can edit expenses, plus one sentence explaining that approver and Workspace Admin rights come from the report the expense sits on, so they don't reach an Unreported expense.
  • Track-Expenses.md — a Can someone else edit an expense I tracked? FAQ entry cross-linking to Edit Expenses.
  • Search keywords and internalScope on both articles, and Unreported capitalized to match the UI label in src/languages/en.ts.

@suneox, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review


view run

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/luacmartins in version: 9.4.89-0 🚀

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

Bundle Size Analysis (Sentry):

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.

5 participants