Skip to content

Stop showing the LHN RBR, Fix badge and To-do for companyCardRequired after submit - #100190

Merged
chuckdries merged 9 commits into
mainfrom
claude-companyCardRequiredRbrAfterSubmit
Sep 3, 2026
Merged

Stop showing the LHN RBR, Fix badge and To-do for companyCardRequired after submit#100190
chuckdries merged 9 commits into
mainfrom
claude-companyCardRequiredRbrAfterSubmit

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

After a report is submitted, a companyCardRequired violation still drove the submitter's LHN red dot, the Fix action badge and the Inbox To-do - but clicking Fix opened a report with nothing to act on. Unlike every other Rules > Expenses violation, companyCardRequired maps to no editable field (useViolations.ts maps it to none), has no Mark as cash and no dismiss, so only an admin turning the rule off can clear it.

This PR keeps the violation message visible to everyone on the expense, and stops it from driving the RBR / Fix / To-do for the submitter once the report is submitted.

src/libs/ReportUtils.ts

getViolatingReportIDForRBRInLHN builds a new excludedViolationNamesForLHN list — [COMPANY_CARD_REQUIRED] on processing reports, empty otherwise — and passes it to both operands of the && that decides the RBR. The pre-existing excludedNoticeNamesForLHN ([MODIFIED_AMOUNT]) is unchanged and still reaches the notice check only.
The exclusion is applied by violation name, before the type checks, so it holds no matter which bucket (violation / warning / notice) the back end assigns. To make that possible, hasNoticeTypeViolationsForRBRInLHN became the type-agnostic hasViolationOfAnyTypeForRBRInLHN: it takes both exclusion lists and folds the three previously-ORed calls into a single pass over the report transactions.
src/libs/Violations/ViolationsUtils.ts

hasVisibleViolationsForUser gains an optional excludedViolationNames parameter defaulting to [], so every existing caller is unaffected. Both operands of the && above have to judge the same set of violations — without this, the visibility check still saw companyCardRequired and let it vouch for a violation hidden from the submitter (missingCategory while auto-categorization is still running, for example), leaving the red dot lit with nothing behind it.

Fixed Issues

$#99793
PROPOSAL:#99793 (comment)

Tests

Preconditions

  • Create new accounts
  • Account A — workspace owner / admin, and the approver.
  • Account B — plain workspace member, and the submitter. B must not be an
  • admin of the workspace.
  • As A, the workspace must be on the Control plan.
  • As A: Workspace → More features → enable Company cards.
  • As A: Workspace → RulesExpenses → enable
  • Require company cards for all purchases.
  1. Sign in as B. Create a manual cash expense in the workspace (any amount,
  2. any merchant, any category)
  3. Open the expense and verify the message Company card purchases required
  4. is shown on it.
  5. Submit the report.
  6. Verify the workspace chat row in B's LHN doesn't show the red dot and the Fix badge.
  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

Same as tests

  • 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

…uired after submit

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team September 2, 2026 14:33
@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 2, 2026
@Eskalifer1

Copy link
Copy Markdown
Contributor

@codex review

@MelvinBot

Copy link
Copy Markdown
Contributor Author

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

To take ownership of this branch and be able to push updates, run:

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

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

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Two checks were failing. I fixed one and confirmed the other is unrelated.

Fixed — Oxfmt check (pushed as 2101796)

The ternary at src/libs/ReportUtils.ts:9892 was written across three lines, but it fits inside the 190-column printWidth in .oxfmtrc.json, so oxfmt collapses it to one. Whitespace only — no logic changed.

Not fixed — test (job 3) / NextStepUtilsTest

libs/NextStepUtils › buildNextStep › … › monthly on the 2nd fails only because today is the 2nd of the month. The test builds its own expected value from DateUtils.getNextNthOfMonth(2), which uses day < nth — on the 2nd that is 2 < 2, false, so it rolls to 2026-10-02. buildOptimisticNextStep returns 2026-09-02. The two disagree exactly once a month.

This PR touches src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts only, so it can't affect that assertion. Per policy I left the pre-existing flake alone rather than fixing it here.

Local verification
Check Result
npm run fmt (re-run, idempotent) ✅ no diff
npm run typecheck ✅ passed
npm run lint -- src/libs/ReportUtils.ts ✅ passed
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1308 passed
npm test -- tests/unit/NextStepUtilsTest.ts -t "monthly on the 2nd" ❌ reproduces the CI failure on an untouched file

npm run lint-changed could not run — this checkout has no merge base with origin/main. The CI ESLint check job already passed and will re-run on the new commit.

Check independent approval is also red; that one is a review-state gate, not a code failure.


view run · no recording available

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55f4dc2259

ℹ️ 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
Comment on lines +9945 to +9946
hasViolation(transaction, violations, currentUserEmailParam, currentUserAccountIDParam, report, currentUserEmailParam, policy, true) ||
hasWarningTypeViolation(transaction, violations, currentUserEmailParam, currentUserAccountIDParam, report, currentUserEmailParam, policy, true) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply visibility filtering to hard and warning violations

When a submitted expense has companyCardRequired alongside a currently hidden hard violation—for example, missingCategory while auto-categorization is pending—hasVisibleViolationsForUser() passes because the company-card violation itself is visible. After that violation is removed here, hasViolation() still accepts missingCategory because it does not call shouldShowViolation() (which explicitly hides that violation while isCategoryBeingAnalyzed() is true). The function therefore continues returning the report ID, leaving the LHN RBR, Fix badge, and To-do visible even though none of the remaining issues are actionable; apply the same per-violation visibility predicate before these type checks.

Useful? React with 👍 / 👎.

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 Take a look at this comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex is right, and I've fixed it in 2a9d573.

The premise checks out: of the three helpers I now OR together, only hasNoticeTypeViolation calls shouldShowViolation. hasViolation and hasWarningTypeViolation only check type, showInReview and dismissal. So the two operands of the && were reading different lists — hasVisibleViolationsForUser the raw one, my helper the name-filtered one — which let a visible companyCardRequired vouch for a hidden missingCategory that survived the filter. The dot stayed lit, which is the exact outcome this PR exists to prevent.

The fix moves shouldShowViolation into the same filter as the name exclusion, so both operands agree on what counts:

App/src/libs/ReportUtils.ts

Lines 9939 to 9947 in 2a9d573

const violations = rawViolations.filter(
(violation) =>
!excludedViolationNames.includes(violation.name) &&
// Only `hasNoticeTypeViolation` applies this predicate internally, so without it here a violation the user
// cannot even see — `missingCategory` while auto-categorization is still running, for example — would keep
// driving the red dot. `hasVisibleViolationsForUser` is ANDed ahead of this helper, but it reads the raw
// list, so a visible violation could otherwise vouch for a hidden one that is left after the name filter.
shouldShowViolation(report, policy, violation.name, currentUserEmailParam, currentUserAccountIDParam, true, transaction),
);

One correction to the framing, since it affects how you weigh this: it is not a regression. On main the same report shows the same red dot — just for a different reason (companyCardRequired itself satisfied the unfiltered hasViolations). main has the identical hole for its own modifiedAmount exclusion. The PR's considered violation set is a subset of main's at every branch, so it could only ever remove dots, never add one. What Codex found is a pre-existing gap that this PR would have inherited and left visible in one case, not something it broke.

Reachability, test, and verification

How you'd actually hit it. shouldShowViolation hides missingCategory while isCategoryBeingAnalyzed is true — either pendingAction === ADD or within a 60-second pendingAutoCategorizationTime window. Both violations are backend-generated, so pendingAction === ADD is largely mutually exclusive with having them. The realistic path is instant-submit: the report is born SUBMITTED, the backend returns both violations, and for up to a minute the category chip reads "analyzing" while the LHN dot stays on. Narrow and transient — but the same argument applies to any other hidden violation, and the fix covers all of them rather than special-casing this pair.

Test. Added should not surface RBR when the violation left next to companyCardRequired is hidden while the category is being analyzed. I confirmed it actually reproduces the bug: stashing the ReportUtils.ts change and re-running gives 1 failed, 5 passed on the companyCardRequired block; with the fix restored it passes.

Checks on 2a9d573:

Check Result
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1309 passed
npm test -- ViolationUtilsTest SidebarFilterTest SidebarOrderTest OptionsListUtilsTest navigateAfterOnboardingTest ✅ 614 passed
npm run typecheck ✅ passed
npm run lint -- src/libs/ReportUtils.ts tests/unit/ReportUtilsTest.ts ✅ passed
npm run fmt ✅ no diff beyond the intended change
npm run spell ✅ 0 issues in the changed files

npm run lint-changed and spell-changed still can't run in this checkout — no merge base with origin/main — so I ran lint and spell against the files directly instead.


view run

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Android test steps verification results: ❌ fail

I could not run the testing steps. The app crashed on the Android emulator with a fatal RedBox Fabric error (addViewAt: failed to insert view [608] into parent [550] at index 1, SurfaceMountingManager.kt:390) immediately after sign-in, before reaching Home. Dismissing the RedBox left a blank screen with zero accessibility nodes, and restarting Metro did not recover it. A second attempt found the app still blank, and the recovery call tore down the emulator session, so no further attempts were possible. None of the four steps were attempted — no results below are from the app under test.

Blank crashed app at the start of the retry run

Step Status
Reach Home / complete session setup before driving the testTapping Join produced a fatal RedBox: addViewAt: failed to insert view [608] into parent [550] at index 1 (SurfaceMountingManager.kt:390 via FabricUIManager.tryDispatchMountItems). After dismissing it and restarting Metro, the app stayed blank with 0 accessibility nodes. On the retry, appstate showed com.expensify.chat.dev/MainActivity foregrounded but not rendering.
Fatal RedBox Fabric crash after tapping Join on the sign-in screen
Blank screen after dismissing the RedBox
Still blank after restarting Metro
Step 1 — Create an expense that triggers the "Company card purchases required" violationNot attempted — the app was unusable before this step.
Step 2 — Submit the expense/report containing the violationNot attempted.
Step 3 — After submission, open the expense and confirm the violation message is still visibleNot attempted.
Step 4 — Confirm no LHN red dot, no Fix badge and no Inbox To-do after submitNot attempted.

Two things worth flagging:

  1. This PR has no documented testing steps. The ### Tests and ### QA Steps sections are still the // TODO: **The human co-author** must fill out... placeholders. I used the ## Action Performed steps from the linked issue After an expense with a “Company card purchases required” violation is submitted, the Fix / To-do action continues to be displayed for the expense. #99793 instead, plus the expected outcome from the PR description. Filling in the real Tests section would make a rerun unambiguous.
  2. The steps need multi-account setup that the steps themselves don't mention — an admin workspace with Workflows/Submit enabled and Rules > Expenses > "Company card purchases required" turned on. Spelling that setup out in the Tests section would help both a rerun here and QA.

Retriggering the Melvin-Test-Android label should get a fresh emulator session; the crash looked like an environment/bring-up failure rather than something in this diff, since it happened on the sign-in screen before any code path this PR touches was reached.


view run · view recording

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Oxfmt check is now green — my earlier commit 2101796 fixed it.

Nothing left to push. The three still-red checks are all outside this PR:

Check Why it's red
test (job 3) Repo-wide date flake, not this PR — see below
Check independent approval Review-state gate, not code
checklist (PR Reviewer Checklist) Awaiting a reviewer

The test (job 3) failure is a repo-wide flake that only fires on the 2nd of the month.

NextStepUtils › … › monthly on the 2nd builds its expected value from DateUtils.getNextNthOfMonth(2), which uses day < nth. Today that is 2 < 2 — false — so it rolls forward to 2026-10-02, while buildOptimisticNextStep returns 2026-09-02. The two only disagree on the Nth day itself.

It will pass on its own tomorrow. Per policy I'm not touching an unrelated flake from this PR, but the off-by-one in getNextNthOfMonth looks like a real bug worth its own issue — say the word and I'll file it.

Evidence it isn't this PR
  • This PR touches only src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts. The failing assertion is on eta.dateTime, produced by NextStepUtils/DateUtils.
  • An unrelated PR branch (fix/100112-cross-domain-search-navigation) fails the identical test in run 33644183003 today.
  • Reproduced locally on this branch: npm test -- tests/unit/NextStepUtilsTest.ts -t "monthly on the 2nd" → same diff.

Local checks on the pushed commit: npm run fmt idempotent ✅, npm run typecheck ✅, npm run lint -- src/libs/ReportUtils.ts ✅, tests/unit/ReportUtilsTest.ts 1308 passed ✅. CI now confirms Oxfmt check, ESLint check, typecheck, Bun tests, Storybook tests and test jobs 1, 2, 4, 5, 8 green.


view run · view recording

@Eskalifer1

Copy link
Copy Markdown
Contributor

@MelvinBot Take a step back and make an adversarial review of this PR

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Oxfmt check is green now — my previous push (2101796) fixed it. Nothing further to push.

The remaining red checks are not this PR's code:

Check Why it's red
test (job 3) Pre-existing repo-wide flake, see below
Check independent approval Review-state gate, not a code failure
PR Reviewer Checklist / checklist Review-process gate, not a code failure

test (job 3)NextStepUtilsTest, not caused by this PR

libs/NextStepUtils › buildNextStep › … › monthly on the 2nd fails only because today is the 2nd of the month. The test derives its own expected value from DateUtils.getNextNthOfMonth(2), which branches on day < nth — on the 2nd that is 2 < 2, false, so it rolls forward to 2026-10-02. buildOptimisticNextStep returns 2026-09-02. The two disagree exactly one day a month.

Every other PR running CI today hits it too — for example #100189, an unrelated PR, fails the identical assertion in the identical job. This PR only touches src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts, neither of which is in that code path.

Fixing it means changing getNextNthOfMonth (or the test's expectation) to agree with buildOptimisticNextStep on the boundary day. That's a separate change to unrelated code, so I'm leaving it rather than folding it into this PR.

Local verification of the Oxfmt fix
Check Result
npm run fmt (re-run, idempotent) ✅ no diff
npm run typecheck ✅ passed
npm run lint -- src/libs/ReportUtils.ts ✅ passed
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1308 passed
npm test -- tests/unit/NextStepUtilsTest.ts -t "monthly on the 2nd" ❌ reproduces the CI failure on an untouched file

On CI, Oxfmt check, ESLint check and typecheck all pass on the current head.


view run · view recording

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Oxfmt check is now green after 2101796. Nothing left for me to fix on this PR.

The three remaining red checks are all outside this PR's scope:

Check Why it's red
test (job 3) Known org-wide flake — #100134
Check independent approval Review-state gate, not a code failure
PR Reviewer Checklist / checklist Review-state gate, not a code failure

On test (job 3): it's the same NextStepUtilsTest › monthly on the 2nd failure as before, and there's already an open issue for it — Expensify/App#100134, "NextStepUtilsTest 'monthly on the 2nd' fails on the 2nd of every month, blocking all PR CI". It is failing on every PR in the repo today, not just this one. I'm leaving it alone rather than patching an unrelated test file inside this PR.

Why it fails only today

The test derives its own expected value from DateUtils.getNextNthOfMonth(2), which uses day < nth. On the 2nd that's 2 < 2 — false — so it rolls forward to 2026-10-02, while buildOptimisticNextStep returns 2026-09-02. The two only disagree on the Nth day itself.

This PR changes src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts only, so it cannot affect that assertion. I reproduced the failure locally on this branch and confirmed tests/unit/ReportUtilsTest.ts passes (1308 tests), npm run typecheck passes, and npm run fmt is now a no-op.

Once #100134 is fixed on main, merging main in will clear job 3.


view run · view recording

hasViolation and hasWarningTypeViolation never applied shouldShowViolation,
so once companyCardRequired was filtered out by name a hidden violation left
behind it could keep the red dot lit.

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Nothing left to fix on this PR — Oxfmt check is green after my previous push (2101796), and the one remaining red check is a known repo-wide flake.

test (job 3) — pre-existing, not from this PR

libs/NextStepUtils › buildNextStep › … › monthly on the 2nd fails today only because today is the 2nd of the month. It's already tracked in #100134 ("fails on the 2nd of every month, blocking all PR CI").

The test derives its own expected value from DateUtils.getNextNthOfMonth, which uses day < nth — on the 2nd that's 2 < 2, false, so it rolls forward to 2026-10-02, while buildOptimisticNextStep returns 2026-09-02. This PR only touches src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts, neither of which is in that path. Per policy I'm not fixing an unrelated flake inside this PR.

The other two red checks are review gates, not code failures: Check independent approval and checklist (the PR Reviewer Checklist workflow). Both clear through review, not a push.

Current check state
Check Result
Oxfmt check ✅ pass (was the real failure; fixed in 2101796)
ESLint check ✅ pass
typecheck ✅ pass
spellcheck ✅ pass
Bun tests, Storybook tests, test jobs 1, 4–8 ✅ pass
test (job 3) NextStepUtilsTest date flake — #100134
Check independent approval, checklist ❌ review gates

I reproduced the NextStepUtilsTest failure locally on this branch and confirmed it also has nothing to do with the changed files. ReportUtilsTest.ts passes locally (1308 tests).


view run · view recording

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Oxfmt check is fixed and green. The one remaining code failure is a pre-existing race in NextStepUtils that only misfires on the 2nd of the month — today. Nothing in this PR can cause it, so I did not touch it.

test (job 3)NextStepUtils › … › monthly on the 2nd

Production builds the auto-submit ETA at src/libs/NextStepUtils.ts:212-215:

let etaDateTime = setDate(new Date(), policy?.autoReportingOffset);
if (isPast(etaDateTime)) {
    etaDateTime = addMonths(etaDateTime, 1);
}

setDate keeps the current time of day, so on the offset day etaDateTime is now. Whether isPast(etaDateTime) returns true then comes down to how many microseconds elapse before it reads the clock — so the result flips between 2026-09-02 and 2026-10-02 run to run. The test compares against DateUtils.getNextNthOfMonth, which uses day < nth and so always rolls to next month on the 2nd, so it expects 2026-10-02 and fails whenever the race lands the other way.

I confirmed this by running that single test twice back to back on the current head — one pass, one fail, same commit, same minute. It should stop failing on its own tomorrow.

This is a real (if cosmetic) bug in NextStepUtils, but it is in a different subsystem from this PR, which touches only src/libs/ReportUtils.ts and tests/unit/ReportUtilsTest.ts. DateUtils.getNextNthOfMonth has no callers in src/ at all — it exists only for this assertion. Say the word and I'll open a separate issue or PR for it.

Full check status and local verification

Fixed earlier in 2101796 — the ternary at src/libs/ReportUtils.ts:9892 fits inside the 190-column printWidth in .oxfmtrc.json, so oxfmt collapses it to one line. Oxfmt check now passes.

Verified on the current head 2a9d573:

Check Result
npm run fmt ✅ no diff
npm run typecheck ✅ passed
npm run lint -- src/libs/ReportUtils.ts ✅ passed
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1309 passed
npm test -- tests/unit/NextStepUtilsTest.ts -t "monthly on the 2nd" ⚠️ passes and fails nondeterministically

npm run lint-changed could not run — this checkout has no merge base with origin/main. CI's ESLint check job covers it and passes.

The other two red checks are human gates, not code failures: Check independent approval and checklist (PR Author Checklist — the Tests and QA Steps sections are still TODO placeholders for the human co-author).


view run · no recording available

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Adversarial review

The companyCardRequired fix itself is correct and correctly gated. The problem is the second commit: this diff silently changes RBR behaviour for at least five other violations, on both open and submitted reports, and none of that is in the title, the description, or the tests.

If you land only the name exclusion, this is a clean one-line fix. The shouldShowViolation pre-filter is what makes it risky.


1. 🔴 shouldShowViolation now gates the violation and warning buckets, not just notice

The new filter at src/libs/ReportUtils.ts:9939-9947 applies shouldShowViolation to every violation before the type checks. Previously only hasNoticeTypeViolation applied it — I confirmed hasViolation and hasWarningTypeViolation do not.

This path only ever runs for the submitter, and for a submitter shouldShowViolation returns false for these. Typed violation or warning, each one used to light the LHN dot and now will not:

Violation Why it now returns false Scope
receiptNotSmartScanned isPolicyMember && !isSubmitter && !isReportOpenalways false for the submitter open and submitted
missingAttendees isAttendeeTrackingEnabledForPolicy(policy) is false on every non-Control policy, and when policy hasn't loaded yet open and submitted
futureDate re-derived from the local date, so a stale back-end violation is dropped once the date passes open and submitted
overAutoApprovalLimit needs isPolicyAdmin && (!isSubmitter || isReportManager) open and submitted
missingCategory during the 60s analyze window or pendingAction === ADD the only one tested

Note the scope column: excludedViolationNamesForLHN is empty on open reports, but the shouldShowViolation filter still runs there. So this reaches reports the fix explicitly says it doesn't touch. The missingAttendees case is the sharpest — that's a hard violation dropped for every Collect/Team workspace.

Your inline comment argues the filter is needed because hasVisibleViolationsForUser reads the raw list. That's true, but it justifies filtering the leftovers after a name exclusion — it doesn't justify applying it on open reports where nothing was excluded.

Ask: either scope the shouldShowViolation filter to excludedViolationNames.length > 0, or split it into its own PR with tests for the five names above.

2. 🔴 The modifiedAmount exclusion silently widened from notice-only to all three types

Old code passed excludedNoticeNamesForLHN only into the notice branch. Line 9893 now applies it to all three. modifiedAmount is known to arrive as violation as well as notice, so on a processing report a violation-typed modifiedAmount stops driving the RBR where it previously did. Not in the title, not in the description, no test.

3. 🟡 The stated criterion doesn't match the precedent it cites

The description says companyCardRequired qualifies because it "maps to no editable field (useViolations.ts maps it to none)... the same mechanism already used for modifiedAmount." But useViolations.ts:39 maps modifiedAmount to 'amount' — an editable field. The real rule behind the precedent is "not actionable once submitted", not "maps to none".

That matters because it leaves "is the two-name list complete?" unanswered. The none mapping isn't the discriminator: the only other none is hold, which is resolvable. Please state the actual rule.

4. 🟡 The central safety claim and two of the three named surfaces are untested

  • "Keeps the violation message visible to everyone on the expense" — the whole justification for scoping this to the RBR — has no test. It exists only as a code comment. This is the one I'd block on.
  • The title names three surfaces. All five new assertions call getViolatingReportIDForRBRInLHN directly; nothing covers the Fix badge or the Inbox To-do. That's not cosmetic — the To-do path reaches the helper via hasErrorsOtherThanFailedReceipt and bypasses the willShowGreenSubmit and isChildOfAccessiblePolicyExpenseChat guards that gate the dot and badge.
  • Minor framing: the red dot and the Fix badge aren't two fixes. ErrorBadge.tsx:34-45 renders one or the other from the same brickRoadIndicator === ERROR.

5. 🟡 "The plural wrappers are unchanged, since they have many other callers" is only half true

True for hasViolations (~33 call sites). False for hasWarningTypeViolations — it has exactly one caller left, the private sibling hasAnyViolations at ReportUtils.ts:10078, and it isn't exported. Verified by grep across src/ and tests/. Not a defect, but the description shouldn't lump them together.

Lower-severity findings

6. hasVisibleViolationsForUser may now be a dead AND-term. The new helper applies the same shouldShowViolation(...) with the same arguments, and the singular helpers apply the same isViolationDismissed. So hasViolationOfAnyTypeForRBRInLHN === true implies hasVisibleViolationsForUser === true, and the gate at L9897-L9904 can no longer change the outcome. Your comment at L9944-L9945 justifies the filter using a condition the filter itself made unreachable.

7. The last test depends on the wall clock. ReportUtilsTest.ts:17499 builds pendingAutoCategorizationTime from new Date() to land inside a 60s window. Timers are real here — jest/setupAfterEnv.ts:11 calls jest.useRealTimers() for every suite. TransactionUtilsTest.ts:2350 already uses the jest.setSystemTime idiom. Practical flake risk is low (the 5s per-test timeout fires long before 60s elapses), but the same test also asserts a negative with no positive control on the same fixture — if the fixture silently broke, it would still pass.

8. isProcessingReport(potentialReport) at L9893 is exactly !isOpenReport(potentialReport) given the isOpenOrProcessingReport guard 13 lines above at L9879-L9882. The ternary advertises a lifecycle discrimination it can't exercise, and the comment reads as if it handles post-approval states that can never reach it. (I checked: the fix is not incomplete for APPROVED/REIMBURSED — those return false at the guard.)

9. Double work. L9946 evaluates shouldShowViolation for every violation, then hasNoticeTypeViolation re-evaluates it internally on the already-filtered list. The old excludedViolationNames.length > 0 ? filter : raw short-circuit was also dropped, so the filter now always runs on the LHN hot path.

Things I checked and cleared: argument order and positional correctness of all the new call sites (the true at position 6 of shouldShowViolation is shouldShowRterForSettledReport, which is also its default — not showInReview); the ViolationName typing (sound, though the old string[] also type-checked, so this is a strictness upgrade, not a fix); hasNoticeTypeViolationsForRBRInLHN is fully removed with nothing exported-but-unused; the submitter gating is real and doubly enforced (the guard at L9862-L9865 and the owner filter on the report cache at L1152), so no admin/approver leak is possible; and companyCardRequired genuinely has no Mark as cash and no dismiss affordance anywhere.

Not fixed, worth stating in the description: Search still renders a red dot and the violation text on the report row post-submit — shouldShowViolationDescription is explicitly isOpenExpenseReport || isProcessingReport. You call this intentional, and I agree with the reasoning, but the reporter on #99793 will still see a red dot somewhere.


CI: test (job 3) is red, but it's unrelated — NextStepUtilsTest.ts:314, case "monthly on the 2nd", failing on the 2nd of the month. A pre-existing date flake, and an argument for finding 7. Your new tests pass in CI (jobs 1, 2, 4–8 green; job 3's only failure is that one).

