diff --git a/src/hooks/useCurrentReportID.tsx b/src/hooks/useCurrentReportID.tsx
index c6574ac4658e..bbdaf3f48da8 100644
--- a/src/hooks/useCurrentReportID.tsx
+++ b/src/hooks/useCurrentReportID.tsx
@@ -1,9 +1,11 @@
-import type {NavigationState} from '@react-navigation/native';
+import type {NavigationState, PartialState} from '@react-navigation/native';
 import React, {createContext, startTransition, useCallback, useContext, useMemo, useRef, useState} from 'react';
 import Navigation from '@libs/Navigation/Navigation';
+import NAVIGATORS from '@src/NAVIGATORS';
 
 type CurrentReportIDStateContextType = {
     currentReportID: string | undefined;
+    currentRHPReportID?: string | undefined;
 };
 
 type CurrentReportIDActionsContextType = {
@@ -20,28 +22,46 @@ type CurrentReportIDContextProviderProps = {
     onSetCurrentReportID?: (reportID: string | undefined) => void;
 };
 
+/**
+ * Traverse the focused route at each level of the navigation state to find a reportID param.
+ * This handles modal navigators (e.g. RightModalNavigator > ExpenseReport) that carry a reportID
+ * in their screen params but are not part of the ReportsSplitNavigator hierarchy.
+ */
+function getFocusedRouteReportID(state: NavigationState | PartialState<NavigationState>): string | undefined {
+    const index = state.index ?? state.routes.length - 1;
+    const focusedRoute = state.routes[index];
+    if (!focusedRoute) {
+        return;
+    }
+    if (focusedRoute.params && 'reportID' in focusedRoute.params && typeof focusedRoute.params.reportID === 'string') {
+        return focusedRoute.params.reportID;
+    }
+    if (focusedRoute.state) {
+        return getFocusedRouteReportID(focusedRoute.state);
+    }
+}
+
 const defaultCurrentReportIDActionsContext: CurrentReportIDActionsContextType = {
     updateCurrentReportID: () => {},
 };
 
-const CurrentReportIDStateContext = createContext<CurrentReportIDStateContextType>({currentReportID: undefined});
+const CurrentReportIDStateContext = createContext<CurrentReportIDStateContextType>({currentReportID: undefined, currentRHPReportID: undefined});
 
 const CurrentReportIDActionsContext = createContext<CurrentReportIDActionsContextType>(defaultCurrentReportIDActionsContext);
 
 function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderProps) {
     const [currentReportID, setCurrentReportID] = useState<string | undefined>('');
+    const [currentRHPReportID, setCurrentRHPReportID] = useState<string | undefined>(undefined);
     // Tracks the most recently requested reportID synchronously so the dedupe
     // check below stays accurate even while a startTransition is pending.
     const pendingReportIDRef = useRef<string | undefined>('');
 
     /**
-     * This function is used to update the currentReportID
+     * This function is used to update the currentReportID and currentRHPReportID
      * @param state root navigation state
      */
     const updateCurrentReportID = useCallback(
         (state: NavigationState) => {
-            const reportID = Navigation.getTopmostReportId(state);
-
             /*
              * Make sure we don't make the reportID undefined when switching between the chat list and settings tab.
              * This helps prevent unnecessary re-renders.
@@ -50,25 +70,31 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro
             if (params && 'screen' in params && typeof params.screen === 'string' && params.screen.indexOf('Settings_') !== -1) {
                 return;
             }
-            if (pendingReportIDRef.current === reportID) {
-                return;
-            }
 
-            if (!pendingReportIDRef.current && !reportID) {
-                return;
+            const reportID = Navigation.getTopmostReportId(state);
+
+            if (pendingReportIDRef.current !== reportID) {
+                if (pendingReportIDRef.current || reportID) {
+                    pendingReportIDRef.current = reportID;
+                    props.onSetCurrentReportID?.(reportID);
+                    // Mark the report ID update as a non-urgent transition so React can keep the
+                    // UI responsive to user input while the (potentially expensive) report screen
+                    // re-render is processed in the background.
+                    startTransition(() => {
+                        setCurrentReportID(reportID);
+                    });
+                }
             }
 
-            pendingReportIDRef.current = reportID;
-            props.onSetCurrentReportID?.(reportID);
-            // Mark the report ID update as a non-urgent transition so React can keep the
-            // UI responsive to user input while the (potentially expensive) report screen
-            // re-render is processed in the background.
-            startTransition(() => {
-                setCurrentReportID(reportID);
-            });
+            const focusedTopRoute = state.routes[state.index];
+            const modalReportID = focusedTopRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && focusedTopRoute.state ? getFocusedRouteReportID(focusedTopRoute.state) : undefined;
+
+            if (currentRHPReportID !== modalReportID && (currentRHPReportID || modalReportID)) {
+                setCurrentRHPReportID(modalReportID);
+            }
         },
         // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to re-render when onSetCurrentReportID changes
-        [setCurrentReportID],
+        [setCurrentReportID, setCurrentRHPReportID, currentRHPReportID],
     );
 
     const actionsContextValue = useMemo<CurrentReportIDActionsContextType>(
@@ -81,8 +107,9 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro
     const stateContextValue = useMemo<CurrentReportIDStateContextType>(
         () => ({
             currentReportID,
+            currentRHPReportID,
         }),
-        [currentReportID],
+        [currentReportID, currentRHPReportID],
     );
 
     return (
diff --git a/src/libs/API/parameters/AddCommentOrAttachmentParams.ts b/src/libs/API/parameters/AddCommentOrAttachmentParams.ts
index c4f973e8ac96..ead10653119a 100644
--- a/src/libs/API/parameters/AddCommentOrAttachmentParams.ts
+++ b/src/libs/API/parameters/AddCommentOrAttachmentParams.ts
@@ -14,6 +14,7 @@ type AddCommentOrAttachmentParams = {
     pageHTML?: string;
     optimisticConciergeReportActionID?: string;
     pregeneratedResponse?: string;
+    sidePanelContext?: string;
 };
 
 export default AddCommentOrAttachmentParams;
diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts
index 1a01a288d3fc..0712a27a8a4f 100644
--- a/src/libs/actions/Report/index.ts
+++ b/src/libs/actions/Report/index.ts
@@ -219,6 +219,7 @@ import type {
     ReportAttributesDerivedValue,
     ReportNextStepDeprecated,
     ReportUserIsTyping,
+    SidePanelContext,
     Transaction,
     TransactionViolations,
     VisibleReportActionsDerivedValue,
@@ -355,6 +356,7 @@ type AddCommentParams = {
     currentUserAccountID: number;
     shouldPlaySound?: boolean;
     isInSidePanel?: boolean;
+    sidePanelContext?: SidePanelContext;
     pregeneratedResponseParams?: PregeneratedResponseParams;
     reportActionID?: string;
     delegateAccountID: number | undefined;
@@ -369,6 +371,7 @@ type AddActionsParams = {
     text?: string;
     file?: FileObject;
     isInSidePanel?: boolean;
+    sidePanelContext?: SidePanelContext;
     pregeneratedResponseParams?: PregeneratedResponseParams;
     reportActionID?: string;
     delegateAccountID: number | undefined;
@@ -385,6 +388,7 @@ type AddAttachmentWithCommentParams = {
     shouldPlaySound?: boolean;
     isInSidePanel?: boolean;
     delegateAccountID: number | undefined;
+    sidePanelContext?: SidePanelContext;
 };
 
 const addNewMessageWithText = new Set<string>([WRITE_COMMANDS.ADD_COMMENT, WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT]);
@@ -682,6 +686,7 @@ function addActions({
     text = '',
     file,
     isInSidePanel = false,
+    sidePanelContext,
     pregeneratedResponseParams,
     reportActionID,
     delegateAccountID,
@@ -797,7 +802,8 @@ function addActions({
         idempotencyKey: Str.guid(),
     };
 
-    if (reportIDDeeplinkedFromOldDot === reportID && isConciergeChatReport(report)) {
+    const isConciergeChat = isConciergeChatReport(report);
+    if (reportIDDeeplinkedFromOldDot === reportID && isConciergeChat) {
         parameters.isOldDotConciergeChat = true;
     }
 
@@ -805,13 +811,17 @@ function addActions({
         parameters.attachmentID = attachmentID;
     }
 
-    if (isInSidePanel && (isConciergeChatReport(report) || isAdminRoom(report))) {
+    if (isInSidePanel && (isConciergeChat || isAdminRoom(report))) {
         const pageHTML = capturePageHTML();
         if (pageHTML) {
             parameters.pageHTML = pageHTML;
         }
     }
 
+    if (isInSidePanel && isConciergeChat && sidePanelContext && commandName === WRITE_COMMANDS.ADD_COMMENT) {
+        parameters.sidePanelContext = JSON.stringify(sidePanelContext);
+    }
+
     // Add pregenerated params
     if (pregeneratedResponseParams) {
         parameters.optimisticConciergeReportActionID = pregeneratedResponseParams.optimisticConciergeReportActionID;
@@ -938,6 +948,7 @@ function addAttachmentWithComment({
     shouldPlaySound = false,
     isInSidePanel = false,
     delegateAccountID,
+    sidePanelContext,
 }: AddAttachmentWithCommentParams) {
     if (!report?.reportID) {
         return;
@@ -952,7 +963,7 @@ function addAttachmentWithComment({
 
     // Single attachment
     if (!Array.isArray(attachments)) {
-        addActions({report, notifyReportID, ancestors, timezoneParam: timezone, currentUserAccountID, text, file: attachments, isInSidePanel, delegateAccountID});
+        addActions({report, notifyReportID, ancestors, timezoneParam: timezone, currentUserAccountID, text, file: attachments, isInSidePanel, delegateAccountID, sidePanelContext});
         handlePlaySound();
         return;
     }
@@ -962,7 +973,18 @@ function addAttachmentWithComment({
 
     // Remaining: attachment-only actions (no text duplication)
     for (let i = 1; i < attachments?.length; i += 1) {
-        addActions({report, notifyReportID, ancestors, timezoneParam: timezone, currentUserAccountID, text: '', file: attachments?.at(i), isInSidePanel, delegateAccountID});
+        addActions({
+            report,
+            notifyReportID,
+            ancestors,
+            timezoneParam: timezone,
+            currentUserAccountID,
+            text: '',
+            file: attachments?.at(i),
+            isInSidePanel,
+            delegateAccountID,
+            sidePanelContext,
+        });
     }
 
     // Play sound once
@@ -979,6 +1001,7 @@ function addComment({
     currentUserAccountID,
     shouldPlaySound,
     isInSidePanel,
+    sidePanelContext,
     pregeneratedResponseParams,
     reportActionID,
     delegateAccountID,
@@ -986,7 +1009,19 @@ function addComment({
     if (shouldPlaySound) {
         playSound(SOUNDS.DONE);
     }
-    addActions({report, notifyReportID, ancestors, timezoneParam, currentUserAccountID, text, isInSidePanel, pregeneratedResponseParams, reportActionID, delegateAccountID});
+    addActions({
+        report,
+        notifyReportID,
+        ancestors,
+        timezoneParam,
+        currentUserAccountID,
+        text,
+        isInSidePanel,
+        pregeneratedResponseParams,
+        reportActionID,
+        delegateAccountID,
+        sidePanelContext,
+    });
 }
 
 function reportActionsExist(reportID: string): boolean {
diff --git a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts
index b59ce22502fd..7428e041b6b0 100644
--- a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts
+++ b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts
@@ -26,6 +26,7 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type * as OnyxTypes from '@src/types/onyx';
 import {useComposerMeta} from './ComposerContext';
+import useSidePanelContext from './useSidePanelContext';
 
 function useComposerSubmit(reportID: string): (comment: string) => void {
     const {isOffline} = useNetwork();
@@ -34,6 +35,7 @@ function useComposerSubmit(reportID: string): (comment: string) => void {
     const personalDetails = usePersonalDetails();
     const {availableLoginsList} = useShortMentionsList();
     const isInSidePanel = useIsInSidePanel();
+    const sidePanelContext = useSidePanelContext(reportID);
     const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE);
     const delegateAccountID = useDelegateAccountID();
 
@@ -75,6 +77,7 @@ function useComposerSubmit(reportID: string): (comment: string) => void {
                 shouldPlaySound: true,
                 isInSidePanel,
                 delegateAccountID,
+                sidePanelContext,
             });
             attachmentFileRef.current = null;
             return;
@@ -145,6 +148,7 @@ function useComposerSubmit(reportID: string): (comment: string) => void {
             currentUserAccountID: currentUserPersonalDetails.accountID,
             shouldPlaySound: true,
             isInSidePanel,
+            sidePanelContext,
             reportActionID: optimisticReportActionID,
             delegateAccountID,
         });
diff --git a/src/pages/inbox/report/ReportActionCompose/useSidePanelContext.ts b/src/pages/inbox/report/ReportActionCompose/useSidePanelContext.ts
new file mode 100644
index 000000000000..5c94d31bdf26
--- /dev/null
+++ b/src/pages/inbox/report/ReportActionCompose/useSidePanelContext.ts
@@ -0,0 +1,56 @@
+import {useMemo} from 'react';
+import {useSearchStateContext} from '@components/Search/SearchContext';
+import {useCurrentReportIDState} from '@hooks/useCurrentReportID';
+import useIsInSidePanel from '@hooks/useIsInSidePanel';
+import useOnyx from '@hooks/useOnyx';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+
+function useSidePanelContext(reportID: string): OnyxTypes.SidePanelContext | undefined {
+    const isInSidePanel = useIsInSidePanel();
+    const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
+    const {currentReportID, currentRHPReportID} = useCurrentReportIDState();
+    const {currentSearchQueryJSON, selectedTransactionIDs, selectedTransactions, selectedReports} = useSearchStateContext();
+
+    return useMemo(() => {
+        if (conciergeReportID !== reportID || !isInSidePanel) {
+            return undefined;
+        }
+
+        const contextReportID = currentRHPReportID ?? currentReportID ?? undefined;
+
+        // selectedTransactions (map) is populated from the Search list; selectedTransactionIDs (array)
+        // is populated from the report table view. The two are mutually exclusive.
+        const txIDsFromMap = !isEmptyObject(selectedTransactions)
+            ? Object.entries(selectedTransactions)
+                  .filter(([, info]) => info.isSelected && !!info.transaction)
+                  .map(([id]) => id)
+            : [];
+        const allTransactionIDs = txIDsFromMap.length > 0 ? txIDsFromMap : selectedTransactionIDs;
+        const selectedTransactionIDsForContext = allTransactionIDs.length > 0 ? allTransactionIDs.join(',') : undefined;
+
+        const selectedReportIDsForContext =
+            selectedReports.length > 0
+                ? selectedReports
+                      .map((r) => r.reportID)
+                      .filter((id): id is string => !!id)
+                      .join(',') || undefined
+                : undefined;
+
+        // This condition is reached when we are either in the global Reports => Reports page, or within a single expense report having multiple transactions.
+        // If we have selectedReportIDs, that means we're in the Reports page, otherwise we're in the expense report RHP.
+        if (currentSearchQueryJSON?.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
+            return selectedReportIDsForContext ? {selectedReportIDs: selectedReportIDsForContext} : {reportID: contextReportID, selectedTransactionIDs: selectedTransactionIDsForContext};
+        }
+
+        if (!contextReportID && !selectedTransactionIDsForContext && !selectedReportIDsForContext) {
+            return undefined;
+        }
+
+        return {reportID: contextReportID, selectedTransactionIDs: selectedTransactionIDsForContext, selectedReportIDs: selectedReportIDsForContext};
+    }, [conciergeReportID, reportID, isInSidePanel, currentSearchQueryJSON?.type, currentRHPReportID, currentReportID, selectedTransactionIDs, selectedTransactions, selectedReports]);
+}
+
+export default useSidePanelContext;
diff --git a/src/types/onyx/SidePanel.tsx b/src/types/onyx/SidePanel.tsx
index 915e2ed7c48c..2f2bfa26ca9d 100644
--- a/src/types/onyx/SidePanel.tsx
+++ b/src/types/onyx/SidePanel.tsx
@@ -6,4 +6,11 @@ type SidePanel = {
     openNarrowScreen: boolean;
 };
 
+/**
+ * Describes the context of what the user was viewing when they sent a message from the Side Panel.
+ * Sent to the backend so Concierge can tailor its response to the user's current context.
+ */
+type SidePanelContext = {reportID?: string; selectedTransactionIDs?: string; selectedReportIDs?: string};
+
 export default SidePanel;
+export type {SidePanelContext};
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index adf447f942c3..8934f860d3bc 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -156,6 +156,7 @@ import type Session from './Session';
 import type ShareBankAccount from './ShareBankAccount';
 import type ShareTempFile from './ShareTempFile';
 import type SidePanel from './SidePanel';
+import type {SidePanelContext} from './SidePanel';
 import type StripeCustomerID from './StripeCustomerID';
 import type SupportalPermissionDenied from './SupportalPermissionDenied';
 import type Task from './Task';
@@ -362,6 +363,7 @@ export type {
     DismissedProductTraining,
     TravelProvisioning,
     SidePanel,
+    SidePanelContext,
     LastPaymentMethodType,
     ReportAttributesDerivedValue,
     LastSearchParams,
diff --git a/tests/unit/SuggestionMentionTest.tsx b/tests/unit/SuggestionMentionTest.tsx
index cc9760d2e52f..afe373edc9e1 100644
--- a/tests/unit/SuggestionMentionTest.tsx
+++ b/tests/unit/SuggestionMentionTest.tsx
@@ -121,7 +121,7 @@ describe('SuggestionMention', () => {
 
         mockUsePersonalDetails.mockImplementation(() => mockPersonalDetails);
         mockUseArrowKeyFocusManager.mockReturnValue([0, mockSetHighlightedMentionIndex]);
-        mockUseCurrentReportIDState.mockReturnValue({currentReportID: ''});
+        mockUseCurrentReportIDState.mockReturnValue({currentReportID: '', currentRHPReportID: ''});
         mockUseCurrentUserPersonalDetails.mockReturnValue({accountID: 1, login: 'current@gmail.com'});
         mockUseDebounce.mockImplementation((callback) => {
             const callbackRef = React.useRef(callback);
diff --git a/tests/unit/hooks/useSidePanelContext.test.ts b/tests/unit/hooks/useSidePanelContext.test.ts
new file mode 100644
index 000000000000..f4e5c9115458
--- /dev/null
+++ b/tests/unit/hooks/useSidePanelContext.test.ts
@@ -0,0 +1,190 @@
+import {renderHook} from '@testing-library/react-native';
+import Onyx from 'react-native-onyx';
+// eslint-disable-next-line import/order
+import useSidePanelContext from '@pages/inbox/report/ReportActionCompose/useSidePanelContext';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
+
+const REPORT_ID = 'report_123';
+
+// Mutable state referenced by jest.mock factories below
+let mockIsInSidePanel = false;
+let mockCurrentReportIDState: {currentReportID: string | undefined; currentRHPReportID: string | undefined} = {
+    currentReportID: undefined,
+    currentRHPReportID: undefined,
+};
+let mockSearchState: {
+    currentSearchQueryJSON: {type?: string} | undefined;
+    selectedTransactionIDs: string[];
+    selectedTransactions: Record<string, {isSelected: boolean; transaction?: Record<string, unknown>}>;
+    selectedReports: Array<{reportID?: string}>;
+} = {
+    currentSearchQueryJSON: undefined,
+    selectedTransactionIDs: [],
+    selectedTransactions: {},
+    selectedReports: [],
+};
+
+jest.mock('@hooks/useIsInSidePanel', () => ({
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    __esModule: true,
+    default: () => mockIsInSidePanel,
+}));
+
+jest.mock('@hooks/useCurrentReportID', () => ({
+    useCurrentReportIDState: () => mockCurrentReportIDState,
+}));
+
+jest.mock('@components/Search/SearchContext', () => ({
+    useSearchStateContext: () => mockSearchState,
+}));
+
+function resetMocks() {
+    mockIsInSidePanel = false;
+    mockCurrentReportIDState = {currentReportID: undefined, currentRHPReportID: undefined};
+    mockSearchState = {
+        currentSearchQueryJSON: undefined,
+        selectedTransactionIDs: [],
+        selectedTransactions: {},
+        selectedReports: [],
+    };
+}
+
+describe('useSidePanelContext', () => {
+    beforeAll(() => {
+        Onyx.init({keys: ONYXKEYS});
+    });
+
+    beforeEach(async () => {
+        resetMocks();
+        await Onyx.clear();
+        await waitForBatchedUpdates();
+    });
+
+    async function renderWithConciergeReport(reportID = REPORT_ID) {
+        await Onyx.merge(ONYXKEYS.CONCIERGE_REPORT_ID, REPORT_ID);
+        await waitForBatchedUpdates();
+        return renderHook(() => useSidePanelContext(reportID));
+    }
+
+    it('returns undefined when not in the side panel', async () => {
+        mockIsInSidePanel = false;
+        const {result} = await renderWithConciergeReport();
+        await waitForBatchedUpdates();
+        expect(result.current).toBeUndefined();
+    });
+
+    it('returns undefined when reportID does not match conciergeReportID', async () => {
+        mockIsInSidePanel = true;
+        const {result} = await renderWithConciergeReport('different_report');
+        await waitForBatchedUpdates();
+        expect(result.current).toBeUndefined();
+    });
+
+    it('returns undefined when no context is available', async () => {
+        mockIsInSidePanel = true;
+        const {result} = await renderWithConciergeReport();
+        await waitForBatchedUpdates();
+        expect(result.current).toBeUndefined();
+    });
+
+    describe('EXPENSE_REPORT search type', () => {
+        beforeEach(() => {
+            mockIsInSidePanel = true;
+            mockSearchState.currentSearchQueryJSON = {type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT};
+        });
+
+        it('returns only selectedReportIDs when reports are selected', async () => {
+            mockSearchState.selectedReports = [{reportID: 'report_1'}, {reportID: 'report_2'}];
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toEqual({selectedReportIDs: 'report_1,report_2'});
+        });
+
+        it('falls back to contextReportID and selectedTransactionIDs when no reports are selected', async () => {
+            mockSearchState.selectedReports = [];
+            mockCurrentReportIDState = {currentRHPReportID: 'rhp_report', currentReportID: undefined};
+            mockSearchState.selectedTransactionIDs = ['txn_a'];
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toEqual({reportID: 'rhp_report', selectedTransactionIDs: 'txn_a'});
+        });
+
+        it('includes child transaction selections when drilling into a report', async () => {
+            mockSearchState.selectedReports = [];
+            mockSearchState.selectedTransactionIDs = ['txn_1', 'txn_2'];
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toMatchObject({selectedTransactionIDs: 'txn_1,txn_2'});
+        });
+    });
+
+    describe('contextReportID resolution', () => {
+        beforeEach(() => {
+            mockIsInSidePanel = true;
+        });
+
+        it('prefers currentRHPReportID over currentReportID', async () => {
+            mockCurrentReportIDState = {currentRHPReportID: 'rhp_report', currentReportID: 'main_report'};
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toMatchObject({reportID: 'rhp_report'});
+        });
+
+        it('falls back to currentReportID when no RHP report is open', async () => {
+            mockCurrentReportIDState = {currentRHPReportID: undefined, currentReportID: 'main_report'};
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toMatchObject({reportID: 'main_report'});
+        });
+    });
+
+    describe('transaction ID derivation', () => {
+        beforeEach(() => {
+            mockIsInSidePanel = true;
+        });
+
+        it('derives IDs from selectedTransactions map (Search list selections), filtering out unselected and non-transaction entries', async () => {
+            mockSearchState.selectedTransactions = {
+                txn1: {isSelected: true, transaction: {}},
+                txn2: {isSelected: true, transaction: {}},
+                txn3: {isSelected: false, transaction: {}}, // not selected
+                reportKey: {isSelected: true, transaction: undefined}, // empty report row — no transaction
+            };
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toMatchObject({selectedTransactionIDs: 'txn1,txn2'});
+        });
+
+        it('falls back to selectedTransactionIDs array when map is empty (report table view selections)', async () => {
+            mockSearchState.selectedTransactions = {};
+            mockSearchState.selectedTransactionIDs = ['txn_a', 'txn_b'];
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toMatchObject({selectedTransactionIDs: 'txn_a,txn_b'});
+        });
+
+        it('prefers the map over the array when both are non-empty', async () => {
+            mockSearchState.selectedTransactions = {txnMap: {isSelected: true, transaction: {}}};
+            mockSearchState.selectedTransactionIDs = ['txnArray'];
+            const {result} = await renderWithConciergeReport();
+            await waitForBatchedUpdates();
+            expect(result.current).toMatchObject({selectedTransactionIDs: 'txnMap'});
+        });
+    });
+
+    it('returns all available context when every field is populated', async () => {
+        mockIsInSidePanel = true;
+        mockCurrentReportIDState = {currentRHPReportID: 'rhp_report', currentReportID: undefined};
+        mockSearchState.selectedTransactions = {txn1: {isSelected: true, transaction: {}}};
+        mockSearchState.selectedReports = [{reportID: 'report_1'}];
+        const {result} = await renderWithConciergeReport();
+        await waitForBatchedUpdates();
+        expect(result.current).toEqual({
+            reportID: 'rhp_report',
+            selectedTransactionIDs: 'txn1',
+            selectedReportIDs: 'report_1',
+        });
+    });
+});
