diff --git a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx
index 8223449df6b5..ada56a885554 100644
--- a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx
+++ b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx
@@ -1,4 +1,5 @@
 import {isTrackIntentUserSelector} from '@selectors/Onboarding';
+import {transactionViolationsByIDsSelector} from '@selectors/TransactionViolations';
 import React, {useCallback, useMemo} from 'react';
 import {View} from 'react-native';
 // We need direct access to useOnyx to fetch live policy data at render time
@@ -31,6 +32,7 @@ import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
 import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import {isAttendeeTrackingEnabled} from '@libs/PolicyUtils';
 import {getNonHeldAndFullAmount, isInvoiceReport, isOpenExpenseReport, isProcessingReport, isReportPendingDelete, shouldShowMarkAsDone} from '@libs/ReportUtils';
+import {hasVisibleViolations} from '@libs/SearchUIUtils';
 import {isOnHold, isViolationDismissed, shouldShowViolation, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
@@ -179,8 +181,27 @@ function ExpenseReportListItem<TItem extends ListItem>({
     const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
     const {showConfirmModal} = useConfirmModal();
     const {showHoldMenu} = useHoldMenuModal();
-    const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportItem.reportID);
+    const {transactions: reportTransactions, violations: reportViolations} = useTransactionsAndViolationsForReport(reportItem.reportID);
     const liveReportTransactions = useMemo(() => Object.values(reportTransactions), [reportTransactions]);
+
+    // Recompute the violations badge from live data at the row, replacing the screen-level
+    // violations merge that getSections previously did. Policy comes from the live `policyForViolations`
+    // (parentPolicy ?? snapshot); violations + transactions come from the report's live Onyx data.
+    const liveHasVisibleViolations = hasVisibleViolations(
+        reportForViolations,
+        reportViolations,
+        currentUserDetails.email ?? '',
+        currentUserDetails.accountID,
+        liveReportTransactions,
+        policyForViolations,
+    );
+
+    // Scoped live violations for the report's snapshot transactions: before the report's transactions
+    // hydrate into the live collection, rule/category changes still push violation updates that must
+    // reflect on the badge (per-row selector, not the screen-level collection merge this slice removed).
+    const snapshotTransactionIDs = (reportItem.transactions ?? []).map((transaction) => transaction.transactionID);
+    const liveViolationsSelector = transactionViolationsByIDsSelector(snapshotTransactionIDs);
+    const [liveViolationsForSnapshotTransactions] = originalUseOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: liveViolationsSelector}, [liveViolationsSelector]);
     const {currentUserAccountID, currentUserLogin, introSelected, betas, isSelfTourViewed, activePolicy, nextStep, chatReportPolicy, amountOwed} = useReportPaymentContext({
         reportID: reportItem.reportID,
         chatReportPolicyID: chatReport?.policyID,
@@ -308,11 +329,25 @@ function ExpenseReportListItem<TItem extends ListItem>({
     const shouldShowViolationDescription = isOpenExpenseReport(reportItem) || isProcessingReport(reportItem);
 
     // Show violation description if either:
-    // 1. Pre-computed hasVisibleViolations from search data, OR
+    // 1. Visible violations recomputed from the report's live transactions, or, before those hydrate,
+    //    from the snapshot transactions joined with the scoped live violations (so a rule change updates
+    //    the badge even for reports never opened this session); the snapshot's pre-computed value only
+    //    covers the window before the violations subscription loads, OR
     // 2. Synced missingAttendees violation computed at render time (for stale data)
     // We're using || instead of ?? because the variables are boolean
-    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-    const hasAnyVisibleViolations = reportItem?.hasVisibleViolations || hasSyncedMissingAttendeesViolation;
+    const hasLiveTransactions = liveReportTransactions.length > 0;
+    const fallbackHasVisibleViolations = liveViolationsForSnapshotTransactions
+        ? hasVisibleViolations(
+              reportForViolations,
+              liveViolationsForSnapshotTransactions,
+              currentUserDetails.email ?? '',
+              currentUserDetails.accountID,
+              reportItem.transactions ?? [],
+              policyForViolations,
+          )
+        : !!reportItem.hasVisibleViolations;
+    const hasVisibleReportViolations = hasLiveTransactions ? liveHasVisibleViolations : fallbackHasVisibleViolations;
+    const hasAnyVisibleViolations = hasVisibleReportViolations || hasSyncedMissingAttendeesViolation;
 
     const getDescription = useMemo(() => {
         if (reportItem?.isRejectedReport) {
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index b974e2ffb743..89af401aac84 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -341,8 +341,6 @@ function Search({
     const previousTransactions = usePrevious(transactions);
     const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
     const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID);
-    const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
-    const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
     const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
     const {accountID, email, login} = useCurrentUserPersonalDetails();
     const selfDMReport = useSelfDMReport();
@@ -591,7 +589,6 @@ function Search({
         const [filteredData1, allLength, hasDeletedTransactionFromSections] = getSections({
             type,
             data: searchDataWithOptimisticTransaction,
-            policies,
             currentAccountID: accountID,
             currentUserEmail: email ?? '',
             translate,
@@ -607,7 +604,6 @@ function Search({
             cardList: personalAndWorkspaceCards,
             nonPersonalAndWorkspaceCardList: nonPersonalAndWorkspaceCards,
             isOffline,
-            allTransactionViolations: violations,
             customCardNames,
             conciergeReportID,
             onyxPersonalDetailsList,
@@ -642,9 +638,7 @@ function Search({
         cardFeeds,
         personalAndWorkspaceCards,
         nonPersonalAndWorkspaceCards,
-        policies,
         bankAccountList,
-        violations,
         customCardNames,
         conciergeReportID,
         onyxPersonalDetailsList,
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index dd6f8082280e..a3d7e9469693 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -236,7 +236,6 @@ type TransactionQuarterGroupSorting = ColumnSortMapping<TransactionQuarterGroupL
 
 type GetReportSectionsParams = {
     data: OnyxTypes.SearchResults['data'];
-    policies: OnyxCollection<OnyxTypes.Policy>;
     currentSearch: SearchKey;
     currentAccountID: number;
     currentUserEmail: string;
@@ -245,7 +244,6 @@ type GetReportSectionsParams = {
     convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString'];
     isActionLoadingSet: ReadonlySet<string> | undefined;
     isOffline: boolean | undefined;
-    allTransactionViolations: OnyxCollection<OnyxTypes.TransactionViolation[]>;
     bankAccountList: OnyxEntry<OnyxTypes.BankAccountList>;
     reportActions?: Record<string, OnyxTypes.ReportAction[]>;
     queryJSON?: SearchQueryJSON;
@@ -587,7 +585,6 @@ type GetSectionsResult = [
 type GetSectionsParams = {
     type: SearchDataTypes;
     data: OnyxTypes.SearchResults['data'];
-    policies?: OnyxCollection<OnyxTypes.Policy>;
     currentAccountID: number;
     currentUserEmail: string;
     translate: LocalizedTranslate;
@@ -605,7 +602,6 @@ type GetSectionsParams = {
     cardList?: OnyxEntry<OnyxTypes.CardList>;
     nonPersonalAndWorkspaceCardList?: OnyxEntry<OnyxTypes.CardList>;
     customCardNames?: Record<number, string>;
-    allTransactionViolations?: OnyxCollection<OnyxTypes.TransactionViolation[]>;
     visibleReportActionsData?: OnyxTypes.VisibleReportActionsDerivedValue;
     conciergeReportID: string | undefined;
     onyxPersonalDetailsList?: OnyxTypes.PersonalDetailsList;
@@ -2736,7 +2732,6 @@ function getReportActionsSections(
  */
 function getReportSections({
     data,
-    policies,
     currentSearch,
     currentAccountID,
     currentUserEmail,
@@ -2744,7 +2739,6 @@ function getReportSections({
     isOffline,
     formatPhoneNumber,
     isActionLoadingSet,
-    allTransactionViolations,
     bankAccountList,
     reportActions = {},
     queryJSON,
@@ -2827,14 +2821,13 @@ function getReportSections({
                 const formattedTo = !shouldShowBlankTo ? formatPhoneNumber(getDisplayNameOrDefault(toDetails)) : '';
 
                 const formattedStatus = getReportStatusTranslation({stateNum: reportItem.stateNum, statusNum: reportItem.statusNum, translate});
-                const policyFromKey = getPolicyFromKey(data, reportItem);
-                const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem?.policyID ?? String(CONST.DEFAULT_NUMBER_ID)}`] ?? policyFromKey;
+                const policy = getPolicyFromKey(data, reportItem);
 
                 const shouldShowStatusAsPending = !!isOffline && reportItem?.pendingFields?.nextStep === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;
 
                 const hasVisibleViolationsForReport = hasVisibleViolations(
                     reportItem,
-                    allTransactionViolations ?? allViolations,
+                    allViolations,
                     currentUserEmail,
                     currentAccountID ?? CONST.DEFAULT_NUMBER_ID,
                     allReportTransactions,
@@ -3582,7 +3575,6 @@ function getListItem(type: SearchDataTypes, status: SearchStatus, groupBy?: Sear
 function getSections({
     type,
     data,
-    policies,
     currentAccountID,
     currentUserEmail,
     translate,
@@ -3599,7 +3591,6 @@ function getSections({
     cardList,
     nonPersonalAndWorkspaceCardList,
     customCardNames,
-    allTransactionViolations,
     visibleReportActionsData,
     conciergeReportID,
     onyxPersonalDetailsList,
@@ -3618,7 +3609,6 @@ function getSections({
     if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
         return getReportSections({
             data,
-            policies,
             currentSearch,
             currentAccountID,
             currentUserEmail,
@@ -3626,7 +3616,6 @@ function getSections({
             isOffline,
             formatPhoneNumber,
             isActionLoadingSet,
-            allTransactionViolations,
             bankAccountList,
             reportActions,
             queryJSON,
@@ -6284,6 +6273,7 @@ export {
     shouldShowDeleteOption,
     getToFieldValueForTransaction,
     getSearchReportAvatarProps,
+    hasVisibleViolations,
     isTodoSearch,
     getActiveGroupSearchHashes,
     getSelectedGroupFilterEntry,