Basis: static analysis of the working tree at 2a9d573 plus CI logs. I could not execute jest or typecheck locally in this environment, so I did not independently reproduce the five suppressed-violation cases at runtime — they are read off shouldShowViolation's branches, not observed.


view run · view recording

@Eskalifer1

Copy link
Copy Markdown
Contributor

@MelvinBot Take a look at this review. What should we fix?

I'm not sure about the first one, but we don't want to change behaviour for others violations. In other words, we need to leave the logic for all violations as is and change it only for companyCardRequired

Drop the shouldShowViolation pre-filter and keep modifiedAmount notice-only,
so companyCardRequired is the only violation whose behaviour changes.

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

You were right to push back, and your rule resolves finding 1 for us. I've pushed bb26bc5.

Two things needed fixing, and your rule fixes both with one change: drop the shouldShowViolation pre-filter and keep modifiedAmount notice-only. companyCardRequired is now the only violation whose behaviour changes.

On the first finding you weren't sure about — the mechanism is real, but the pre-filter was never needed for companyCardRequired anyway. shouldShowViolation has no branch for companyCardRequired, so it falls through to return true and the filter was a no-op for the violation we're actually fixing. It only ever affected other violations. So removing it costs nothing and is exactly what you asked for.

The reviewer also overstated its blast radius, which is worth recording so nobody re-adds it thinking it fixed five things:

Name Reviewer's claim What the code says
receiptNotSmartScanned suppressed, open + submitted Correct — !isSubmitter short-circuits
overAutoApprovalLimit suppressed, open + submitted Overstated — already false for everyone on open (isProcessingReport), and true for admin self-approvers on submitted
missingAttendees, futureDate, missingCategory suppressed, open + submitted Not submitter- or state-dependent at all. Each re-checks its own rule, mirroring the client's own creation gate

Finding 2 was the real defect — and worse than "untested". modifiedAmount genuinely ships as type violation: TransactionPreviewUtils.ts:505 exists specifically to handle it. So on 2a9d573, a submitted report whose only violation was a violation-typed modifiedAmount lost its LHN dot while transactionHasRBR still returned true in the report body — LHN and report disagreeing. The two new positive-control tests fail on 2a9d573 and pass now.

What changed in bb26bc5

src/libs/ReportUtils.ts:

  • Removed the shouldShowViolation pre-filter and its import. The three type checks see the same list they always did.
  • Split one exclusion list into two: excludedViolationNamesForLHN = [COMPANY_CARD_REQUIRED] across all three buckets (the back end owns the type, so the exclusion has to hold in any bucket), and excludedNoticeNamesForLHN = [MODIFIED_AMOUNT] passed to the notice check only, exactly as before.
  • Restored the length > 0 ? filter : raw short-circuit, so the LHN hot path does no work when nothing is excluded.

The single-pass consolidation stays — it's now purely a refactor of the old three-branch OR with no behaviour change beyond the one name.

tests/unit/ReportUtilsTest.ts:

  • Added it.each([violation, warning]) asserting modifiedAmount still drives the RBR on a submitted report. These are the regression guards for finding 2.
  • Added a test that hasVisibleViolationsForUser still returns true for companyCardRequired post-submit — the "message stays visible" claim the reviewer said they'd block on. It was only a code comment before.
  • Removed the wall-clock missingCategory-during-analysis test. It existed only to justify the pre-filter, and it was finding 7 (built pendingAutoCategorizationTime from new Date() under real timers).

Findings 6, 7 and 9 all dissolve with the pre-filter: hasVisibleViolationsForUser is a meaningful AND-term again, the clock-dependent test is gone, and the double shouldShowViolation evaluation is gone.

Checks on bb26bc5: ReportUtilsTest.ts 1311 passed (8 in the companyCardRequired block), SidebarUtilsTest + SidebarFilterTest + SidebarOrderTest + TransactionUtilsTest 552 passed, npm run typecheck passed, ESLint on both files 0 errors. lint-changed/spell-changed couldn't run — this checkout is shallow so git merge-base fails — so I ran ESLint on the two files directly instead.

