Skip to content

[$250] Show thread comments/comment number below the support ticket threads. #104400

Open

Description

@melvin-bot

Problem

Support ticket previews in the Concierge chat do not show that the ticket thread has replies. Customers can't tell that a conversation with their support rep is happening inside the ticket, so they keep typing in the main Concierge chat instead.

Example

  1. A customer created a support ticket and got good help from a support rep.
  2. The customer started following the rep's instructions.
  3. The customer opened Concierge in the RHP and saw the support ticket preview.
  4. The customer typed in the main Concierge chat, thinking they were replying to the rep. This started a new thread with Concierge instead.
  5. Concierge replied, and the customer got frustrated.

There is currently no indicator on the support ticket preview that the support rep has responded.
Image

Solution

Show the thread replies (reply count, like regular threads) below the support ticket preview, so customers know there is a conversation in the ticket and can open it. This should reuse the existing thread-reply UI pattern.

Image

cc @jasperhuangg

Reported in Slack.

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @ChavdaSachin

Activity

  1. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Oct 21, 2026
  2. changed the title [-]Show thread comments/comment number below the support ticket threads.[/-] [+][$250] Show thread comments/comment number below the support ticket threads.[/+] on Oct 21, 2026
  3. removed
    Help WantedApply this label when an issue is open to proposals by contributors
    on Oct 21, 2026
  4. wildan-m commented on Oct 21, 2026

    @wildan-m
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When a support ticket thread in the Concierge chat has replies from a support rep, the support ticket preview gives no indication of them. Unlike regular threads, no reply count is shown below the preview, so customers don't realize a conversation is happening inside the ticket and reply in the main Concierge chat instead, starting an unrelated exchange with Concierge.

    What is the root cause of that problem?

    The "replies" footer that appears under a report action with a thread is rendered by ReportActionItemThread inside ReportActionItem, gated by shouldDisplayThreadReplies:

    {shouldDisplayThreadReplies && (
    <ReportActionItemThread
    reportAction={action}
    report={report}
    isHovered={isHoveredOrActive}
    onSecondaryInteraction={showPopover}
    isActive={isReportActionActive && !isContextMenuActive}
    isEditingInline={isEditingInline}
    processingAgentIDs={isAgentProcessingInThread ? threadProcessingAgentIDs : undefined}
    />
    )}

    shouldDisplayThreadReplies explicitly returns false for support ticket creation actions because of the trailing isCreatedSupportTicketReportAction exclusion:

    App/src/libs/ReportUtils.ts

    Lines 12531 to 12534 in 9d14b70

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
    const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
    return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction);
    }

    This exclusion was added deliberately when support tickets were built (commit 187e161410c, "fix(support): hide ticket preview reply counts"), along with a unit test asserting that a parent action whose childType is a support ticket omits the replies footer. The SupportTicketPreview component itself renders no reply indicator either, so even when the ticket thread has visible replies (childVisibleActionCount and childCommenterCount are populated on the parent action, same as regular threads), nothing on the preview reflects them:

    if (isCreatedSupportTicketReportAction(action)) {
    return (
    <SupportTicketPreview
    style={displayAsGroup ? [] : [styles.mt1]}
    action={action}
    isHovered={hovered}
    />
    );
    }

    What changes do you think we should make in order to solve the problem?

    In shouldDisplayThreadReplies in src/libs/ReportUtils.ts, remove the isCreatedSupportTicketReportAction exclusion so the function evaluates support ticket creation actions with exactly the same condition as regular threads (visible child actions plus commenters, and not the thread's own parent-action view). A support ticket creation action is an ADD_COMMENT action whose childType is a support ticket, so it already flows through the standard thread bookkeeping:

    function isCreatedSupportTicketReportAction(reportAction: OnyxInputOrEntry<ReportAction>): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT> {
    return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) && reportAction.childType === CONST.REPORT.TYPE.SUPPORT_TICKET;
    }

    With the exclusion gone, the existing ReportActionItemThread footer — reply count, replier avatars, and last-reply time — renders below the support ticket preview, and pressing it opens the ticket thread. This reuses the existing thread-reply UI pattern exactly as the issue requests; no changes to SupportTicketPreview are needed. The helper is also read by the Join thread / Leave thread context menu items, but those items (and Reply in thread) keep their own explicit isCreatedSupportTicketReportAction exclusion, so the ticket preview's context menu is unchanged. The isCreatedSupportTicketReportAction import in ReportUtils.ts stays, since canEditReportAction still uses it to keep ticket previews non-editable.

    We should also update the existing unit test table for shouldDisplayThreadReplies in tests/unit/ReportUtilsTest.ts, which currently expects the footer to be omitted for the support ticket child type, so it now expects the footer to show — keeping explicit regression coverage for the new behavior:

    https://github.com/Expensify/App/blob/9d14b70be07d516add0a1fe43c06aef15bec321e/tests/unit/ReportUtilsTest.ts#L7200-L7221

    Compare branch: https://github.com/Expensify/App/compare/main...wildan-m:App:wildan/104400-support-ticket-thread-replies?expand=1

    What alternative solutions did you explore? (Optional)

    Rendering a bespoke reply-count row inside the SupportTicketPreview component itself. Rejected: it would duplicate what ReportActionItemThread already does (count, avatars, last-reply time, navigation to the thread), would need its own styling and press handling, and would drift from regular threads over time. Gating the existing footer at the ReportActionItem level keeps support tickets visually and behaviorally identical to the established thread pattern.

  5. melvin-bot commented on Oct 21, 2026

    @melvin-bot
    Author
  6. wildan-m commented on Oct 21, 2026

    @wildan-m
    Contributor

    Proposal updated — added compare branch with implementation.

  7. added
    Help WantedApply this label when an issue is open to proposals by contributors
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 22, 2026
  8. abbasifaizan70 commented on Oct 22, 2026

    @abbasifaizan70
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When a customer submits a support ticket from the Concierge chat and a support rep replies inside that ticket's thread, the support ticket preview shown in the main Concierge chat gives no indication that a reply/conversation exists. Customers don't realize the rep already responded inside the ticket, so they type their follow-up in the main Concierge chat instead of opening the ticket thread, which kicks off an unrelated conversation with Concierge and causes frustration.

    What is the root cause of that problem?

    Every normal chat message that has thread replies shows a "X replies" row (avatars + reply count + timestamp) right below it when viewed in the parent chat. That row is rendered by ReportActionItemThread and is only shown when shouldDisplayThreadReplies returns true:

    const shouldDisplayThreadReplies = shouldDisplayThreadRepliesUtils(action, isThreadReportParentAction, isAgentProcessingInThread) && !isOnSearch;

    {shouldDisplayThreadReplies && (
    <ReportActionItemThread
    reportAction={action}
    report={report}
    isHovered={isHoveredOrActive}
    onSecondaryInteraction={showPopover}
    isActive={isReportActionActive && !isContextMenuActive}
    isEditingInline={isEditingInline}
    processingAgentIDs={isAgentProcessingInThread ? threadProcessingAgentIDs : undefined}
    />
    )}

    shouldDisplayThreadReplies itself explicitly excludes support ticket actions from ever showing the indicator, regardless of whether the ticket actually has replies:

    App/src/libs/ReportUtils.ts

    Lines 12616 to 12619 in ec8371a

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
    const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
    return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction);
    }

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
        const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
        return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction);
    }

    isCreatedSupportTicketReportAction flags the ADD_COMMENT action created when a support ticket is opened (its childType is supportTicket):

    function isCreatedSupportTicketReportAction(reportAction: OnyxInputOrEntry<ReportAction>): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT> {
    return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) && reportAction.childType === CONST.REPORT.TYPE.SUPPORT_TICKET;
    }

    So even though the support ticket's ADD_COMMENT action gets childVisibleActionCount/childCommenterCount populated exactly like any other thread parent once the rep replies, the !isCreatedSupportTicketReportAction(reportAction) check always short-circuits the result to false for support tickets, and ReportActionItemThread never renders for them.

    Instead, support tickets are rendered via a dedicated SupportTicketPreview card, which only shows the ticket's checkbox/avatar/name/arrow — it has no reply-count UI at all:

    if (isCreatedSupportTicketReportAction(action)) {
    return (
    <SupportTicketPreview
    style={displayAsGroup ? [] : [styles.mt1]}
    action={action}
    isHovered={hovered}
    />
    );
    }

    return (
    <OfflineWithFeedback
    shouldShowErrorMessages
    errors={createReportErrors}
    onClose={dismissSupportTicket}
    >
    <View style={styles.chatItemMessage}>
    <PressableWithoutFeedback
    onPress={() => Navigation.navigate(getReportRouteForCurrentContext({reportID: supportTicketReportID}))}
    style={[styles.flexRow, styles.alignItemsCenter, style]}
    role={CONST.ROLE.BUTTON}
    accessibilityLabel={supportTicketName}
    sentryLabel={CONST.SENTRY_LABEL.SUPPORT_TICKET.PREVIEW_CARD}
    >
    <Tooltip
    text={translate('supportTicket.checkboxTooltip')}
    maxWidth={variables.w184}
    >
    <View>
    <Checkbox
    isChecked={isResolved}
    style={[supportRepAccountID > 0 && styles.mr2, styles.cursorDisabled]}
    onPress={(event) => {
    event?.stopPropagation();
    showCheckboxInfo();
    }}
    shouldStopMouseDownPropagation
    shouldSelectOnPressEnter
    accessibilityLabel={translate('supportTicket.checkboxTooltip')}
    sentryLabel={CONST.SENTRY_LABEL.SUPPORT_TICKET.PREVIEW_CHECKBOX}
    />
    </View>
    </Tooltip>
    {supportRepAccountID > 0 && (
    <AccountAvatar
    accountID={supportRepAccountID}
    size={CONST.AVATAR_SIZE.SMALL}
    containerStyle={styles.mr2}
    />
    )}
    <Text style={[supportRepAccountID <= 0 && styles.ml3, styles.flex1]}>{supportTicketName}</Text>
    <Icon
    src={icons.ArrowRight}
    fill={StyleUtils.getIconFillColor({buttonState: getButtonState({isActive: isHovered})})}
    />
    </PressableWithoutFeedback>
    </View>
    </OfflineWithFeedback>
    );

    For comparison, regular Tasks go through the exact same shouldDisplayThreadReplies check but are not excluded from it, which is why task previews correctly show "X replies" under them when a task thread has comments:

    if (isCreatedTaskReportAction(action)) {
    return (
    <TaskPreview
    style={displayAsGroup ? [] : [styles.mt1]}
    chatReportID={reportID}
    action={action}
    isHovered={hovered}
    policyID={policyID}
    />
    );

    This confirms the hard-coded support ticket exclusion in shouldDisplayThreadReplies is the actual root cause: it suppresses the exact reply indicator the issue is asking for, even though the underlying data (childVisibleActionCount, childCommenterCount, childOldestFourAccountIDs, childReportID) is already present and correct for support ticket actions.

    What changes do you think we should make in order to solve the problem?

    Remove the support-ticket exclusion from shouldDisplayThreadReplies so support ticket actions follow the same rule as every other thread-having action (including tasks), reusing the existing ReportActionItemThread "X replies" UI exactly as the issue requests, instead of building a new indicator:

    App/src/libs/ReportUtils.ts

    Lines 12616 to 12619 in ec8371a

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
    const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
    return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction);
    }

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
        const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
        return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction;
    }

    With this change, once a support rep replies inside the ticket thread, childVisibleActionCount/childCommenterCount on the ticket's ADD_COMMENT action become truthy (the same fields SupportTicketPreview already reads childReportID from), shouldDisplayThreadReplies returns true, and ReportActionItemThread automatically renders the "X replies" row (with the rep's avatar, reply count, and last-reply timestamp) directly below the SupportTicketPreview card in the main Concierge chat — matching the mock in the issue. Tapping it uses the existing navigateToAndOpenChildReport flow to jump straight into the ticket thread, so customers can immediately see the rep replied and continue the conversation there instead of starting a new thread with Concierge.

    No changes are needed to ReportActionItemThread, SupportTicketPreview, or the backend — the thread metadata needed to drive the indicator is already being sent and stored on the support ticket's ADD_COMMENT action; the indicator was simply being explicitly suppressed for that one action type.

  9. 43 remaining items

  10. samranahm commented on Oct 23, 2026

    @samranahm
    Contributor

    PROPOSAL UPDATED


    When the replies row is tapped on support-ticket action, user should navigate directly to the existing ticket using the same method as the preview card. This avoids the generic thread handler creating a new simple non-support ticket thread if the ticket thread isn’t loaded in Onyx. Other thread replies keep their existing navigation behavior.

    cc: @ChavdaSachin

  11. juni2003 commented on Oct 23, 2026

    @juni2003

    Contributor details

    Your Expensify account email: juni.xatti@gmail.com

    Upwork Profile Link: https://www.upwork.com/freelancers/~01867ae3203c9b7815?mp_source=share

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When a support representative replies inside a customer's support ticket, the ticket preview in the parent Concierge conversation does not show any reply indicator.

    Because the preview looks unchanged, the customer may reply in the main Concierge conversation instead of opening the existing support-ticket conversation. This starts an unrelated conversation with Concierge and makes it appear that the support representative did not respond.

    The expected behavior is for the support-ticket preview to show the same reply summary used by regular threaded messages, including the reply count, participant avatar(s), and most recent reply time.

    What is the root cause of that problem?

    The support-ticket preview is rendered as a report action whose child report is the support ticket:

    • src/pages/inbox/report/actionContents/ActionContentRouter.tsx
    • src/components/ReportActionItem/SupportTicketPreview.tsx

    SupportTicketPreview renders the ticket card itself, but it does not render a reply summary. The shared reply summary is rendered separately by ReportActionItemThread inside ReportActionItem.

    ReportActionItem calls shouldDisplayThreadReplies() from:

    src/libs/ReportUtils.ts

    That helper already checks the normal thread metadata:

    • childVisibleActionCount
    • childCommenterCount
    • isAgentProcessingInThread
    • isThreadReportParentAction

    However, it also contains a support-ticket exclusion:

    return (hasReplies || isAgentProcessingInThread) &&
        !isThreadReportParentAction &&
        !isCreatedSupportTicketReportAction(reportAction);

    Support-ticket creation actions are ADD_COMMENT actions with a support-ticket childType. When a support representative replies, the parent action already receives the same child-thread metadata used by regular comments and tasks. Therefore, the data needed by ReportActionItemThread exists, but the final condition always prevents the component from mounting.

    The original support-ticket implementation intentionally hid this footer, which is why the current unit test expects support-ticket actions to return false. Issue #104400 reverses that product decision.

    There is also a second issue that must be fixed at the same time.

    ReportActionItemThread currently opens its child by calling:

    navigateToAndOpenChildReport(...)

    That generic flow may call createChildReport() when the child report is not currently loaded in Onyx. createChildReport() is designed for locally-created chat threads and can build an optimistic report with type: CHAT.

    That behavior is unsafe for support tickets because:

    1. A support ticket is an existing server-side report, not a new chat thread.
    2. The parent action already contains the real childReportID.
    3. SupportTicketPreview already opens the ticket directly using getReportRouteForCurrentContext().
    4. If the generic fallback writes a chat-shaped optimistic report under the ticket's report ID, the application may temporarily stop recognizing it as a support ticket.
    5. This can affect support-ticket-specific UI such as the support-ticket header, resolved footer, survey, composer rules, and context-menu restrictions.

    Therefore, removing only the visibility exclusion would fix the missing UI but could create an inconsistent navigation path when the ticket report is not hydrated in Onyx.

    What changes do you think we should make in order to solve the problem?

    1. Allow the shared reply footer for support-ticket previews

    Update shouldDisplayThreadReplies() in:

    src/libs/ReportUtils.ts

    The helper should use the existing thread conditions without excluding support-ticket creation actions:

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
        const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
    
        return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction;
    }

    This preserves the existing behavior for:

    • Actions without replies.
    • The parent action rendered inside its own thread.
    • Agent-processing indicators.
    • Search results, because the caller still controls the existing search exclusion.
    • Normal chat, task, and expense threads.

    The isCreatedSupportTicketReportAction import must remain if it is still used by other functions such as canEditReportAction. Only its use inside shouldDisplayThreadReplies() should be removed.

    This allows the existing ReportActionItemThread component to render beneath SupportTicketPreview, reusing the established reply-count UI instead of introducing a second support-ticket-specific implementation.

    2. Make reply-footer navigation ticket-safe

    Update:

    src/pages/inbox/report/ReportActionItemThread.tsx

    Before calling the generic navigateToAndOpenChildReport() flow, detect support-ticket creation actions and navigate directly to the existing child report.

    The relevant behavior should be equivalent to:

    onPress={() => {
        if (isCreatedSupportTicketReportAction(reportAction)) {
            const supportTicketReportID = childReport?.reportID ?? reportAction.childReportID;
    
            if (supportTicketReportID) {
                Navigation.navigate(getReportRouteForCurrentContext({reportID: supportTicketReportID}));
            }
    
            return;
        }
    
        const participantsPersonalDetails = getParticipantsPersonalDetails(
            [currentUserAccountID, Number(reportAction.actorAccountID)],
            personalDetails,
        );
    
        navigateToAndOpenChildReport(
            childReport,
            reportAction,
            report,
            currentUserAccountID,
            introSelected,
            participantsPersonalDetails,
            isSelfTourViewed,
            conciergeChat,
        );
    }}

    The ticket branch should use the same navigation helper as SupportTicketPreview:

    getReportRouteForCurrentContext({reportID: supportTicketReportID})

    The fallback to reportAction.childReportID is important because the ticket report may not yet be available in Onyx. This preserves the existing behavior of the preview card and prevents the generic child-report flow from creating an optimistic chat under the ticket's report ID.

    Normal comments and other threaded actions must continue using navigateToAndOpenChildReport() unchanged.

    This is preferable to modifying createChildReport() globally for this issue because it:

    • Changes only the newly exposed support-ticket interaction.
    • Does not alter optimistic creation behavior for ordinary chat threads.
    • Does not change task, expense, or transaction-thread creation semantics.
    • Keeps support-ticket navigation policy close to the support-ticket UI behavior.
    • Makes the reply footer and ticket preview use the same route-resolution logic.

    3. Preserve existing context-menu restrictions

    No new context-menu actions should be added.

    The following existing restrictions must remain unchanged:

    • Reply in thread remains unavailable for support-ticket actions.
    • Join thread remains unavailable.
    • Leave thread remains unavailable.
    • Support-ticket preview actions remain non-editable.
    • The ticket's own parent action does not display a nested reply footer inside the ticket thread.

    Removing the exclusion from shouldDisplayThreadReplies() does not automatically enable those actions because their context-menu entries already have separate support-ticket checks.

    4. Update and extend regression coverage

    Update the existing shouldDisplayThreadReplies() test in:

    tests/unit/ReportUtilsTest.ts

    The support-ticket case should now expect the reply footer to be displayed when reply metadata exists.

    The test coverage should include:

    1. Support-ticket action with visible replies returns true.
    2. Support-ticket action with no visible replies returns false.
    3. Support-ticket action with isAgentProcessingInThread === true returns true.
    4. Support-ticket action used as the parent action inside its own thread returns false.
    5. Normal chat actions continue returning true when they have replies.
    6. Task actions continue returning true when they have replies.
    7. canEditReportAction() continues returning false for support-ticket creation actions.
    8. Search results continue to hide the footer through the existing caller-level guard.

    Add or extend a focused UI/navigation test for:

    src/pages/inbox/report/ReportActionItemThread.tsx

    The important navigation regression case is:

    • The support-ticket parent action contains a valid childReportID.
    • The support-ticket report is absent from Onyx.
    • Pressing the replies row navigates to the ticket route using that ID.
    • No optimistic CHAT report is created under the ticket report key.
    • The normal thread navigation path is still used for a regular chat action.

    The support-ticket context-menu test should also continue verifying that Reply in thread, Join thread, Leave thread, and reactions remain hidden where applicable.

    Files to change

    The implementation should be limited to these files:

    1. src/libs/ReportUtils.ts

      • Remove the support-ticket exclusion from shouldDisplayThreadReplies().
      • Keep the support-ticket helper import if it is required elsewhere.
    2. src/pages/inbox/report/ReportActionItemThread.tsx

      • Add the support-ticket-specific direct navigation branch.
      • Leave normal thread navigation unchanged.
    3. tests/unit/ReportUtilsTest.ts

      • Update the support-ticket visibility expectation.
      • Add coverage for zero replies, replies, processing state, and thread-parent state.
    4. The existing focused UI/navigation test file for ReportActionItemThread

      • Add coverage for an unloaded support-ticket report.
      • Verify that direct ticket navigation does not create an optimistic chat.
      • If no suitable test file exists, add a focused test beside the component according to the repository's existing test conventions.

    No changes are required to:

    • SupportTicketPreview.tsx
    • ActionContentRouter.tsx
    • The backend response format
    • Onyx schema
    • Context-menu behavior
    • Mobile-Expensify or HybridApp integration

    Expected result

    After a support representative replies:

    • The Concierge support-ticket preview displays the standard reply summary.
    • The reply count is shown using the existing thread UI.
    • The support representative's avatar can appear in the reply summary.
    • The most recent reply time is displayed.
    • Pressing the summary opens the existing support ticket.
    • The ticket opens correctly even if it has not yet been loaded into Onyx.
    • No optimistic chat report is created for the ticket.
    • Tickets without replies remain visually unchanged.
    • Normal threads retain their current behavior.
    • Support-ticket context-menu restrictions remain unchanged.

    What alternative solutions did you explore?

    Add a custom reply row inside SupportTicketPreview

    This would solve the visual problem, but it would duplicate logic already implemented by ReportActionItemThread, including:

    • Reply count formatting.
    • Avatar rendering.
    • Timestamp formatting.
    • Accessibility labels.
    • Press behavior.
    • Hover and active states.
    • Cross-platform styling.

    It would also create two separate implementations that could diverge later. Reusing the shared footer is smaller and matches the issue's requirement to use the existing thread-reply pattern.

    Only remove the condition from shouldDisplayThreadReplies()

    This fixes the missing footer but does not fully fix the user flow. If the support-ticket report is absent from Onyx, pressing the newly visible footer can enter the generic child-report creation path and temporarily represent the ticket as a normal chat.

    The navigation change is therefore required for a complete fix.

    Change createChildReport() globally based on childType

    A broader change could prevent optimistic chat creation for every typed child report, such as tasks or expenses. However, that changes shared report-creation behavior beyond the scope of this issue and requires a wider audit of all callers and optimistic workflows.

    A support-ticket-specific route branch is safer for this issue because it fixes the newly exposed interaction without changing unrelated child-report behavior.

    Add an unread/bold state for support-ticket replies

    Showing unread or bold styling could be useful as a separate enhancement, but it is not required by the issue. The reported problem is that customers cannot tell that a thread exists at all. Adding read-state logic would introduce additional Onyx subscriptions and behavior that are not necessary to solve the original problem.

    Validation plan

    Before submitting a pull request, run the repository-required checks:

    npm run lint-changed
    npm run typecheck
    npm run react-compiler-compliance-check check-changed
    npm run spell-changed

    Run the focused tests for:

    tests/unit/ReportUtilsTest.ts
    tests/unit/ContextMenuActionsSupportTicketTest.ts

    Also run the focused ReportActionItemThread UI/navigation test after adding the regression coverage.

    Manual verification should cover:

    1. Web Concierge chat with a support ticket that has no replies.
    2. Web Concierge chat after a support representative replies.
    3. Concierge in the right-hand panel.
    4. Narrow/mobile web layout.
    5. Android HybridApp.
    6. iOS HybridApp.
    7. The ticket report loaded in Onyx before pressing the reply row.
    8. The ticket report absent from Onyx before pressing the reply row.
    9. A normal chat thread to confirm its navigation is unchanged.
    10. Support-ticket context menus to confirm Reply in thread, Join thread, and Leave thread remain unavailable.

    This proposal intentionally reuses the existing reply-summary component and fixes the ticket navigation invariant at the point where the new interaction is exposed.

  12. melvin-bot commented on Oct 23, 2026

    @melvin-bot
    Author

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  13. Hatem-tmi commented on Oct 24, 2026

    @Hatem-tmi

    Proposal

    What is the root cause of that problem?

    The Concierge ticket card is an ADD_COMMENT whose childType is SUPPORT_TICKET. That is isCreatedSupportTicketReportAction. The replies row already exists. ReportActionItem mounts ReportActionItemThread when shouldDisplayThreadReplies is true.

    On main, that helper ends with a hard exclusion for isCreatedSupportTicketReportAction. The row stays hidden even when childVisibleActionCount and childCommenterCount are already set. SupportTicketPreview only draws the card. It does not need a second reply UI.

    The exclusion is right for an empty "ticket created" card. It is wrong once the ticket has replies. That is why customers keep typing in the main Concierge composer.

    The existing row already opens the child report with navigateToAndOpenChildReport and reportAction.childReportID. Showing the row is the missing piece, not a new navigator.

    What changes do you think we should make in order to solve the problem?

    Leave isCreatedSupportTicketReportAction as it is. Other callers still need it.

    In shouldDisplayThreadReplies, skip that exclusion only when hasReplies is true (childVisibleActionCount greater than 0 and childCommenterCount set). Tickets with no replies stay without a replies row.

    Do not add a count or avatar row inside SupportTicketPreview.

    What alternative solutions did you explore?

    Put the count inside SupportTicketPreview. That duplicates ReportActionItemThread and misses the shared tap path.

    Remove isCreatedSupportTicketReportAction from the helper with no hasReplies guard. Empty tickets can then grow a replies row they should not have.

    Change childType so the action is no longer a support ticket. That breaks ticket identification.

    Contributor details
    Your Expensify account email: hatem.tmi@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0103158c5f0816abbe

  14. melvin-bot commented on Oct 24, 2026

    @melvin-bot
    Author

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  15. dhaulagani commented on Oct 24, 2026

    @dhaulagani

    Proposal

    What is the root cause of that problem?

    In src/libs/ReportUtils.ts, the shouldDisplayThreadReplies function explicitly suppresses thread reply indicators for support ticket parent actions:
    ypescript return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction);
    Because !isCreatedSupportTicketReportAction(reportAction) is hardcoded in the return condition, ReportActionItem never mounts ReportActionItemThread beneath SupportTicketPreview, even when customer support agents have actively responded inside the ticket (childVisibleActionCount > 0 and childCommenterCount > 0).

    What changes do you think we should make in order to solve the problem?

    1. Remove Support Ticket Exclusion in ReportUtils.ts:
      In src/libs/ReportUtils.ts, remove && !isCreatedSupportTicketReportAction(reportAction) from shouldDisplayThreadReplies:
      ypescript function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean { const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !reportAction?.childCommenterCount; return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction; }

    2. Guard Support Ticket Navigation in
      avigateToAndOpenChildReport (src/libs/actions/Report/index.ts)
      :
      When tapping the thread reply row on a support ticket action, if isCreatedSupportTicketReportAction(parentReportAction) is true and parentReportAction.childReportID exists, directly route to the ticket using getReportRouteForCurrentContext({reportID: parentReportAction.childReportID}) rather than executing createChildReport(). This prevents optimistically overriding the report's ype with a standard thread chat if childReport is not yet populated in the Onyx memory cache.

    3. Hermetic Regression Tests:
      Add test coverage in ests/unit/ReportUtilsTest.ts validating that shouldDisplayThreadReplies returns rue for support ticket report actions having visible child actions/replies, and returns alse when no replies exist.

    What alternative solutions did you explore? (Optional)

    Embedding a custom reply counter badge directly inside SupportTicketPreview.tsx. However, reusing the standard ReportActionItemThread component keeps the design system consistent across threads and avoids duplicate reply counter logic.

  16. SARAMALI15792 commented on Oct 24, 2026

    @SARAMALI15792

    @ChavdaSachin I have updated my proposal with the test results and the final test branch.

  17. ADITYASINGH1206 commented on Oct 24, 2026

    @ADITYASINGH1206

    🚨 Edited by proposal-police: This proposal was edited at 2026-10-24 13:14:13 UTC.

    Contributor details
    Your Expensify account email: simghaditya21@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~01b6be874e7d6b47d1

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    In Concierge chat, support ticket preview cards do not display the thread reply counter when a support representative responds. Customers have no visual cue that their conversation with support is happening inside the ticket thread, so they mistakenly continue typing into the main Concierge chat instead.

    What is the root cause of that problem?

    1. In src/libs/ReportUtils.ts, the shouldDisplayThreadReplies helper explicitly excludes support ticket creation actions:
      It checks (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction).
      Because !isCreatedSupportTicketReportAction(reportAction) is hardcoded in the return condition, ReportActionItem.tsx:698 never renders the existing ReportActionItemThread component beneath SupportTicketPreview, even when childVisibleActionCount > 0 and childCommenterCount > 0 confirm that support reps have sent replies.
    2. In tests/unit/ReportUtilsTest.ts, line 7418 explicitly tests and expects [CONST.REPORT.TYPE.SUPPORT_TICKET, false], enforcing this omission in the test suite.
    3. When the thread replies row is clicked, ReportActionItemThread delegates to navigateToAndOpenChildReport. If the support ticket report is not yet populated in Onyx (e.g. cold start or fresh device), navigateToAndOpenChildReport falls back to createChildReport, which builds an optimistic chat report rather than navigating to the support ticket route.

    What changes do you think we should make in order to solve the problem?

    1. Allow thread replies for support ticket actions with replies (src/libs/ReportUtils.ts):
      Update shouldDisplayThreadReplies to allow isCreatedSupportTicketReportAction(reportAction) when it has active replies (hasReplies is true):

      function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
          const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
          return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction;
      }

      Keep !isThreadReportParentAction and maintain existing exclusions for context menu actions (joinThread, replyInThread) in src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx so sub-threads cannot be created on support tickets.

    2. Support Ticket-Aware Navigation (src/pages/inbox/report/ReportActionItemThread.tsx):
      In ReportActionItemThread.tsx, intercept support ticket creation actions to navigate directly to the ticket route via getReportRouteForCurrentContext({reportID: reportAction.childReportID}):

      onPress={() => {
          if (isCreatedSupportTicketReportAction(reportAction)) {
              Navigation.navigate(getReportRouteForCurrentContext({reportID: reportAction.childReportID}));
              return;
          }
          const participantsPersonalDetails = getParticipantsPersonalDetails([currentUserAccountID, Number(reportAction.actorAccountID)], personalDetails);
          navigateToAndOpenChildReport(childReport, reportAction, report, currentUserAccountID, introSelected, participantsPersonalDetails, isSelfTourViewed, conciergeChat);
      }}

      This guarantees that tapping the reply row directly opens the support ticket route even when childReport is not yet cached in Onyx, preventing navigateToAndOpenChildReport from falling back to createChildReport and building an optimistic chat report.

    3. Preserve Context Menu Constraints:
      Keep the existing support-ticket checks in src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx intact so that "Reply in thread", "Join thread", and "Leave thread" remain disabled for support ticket preview actions.

    What specific scenarios should we cover in automated tests?

    1. Unit tests in tests/unit/ReportUtilsTest.ts:
      Update the existing test case for CONST.REPORT.TYPE.SUPPORT_TICKET to expect true when child replies exist:
      describe('shouldDisplayThreadReplies', () => {
          it.each([
              [CONST.REPORT.TYPE.SUPPORT_TICKET, true],
              [CONST.REPORT.TYPE.CHAT, true],
              [CONST.REPORT.TYPE.TASK, true],
          ] as const)('child report type %s shows the replies footer: %s', (childType, expected) => { ... });
      });
      • Verify that support ticket actions with 0 replies return false.
      • Verify that support ticket actions with isAgentProcessingInThread === true return true.
      • Verify that a support ticket parent action inside its own thread returns false.
    2. Navigation tests:
      • Verify that when the support ticket report is not cached in Onyx, pressing the thread replies summary navigates to the ticket route without creating an optimistic chat report.

    What alternative solutions did you explore? (Optional)

    1. Building custom reply count UI inside SupportTicketPreview.tsx:
      Explored creating a dedicated reply count badge directly inside SupportTicketPreview. We rejected this because it duplicates logic already implemented in ReportActionItemThread and does not follow the requirement to reuse the existing thread-reply UI pattern.
    2. Leaving navigateToAndOpenChildReport unchanged:
      Rejected because if the ticket report data has not yet loaded in Onyx, createChildReport would construct a generic chat report object, potentially displaying the ticket with the wrong header and missing support ticket actions.
  18. melvin-bot commented on Oct 24, 2026

    @melvin-bot
    Author

    ⚠️ Invalid email. Please make sure to create an Expensify account with this email first here.

  19. melvin-bot commented on Oct 24, 2026

    @melvin-bot
    Author

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  20. github-actions commented on Oct 24, 2026

    @github-actions
    Contributor

    ⚠️ @ADITYASINGH1206 Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.

  21. kevinjulio93 commented on Oct 24, 2026

    @kevinjulio93

    Contributor details
    Your Expensify account email: kevinjulio93@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0105b043b39ce044a9

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    Support ticket previews in the Concierge conversation do not display the thread replies summary (reply count, avatars, timestamp) when a support representative responds. Because the ticket card looks unchanged, customers assume no reply was received and continue typing in the main Concierge room, inadvertently creating new unwanted threads.

    What is the root cause of that problem?

    There are two distinct root causes:

    1. Suppression of the Thread Replies UI:
      In src/libs/ReportUtils.ts, shouldDisplayThreadReplies() explicitly excludes support tickets:
    return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction && !isCreatedSupportTicketReportAction(reportAction);

    Even though the backend sends childVisibleActionCount and childCommenterCount, the check forces false, preventing ReportActionItemThread from rendering under the preview.

    1. Onyx Cache Mutation / Corrupted Optimistic Fallback (LSP Violation):
      ReportActionItemThread calls navigateToAndOpenChildReport. If the support ticket report is not yet hydrated in Onyx, navigateToAndOpenChildReport falls back to createChildReport(). In src/libs/actions/Report/index.ts, createChildReport() unconditionally builds an optimistic report via buildOptimisticChatReport({ ... }) (hardcoding type: CHAT) and merges it into Onyx under ${childReportID}.
      Writing a generic CHAT object over an existing server-side SUPPORT_TICKET corrupts the report type in Onyx until the network fetch returns, temporarily stripping ticket-specific layout (ticket header, resolved footer, survey, and composer behavior).

    What changes do you think we should make in order to solve the problem?

    Applying SOLID principles (Single Responsibility & Open-Closed Principle), we must keep presentation components decoupled from report routing policies and protect Onyx integrity:

    1. Enable Thread Replies in ReportUtils.ts (SRP):

    Remove the !isCreatedSupportTicketReportAction(reportAction) clause from shouldDisplayThreadReplies:

    function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry<ReportAction>, isThreadReportParentAction: boolean, isAgentProcessingInThread = false): boolean {
        const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0 && !!reportAction?.childCommenterCount;
        return (hasReplies || isAgentProcessingInThread) && !isThreadReportParentAction;
    }

    This allows ReportActionItemThread to mount naturally for support ticket actions with replies, exactly as it does for comments and tasks, while preserving protections against rendering inside parent thread actions or search views.

    2. Prevent Fabricated Chat Seeding for Typed Child Reports in src/libs/actions/Report/index.ts (OCP & LSP):

    In createChildReport(), we should respect the parentReportAction.childType contract. When childReportID exists and parentReportAction.childType indicates an existing server-managed entity (e.g. supportTicket), we must not seed a fabricated CHAT report into Onyx.
    Instead, when the child report is not in Onyx, navigateToAndOpenChildReport should navigate directly via getReportRouteForCurrentContext({reportID: childReportID}), allowing ReportFetchHandler to fetch the authentic ticket from the backend without mutating local Onyx state with an invalid type.

    This approach ensures:

    • ReportActionItemThread.tsx remains a pure, decoupled presentation component without one-off ticket routing hacks.
    • Non-chat child reports (support tickets, tasks, expenses) are never overwritten by optimistic chat objects.
    • Normal chat threads retain their existing optimistic creation and routing lifecycle.

    3. Update Unit & Regression Tests:

    • In tests/unit/ReportUtilsTest.ts: Update the shouldDisplayThreadReplies table test to expect true for SUPPORT_TICKET actions with replies. Add a test ensuring ticket actions without replies return false.
    • In tests/unit/ReportTest.ts: Verify that navigateToAndOpenChildReport with an unloaded support ticket report navigates without writing an optimistic chat object to Onyx.

    What alternative solutions did you explore? (Optional)

    1. Branching directly in ReportActionItemThread.tsx: Adding an if (isCreatedSupportTicketReportAction) inside the presentation component violates SRP and OCP, leaking domain-level report routing into a shared UI component.
    2. Duplicating the reply row inside SupportTicketPreview.tsx: Violates DRY and duplicates avatar rendering, count formatting, and theme styles, risking UI divergence.
  22. melvin-bot commented on Oct 24, 2026

    @melvin-bot
    Author

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorHelp WantedApply this label when an issue is open to proposals by contributors

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions