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-10-16] [$175] Reports- preview Disabled submit button not displayed to prevent self-approval wp #100709
Version Number: 9.4.74-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:#97946 Email or phone of affected tester (no customers):applausetester+dk45@applause.expensifail.com Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: Windows 11 Home/Chrome App Component: Money Requests
The report preview never renders a Submit button in this state, so there is nothing to disable. The two Submit surfaces resolve their action differently:
Report header — ReportPrimaryActionUtils.ts:155 blocks the SUBMIT action for prevented self-approval only when the current user is not the submitter (&& !isReportSubmitter). The submitter still gets SUBMIT, and SubmitPrimaryAction then renders it disabled.
Report preview — canSubmit in ReportPreviewActionUtils.ts:76-78 has the same check without the !isReportSubmitter qualifier, so it returns false for the submitter. getReportPreviewAction therefore never returns SUBMIT, and ReportPreviewActionButton falls through to null and renders only the View button.
This makes the gate added by #97946 (SubmitActionButton.tsx:124-133, isDisabled at L195) unreachable for the prevent-self-approval branch — the component is never mounted. Its unit test mocks shouldBlockSubmitDueToPreventSelfApproval, so it passes while the real app skips the component, which is why this only surfaced in QA.
Why the underlying "block submission" condition really is true here
shouldBlockSubmitDueToPreventSelfApproval (ReportUtils.ts:10102-10111) compares getNextApproverAccountID(report) against report.ownerAccountID. With a self-approval workspace, getApprovalChain pops the owner from the chain when preventSelfApproval is set, leaving it empty, so getNextApproverAccountID falls back to getSubmitToAccountID = the owner. Both gates agree the submission is blocked; they only disagree on whether to hide or disable the button.
Verification note: I could not reproduce the blocked state at runtime — "Prevent self-approvals" is behind the Control-plan upgrade paywall on a test workspace, and the test account is on Collect. With the setting off I confirmed the control case: preview card and report header both show an enabled Submit. The root cause above rests on code evidence, not a live repro.
What changes do you think we should make in order to solve the problem?
Align canSubmit with the header's isSubmitAction: in ReportPreviewActionUtils.ts:76, add the !isSubmitter qualifier (the local isSubmitter is already computed at L57) so the early return only applies to non-submitters.
The preview action then resolves to SUBMIT for the submitter, SubmitActionButton mounts, and the isDisabled gate from #97946 renders it disabled — matching the header and the documented expected result. No change to SubmitActionButton is needed.
Add a unit test for canSubmit/getReportPreviewAction covering preventSelfApproval + submitter → SUBMIT (there is currently no test file for ReportPreviewActionUtils), so the preview and header gates cannot drift apart again.
What alternative solutions did you explore? (Optional)
Treat the preview's current hide-the-button behavior as intended and correct the QA/test plan instead. That still leaves the header showing a disabled Submit while the preview shows no Submit for the same report, so a product decision is needed either way. Hiding-vs-disabling parity should be settled before the fix lands.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/100709) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Please mark it as "Ready for review" when it's ready.
What changed: one line in src/libs/ReportPreviewActionUtils.ts:76 — canSubmit now qualifies the prevent-self-approval early return with !isSubmitter, matching isSubmitAction in the report header. The preview action resolves to SUBMIT for the submitter, SubmitActionButton mounts, and the isDisabled gate from #97946 renders it disabled. No change to SubmitActionButton was needed.
Regression tests: two cases added to tests/actions/ReportPreviewActionUtilsTest.ts — submitter expects SUBMIT, non-submitter expects not SUBMIT. I confirmed the first one fails on main and passes with the fix, so it's a real guard rather than a passing no-op.
Checks I ran locally
Check
Result
npm run lint-changed
✅ pass
npm run typecheck
✅ pass
npm run spell-changed
✅ pass
npm test — ReportPreviewActionUtilsTest, ReportPrimaryActionUtilsTest, MoneyRequestReportPreview, ReportPreviewActionButtonTest, SubmitActionButtonTest, useReportPreviewActionDecisionTest, ReportWorkflowTest
✅ 260 passed
Not applicable: npm run prettier (no such script here — ESLint enforces formatting), the React Compiler check (no .tsx changed), the Storybook smoke test (no component changed).
One correction to my earlier proposal: I said there was no test file for ReportPreviewActionUtils. There is — tests/actions/ReportPreviewActionUtilsTest.ts — so the new cases went there instead of into a new file.
Two things to confirm before merging:
I could not verify this in a browser. The automated web session for this run was stuck on a blank page and couldn't be driven, so the fix rests on unit-test and code evidence, not a live repro. Please confirm the disabled button visually when you test.
The hide-vs-disable question is still open. This PR makes the preview match the header, which is what the issue's expected result describes. If the intended product behavior is actually to hide Submit in both places, the header needs the change instead and this PR should be rejected.
The PR is on an upstream branch since I can't push to your fork — #101148 has the commands to copy it over if you'd rather own it from there.
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.6-6 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-10-16. 🎊
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: NA
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: NA
[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
Precondition:
NA
Test:
Go to Workspace settings > Workflows and enable "Prevent self-approvals".
Go to the workspace chat and create an expense.
Verify the report preview card in the chat shows a Submit button that is disabled, next to the View button.
Open the expense report and verify the header Submit button is disabled too, matching the preview.
Turn "Prevent self-approvals" off and verify both Submit buttons are enabled again.
Activity
applause-bot commentedon Sep 9, 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/382126e64fc48b24fc9274370557986caa27115a9184ed1d37d07244fd730702
MelvinBot commentedon Sep 9, 2026
Proposal
What is the root cause of that problem?
The report preview never renders a Submit button in this state, so there is nothing to disable. The two Submit surfaces resolve their action differently:
ReportPrimaryActionUtils.ts:155blocks the SUBMIT action for prevented self-approval only when the current user is not the submitter (&& !isReportSubmitter). The submitter still gets SUBMIT, andSubmitPrimaryActionthen renders it disabled.canSubmitinReportPreviewActionUtils.ts:76-78has the same check without the!isReportSubmitterqualifier, so it returnsfalsefor the submitter.getReportPreviewActiontherefore never returnsSUBMIT, andReportPreviewActionButtonfalls through tonulland renders only the View button.This makes the gate added by #97946 (
SubmitActionButton.tsx:124-133,isDisabledat L195) unreachable for the prevent-self-approval branch — the component is never mounted. Its unit test mocksshouldBlockSubmitDueToPreventSelfApproval, so it passes while the real app skips the component, which is why this only surfaced in QA.Why the underlying "block submission" condition really is true here
shouldBlockSubmitDueToPreventSelfApproval(ReportUtils.ts:10102-10111) comparesgetNextApproverAccountID(report)againstreport.ownerAccountID. With a self-approval workspace,getApprovalChainpops the owner from the chain whenpreventSelfApprovalis set, leaving it empty, sogetNextApproverAccountIDfalls back togetSubmitToAccountID= the owner. Both gates agree the submission is blocked; they only disagree on whether to hide or disable the button.Verification note: I could not reproduce the blocked state at runtime — "Prevent self-approvals" is behind the Control-plan upgrade paywall on a test workspace, and the test account is on Collect. With the setting off I confirmed the control case: preview card and report header both show an enabled Submit. The root cause above rests on code evidence, not a live repro.
What changes do you think we should make in order to solve the problem?
Align
canSubmitwith the header'sisSubmitAction: inReportPreviewActionUtils.ts:76, add the!isSubmitterqualifier (the localisSubmitteris already computed at L57) so the early return only applies to non-submitters.The preview action then resolves to
SUBMITfor the submitter,SubmitActionButtonmounts, and theisDisabledgate from #97946 renders it disabled — matching the header and the documented expected result. No change toSubmitActionButtonis needed.Add a unit test for
canSubmit/getReportPreviewActioncoveringpreventSelfApproval+ submitter →SUBMIT(there is currently no test file forReportPreviewActionUtils), so the preview and header gates cannot drift apart again.What alternative solutions did you explore? (Optional)
Treat the preview's current hide-the-button behavior as intended and correct the QA/test plan instead. That still leaves the header showing a disabled Submit while the preview shows no Submit for the same report, so a product decision is needed either way. Hiding-vs-disabling parity should be settled before the fix lands.
Related issues
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/100709)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run · no recording available
melvin-bot commentedon Sep 9, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @thelullabyy (
External)melvin-bot commentedon Sep 9, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
melvin-bot commentedon Sep 14, 2026
@thelullabyy Huh... This is 4 days overdue. Who can take care of this?
thelullabyy commentedon Sep 15, 2026
@MelvinBot please draft a PR for me to test your suggestions
MelvinBot commentedon Sep 15, 2026
🤖 Draft PR created: #101148
Please mark it as "Ready for review" when it's ready.
What changed: one line in
src/libs/ReportPreviewActionUtils.ts:76—canSubmitnow qualifies the prevent-self-approval early return with!isSubmitter, matchingisSubmitActionin the report header. The preview action resolves toSUBMITfor the submitter,SubmitActionButtonmounts, and theisDisabledgate from #97946 renders it disabled. No change toSubmitActionButtonwas needed.Regression tests: two cases added to
tests/actions/ReportPreviewActionUtilsTest.ts— submitter expectsSUBMIT, non-submitter expects notSUBMIT. I confirmed the first one fails onmainand passes with the fix, so it's a real guard rather than a passing no-op.Checks I ran locally
npm run lint-changednpm run typechecknpm run spell-changednpm test—ReportPreviewActionUtilsTest,ReportPrimaryActionUtilsTest,MoneyRequestReportPreview,ReportPreviewActionButtonTest,SubmitActionButtonTest,useReportPreviewActionDecisionTest,ReportWorkflowTestNot applicable:
npm run prettier(no such script here — ESLint enforces formatting), the React Compiler check (no.tsxchanged), the Storybook smoke test (no component changed).One correction to my earlier proposal: I said there was no test file for
ReportPreviewActionUtils. There is —tests/actions/ReportPreviewActionUtilsTest.ts— so the new cases went there instead of into a new file.Two things to confirm before merging:
The PR is on an upstream branch since I can't push to your fork — #101148 has the commands to copy it over if you'd rather own it from there.
view run · no recording available
thelullabyy commentedon Sep 18, 2026
Reviewing...
25 remaining items
melvin-bot commentedon Oct 9, 2026
@thelullabyy
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.6-6 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-10-16. 🎊
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]
melvin-bot commentedon Oct 12, 2026
@thelullabyy Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
madmax330 commentedon Oct 13, 2026
Waiting on regression period
melvin-bot commentedon Oct 14, 2026
@thelullabyy Eep! 4 days overdue now. Issues have feelings too...
melvin-bot commentedon Oct 15, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Oct 15, 2026
Payment Summary
Resolving PRs:
Show the disabled Submit button in the report preview when self-approval is prevented #101148
Reviewer: @thelullabyy owed $175 via NewDot
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Oct 17, 2026
Payment Summary
Contributor+: @thelullabyy due $250 via NewDot
@thelullabyy plz complete the Contributor+ checklist and tag me in a post once you have. Thx
thelullabyy commentedon Oct 18, 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: NA
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: NA
[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
Precondition:
Test:
Do we agree 👍 or 👎
thelullabyy commentedon Oct 18, 2026
Thansk @mallenexpensify I posted the checklist