Description fixes I did not make, since they're yours to word:

  • Finding 3 is correct: useViolations.ts:39 maps modifiedAmount to 'amount', not none. The description's stated criterion is wrong. The real rule is "not actionable once submitted", and companyCardRequired now stands alone on it rather than sharing a list with modifiedAmount.
  • Finding 5 is correct: hasWarningTypeViolations has exactly one caller, not "many".

One thing I could not settle: which type bucket the back end actually stamps on companyCardRequired. App never constructs it — every client-side violation in ViolationsUtils hardcodes a type and none is this one — so the all-buckets exclusion is defensive rather than proven necessary. It's harmless either way, but if you want certainty it needs the back-end definition, not App source.


view run · no recording available

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Same two checks as before. Fixed the Oxfmt one; the test failure is still the unrelated date flake.

Fixed — Oxfmt check (pushed as 5e3b071)

The two commits added after my last fix (2a9d573, bb26bc5) reintroduced a formatting diff, this time in tests/unit/ReportUtilsTest.ts:17511. The expect(...) call was wrapped across lines, but oxfmt breaks the .toBe(true) argument instead. Whitespace only.

Not fixed — test (job 3) / NextStepUtilsTest

Still monthly on the 2nd, still failing only because today is the 2nd of the month. The test derives its own expectation from DateUtils.getNextNthOfMonth(2), which uses day < nth — on the 2nd that is 2 < 2, false, so it rolls to 2026-10-02 while buildOptimisticNextStep returns 2026-09-02. Nothing in this PR touches that path. It will go green on its own tomorrow.

Local verification on 5e3b071
Check Result
npm run fmt (re-run, idempotent) ✅ no diff
npm run typecheck ✅ passed
npm run lint -- src/libs/ReportUtils.ts tests/unit/ReportUtilsTest.ts ✅ passed
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1311 passed

npm run lint-changed still can't run — this checkout has no merge base with origin/main. CI's ESLint check job covers it and was green on the previous commit.

Check independent approval is also red; that's a review-state gate, not a code failure.


view run · view recording

@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

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Nothing to push this time — the one real failure is already tracked elsewhere.

Oxfmt check is green. The formatting commits fixed it.

test (job 3) — known flake, not this PR. libs/NextStepUtils › buildNextStep › … › monthly on the 2nd fails on the 2nd of every month. It's tracked in #100134, and the fix PR #100145 was closed without merging, so it's still open.

The test builds its expected value from DateUtils.getNextNthOfMonth, which uses day < nth — on the 2nd that's 2 < 2, false, so it rolls to 2026-10-02. Production's buildOptimisticNextStep returns 2026-09-02. This PR touches only ReportUtils.ts and ReportUtilsTest.ts, so it can't reach that assertion.

Two ways forward: land a fix on #100134 first, or re-run test (job 3) after midnight UTC and it will pass.

Check independent approval is also red — that's a review-state gate, not a code failure.


view run · view recording

@Eskalifer1

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e3b0716b7

ℹ️ 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
Comment on lines +9953 to +9954
hasViolation(transaction, violations, currentUserEmailParam, currentUserAccountIDParam, report, currentUserEmailParam, policy, true) ||
hasWarningTypeViolation(transaction, violations, currentUserEmailParam, currentUserAccountIDParam, report, currentUserEmailParam, policy, true) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter hidden hard and warning violations before type checks

When a submitted expense contains a visible companyCardRequired violation alongside missingCategory while auto-categorization is pending, the outer hasVisibleViolationsForUser() check succeeds because the company-card violation is visible. After that violation is excluded, hasViolation() and hasWarningTypeViolation() still accept the hidden violation because neither applies shouldShowViolation(), so the LHN RBR, Fix badge, and To-do remain despite there being no actionable issue. Fresh evidence since the existing thread is that follow-up commit bb26bc5 removed the shouldShowViolation() prefilter from the final tree; retain per-violation visibility filtering while preserving the intended notice-only treatment of modifiedAmount.

Useful? React with 👍 / 👎.

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 Take a look at this comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex is right on the facts, and I've fixed it in 2f927ce — though not by restoring what bb26bc5 removed.

The finding checks out. bb26bc5 did drop the shouldShowViolation prefilter that 2a9d573 added, and the hole came back with it. On a submitted report carrying a visible companyCardRequired next to a hidden missingCategory, hasVisibleViolationsForUser passed on the raw list while the type checks read the name-filtered one, so the excluded violation vouched for the hidden one and the dot stayed lit.

But restoring the prefilter is the wrong fix, which is why it was removed. It applied shouldShowViolation to the violation and warning buckets, which previously only the notice check gated — and it ran on open reports too, where the exclusion list is empty. That silently dropped receiptNotSmartScanned, missingAttendees, futureDate and overAutoApprovalLimit from the RBR on reports this PR is not supposed to touch. Putting it back would trade one narrow bug for four broad ones.

What I did instead: pass the same exclusion list to the visibility check, so both operands of the && judge the same set.

App/src/libs/ReportUtils.ts

Lines 9900 to 9913 in 2f927ce

ViolationsUtils.hasVisibleViolationsForUser(
potentialReport,
transactionViolations,
currentUserLogin,
deprecatedCurrentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
policy,
transactions,
// Both operands of this `&&` have to judge the same set of violations. Without this the visibility
// check still sees `companyCardRequired`, so a violation this function has deliberately stopped
// acting on could vouch for one that is hidden from the submitter — `missingCategory` while
// auto-categorization is still running, for example — and the red dot would stay lit with nothing
// behind it. On open reports the list is empty, so nothing changes there.
excludedViolationNamesForLHN,
) &&

This closes the vouching hole with no collateral scope. Nothing about which violation types drive the dot changes, so the four violations above are untouched. On open reports excludedViolationNamesForLHN is empty, making the new argument a literal no-op — behaviour there is identical to main. On submitted reports the only delta is companyCardRequired itself, which is the entire point of the PR. Both operands can now only shrink relative to main, so this can remove dots but never add one.

One correction to Codex's framing, unchanged from the earlier thread: this is not a regression. main lights the same dot on the same report, just because companyCardRequired itself satisfied the unfiltered hasViolations. It's a pre-existing gap this PR would have inherited, not one it introduced.

Reachability, test, and verification

How you'd hit it. shouldShowViolation hides missingCategory while isCategoryBeingAnalyzed is true — pendingAction === ADD, or inside a 60-second pendingAutoCategorizationTime window. Both violations are backend-generated, so pendingAction === ADD is largely mutually exclusive with having them. The realistic path is instant-submit: the report is born SUBMITTED, the backend returns both violations, and for up to a minute the category chip reads "analyzing" while the dot stays on. Narrow and transient — but the fix is name-generic, so it covers any hidden violation, not just this pair.

Test. Re-added should not surface RBR when the violation left next to companyCardRequired is hidden while the category is being analyzed (it was dropped in bb26bc5 along with the prefilter). I confirmed it actually pins the behaviour: with the new argument removed it is the only failure in the block —

● should not surface RBR when the violation left next to companyCardRequired is hidden while the category is being analyzed
    expect(received).toBeNull()
    Received: "expense-rbr-company-card-alongside-hidden"
Tests: 1 failed, 8 passed

— and with it restored all 9 pass. Notably the "should still surface RBR on an open expense report" test passes in both configurations, which is the open-report no-op claim above holding empirically.

Checks on 2f927ce:

Check Result
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1312 passed
npm test -- tests/unit/ViolationUtilsTest.ts ✅ 186 passed
npm run typecheck ✅ passed
npm run lint -- src/libs/ReportUtils.ts src/libs/Violations/ViolationsUtils.ts tests/unit/ReportUtilsTest.ts ✅ passed
npm run fmt ✅ no diff

hasVisibleViolationsForUser gained a defaulted trailing parameter, so the change is backwards-compatible: ReportUtils.ts is its only production caller, and all existing ViolationUtilsTest call sites are unchanged.


view run

Both operands of the && in getViolatingReportIDForRBRInLHN have to judge the
same set of violations. Passing the exclusion list only to the type checks let
a visible companyCardRequired vouch for a violation hidden from the submitter,
keeping the red dot lit with nothing behind it.

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@Eskalifer1

Copy link
Copy Markdown
Contributor

@MelvinBot update PR Explanation of changes section to reflect PR changes

@Eskalifer1

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
100190-android-native.mov
Android: mWeb Chrome
100190-android-web.mov
iOS: HybridApp
100190-ios-native.mov
iOS: mWeb Safari
100190-ios-web.mov
MacOS: Chrome / Safari
100190-web.mov

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Updated the description to match the diff as of fc02644. Three things in it had gone stale:

  • hasVisibleViolationsForUser is no longer "deliberately left alone." 2f927ce added an optional excludedViolationNames param to it, and the description still claimed the opposite. It now has its own numbered item explaining why both operands of the && have to judge the same set of violations.
  • The wrapper note was wrong. It said hasViolations / hasWarningTypeViolations are unchanged "since they have many other callers." hasWarningTypeViolations has exactly one caller (hasAnyViolations); hasViolations has three. Reworded to name them.
  • The AI Tests section was from the first commit. 1308 → 1311 passed, 5 new tests → 8, and the suites listed weren't the ones that actually cover this change. I re-ran everything on fc02644 and rewrote the table.

I also added the caveat that the back end's type bucket for companyCardRequired isn't knowable from App source, so the all-buckets exclusion is defensive.

The Tests, Offline tests, QA Steps and checklist sections are untouched — those are yours to fill in.

Checks re-run on fc02644
Check Result
npm test -- tests/unit/ReportUtilsTest.ts ✅ 1311 passed
npm test -- tests/unit/ViolationUtilsTest.ts tests/unit/SidebarUtilsTest.ts tests/unit/SidebarFilterTest.ts tests/unit/SidebarOrderTest.ts tests/unit/TransactionUtilsTest.ts ✅ 738 passed, 144 skipped
npm run typecheck ✅ passed
ESLint on the three changed files ✅ 0 errors
cspell on the three changed files ✅ 0 issues

The 144 skipped are all of SidebarFilterTest, which is xdescribed on main — not something this PR did. lint-changed and spell-changed still can't run here because this checkout is shallow and their git merge-base step fails, so I ran ESLint and cspell against the changed files directly. CI's own ESLint check, spellcheck, Oxfmt check, typecheck and all eight test jobs are green on this commit.


view run

@Eskalifer1

Copy link
Copy Markdown
Contributor
Details

Explanation of Change

After a report is submitted, a companyCardRequired violation still drove the submitter's LHN red dot, the Fix action badge and the Inbox To-do - but clicking Fix opened a report with nothing to act on. Unlike every other Rules > Expenses violation, companyCardRequired maps to no editable field (useViolations.ts maps it to none), has no Mark as cash and no dismiss, so only an admin turning the rule off can clear it.

This PR keeps the violation message visible to everyone on the expense, and stops it from driving the RBR / Fix / To-do for the submitter once the report is submitted.

src/libs/ReportUtils.ts

getViolatingReportIDForRBRInLHN builds a new excludedViolationNamesForLHN list — [COMPANY_CARD_REQUIRED] on processing reports, empty otherwise — and passes it to both operands of the && that decides the RBR. The pre-existing excludedNoticeNamesForLHN ([MODIFIED_AMOUNT]) is unchanged and still reaches the notice check only.
The exclusion is applied by violation name, before the type checks, so it holds no matter which bucket (violation / warning / notice) the back end assigns. To make that possible, hasNoticeTypeViolationsForRBRInLHN became the type-agnostic hasViolationOfAnyTypeForRBRInLHN: it takes both exclusion lists and folds the three previously-ORed calls into a single pass over the report transactions.
src/libs/Violations/ViolationsUtils.ts

hasVisibleViolationsForUser gains an optional excludedViolationNames parameter defaulting to [], so every existing caller is unaffected. Both operands of the && above have to judge the same set of violations — without this, the visibility check still saw companyCardRequired and let it vouch for a violation hidden from the submitter (missingCategory while auto-categorization is still running, for example), leaving the red dot lit with nothing behind it.

Fixed Issues

$#99793
PROPOSAL:#99793 (comment)

Tests

Preconditions

  • Create new accounts
  • Account A — workspace owner / admin, and the approver.
  • Account B — plain workspace member, and the submitter. B must not be an
  • admin of the workspace.
  • As A, the workspace must be on the Control plan.
  • As A: Workspace → More features → enable Company cards.
  • As A: Workspace → RulesExpenses → enable
  • Require company cards for all purchases.
  1. Sign in as B. Create a manual cash expense in the workspace (any amount,
  2. any merchant, any category)
  3. Open the expense and verify the message Company card purchases required
  4. is shown on it.
  5. Submit the report.
  6. Verify the workspace chat row in B's LHN doesn't show the red dot and the Fix badge.
  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests

  • 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 please set the PR body to the content in the details section above

@Eskalifer1

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: fc0264437b

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — the PR body is now the content from your details block, verbatim.

One thing to check before this merges: the Fixed Issues lines lost the space after the marker ($https://... and PROPOSAL:https://...). The template calls for $ <url> and PROPOSAL: <url>, and the previous body had it. If the issue-linking automation is space-sensitive, this won't link. I left it exactly as you wrote it — say the word and I'll re-add the two spaces.


view run

@MelvinBot
MelvinBot marked this pull request as ready for review September 3, 2026 11:37
@MelvinBot
MelvinBot requested a review from a team as a code owner September 3, 2026 11:37
@melvin-bot
melvin-bot Bot requested review from Eskalifer1 and removed request for a team September 3, 2026 11:37
@melvin-bot

melvin-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

@Eskalifer1 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 requested review from heyjennahay and removed request for a team September 3, 2026 11:37
Comment thread src/libs/ReportUtils.ts Outdated
const transactions = getReportTransactions(potentialReport.reportID).filter((transaction) => !isTransactionPendingDelete(transaction));

const excludedNoticeNamesForLHN = isProcessingReport(potentialReport) ? [CONST.VIOLATIONS.MODIFIED_AMOUNT] : [];
// A submitted `companyCardRequired` is not actionable by the submitter, so it must not drive the RBR; it is excluded by name because the back end owns its type.

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.

❌ CONSISTENCY-16 (docs)

Comments should read as plain, natural sentences. This comment uses a semicolon to join two independent clauses instead of writing two separate sentences, which the plain-comment-style guideline flags.

Split the two clauses into separate sentences:

// A submitted `companyCardRequired` is not actionable by the submitter, so it must not drive the RBR. It is excluded by name because the back end owns its type.

Reviewed at: fc02644 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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 Take a look at this asweel

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — that's a semicolon joining two independent clauses, which CONSISTENCY-16 flags. Fixed in 7141e8e, split into two sentences and wrapped to match the neighbouring comments: src/libs/ReportUtils.ts:9888-9889.

The same commit fixes the second CONSISTENCY-16 hit on the hasViolationOfAnyTypeForRBRInLHN JSDoc. Comment text only, no logic touched — npm run typecheck passed, ESLint on the file 0 errors, tests/unit/ReportUtilsTest.ts 1311 passed.


view run

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already handled — this landed in 7141e8e a few minutes ago (my reply just above), so this is only a confirmation that it's in and green.

The comment now reads as two sentences, exactly as suggested: src/libs/ReportUtils.ts:9888-9889. The sibling CONSISTENCY-16 hit on the hasViolationOfAnyTypeForRBRInLHN JSDoc went in the same commit: src/libs/ReportUtils.ts:9921.

Comment text only, no logic touched. On 7141e8e every check passes except Check independent approval, which is the human-approval gate, not a code failure.


view run

Comment thread src/libs/ReportUtils.ts Outdated
/**
* Whether any transaction on the report carries a violation that should drive the LHN RBR, checking all three violation
* types. Names in `excludedViolationNames` are dropped from every type check, so the exclusion holds regardless of which
* bucket the back end assigns the violation to; names in `excludedNoticeNames` are dropped from the notice check only.

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.

❌ CONSISTENCY-16 (docs)

This JSDoc line uses a semicolon to join two independent clauses ("...so the exclusion holds regardless of which bucket the back end assigns the violation to; names in excludedNoticeNames are dropped from the notice check only."). Per the plain-comment-style guideline, use two separate sentences instead of a semicolon.

Rewrite as two sentences:

 * bucket the back end assigns the violation to. Names in `excludedNoticeNames` are dropped from the notice check only.

Reviewed at: fc02644 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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 take a look at this review comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — same CONSISTENCY-16 issue, a semicolon standing in for a sentence break. Fixed in 7141e8e exactly as suggested: src/libs/ReportUtils.ts:9921.

Comment text only, no logic touched — npm run typecheck passed, ESLint on the file 0 errors, tests/unit/ReportUtilsTest.ts 1311 passed.


view run

CONSISTENCY-16 flagged both the inline comment above excludedViolationNamesForLHN
and the hasViolationOfAnyTypeForRBRInLHN JSDoc for joining two independent
clauses with a semicolon. Comment text only.

Co-authored-by: Eskalifer1 <Eskalifer1@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team as a code owner September 3, 2026 11:47
@melvin-bot
melvin-bot Bot requested review from chuckdries and removed request for a team September 3, 2026 11:47
@melvin-bot

melvin-bot Bot commented Sep 3, 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]

@Eskalifer1

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 7141e8e1f4

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

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

LGTM!

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

LGTM

@chuckdries
chuckdries merged commit c53cb91 into main Sep 3, 2026
47 of 51 checks passed
@chuckdries
chuckdries deleted the claude-companyCardRequiredRbrAfterSubmit branch September 3, 2026 20:50
@OSBotify

OSBotify commented Sep 3, 2026

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

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🚧 chuckdries has triggered a test Expensify/App build. You can view the workflow run here.

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.

4 participants