From 3b6fbfa2a4f2efeb2a409934a2bd7a95e585e2c0 Mon Sep 17 00:00:00 2001 From: Pavan Kanukollu Date: Tue, 26 May 2026 14:16:42 -0700 Subject: [PATCH 1/2] Fix chat read receipt for sender rejoin Prevent the current user's own read receipt from advancing latestReadTime, which is used to mark sent messages as seen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...-react-fix-read-receipt-sender-rejoin.json | 9 ++++++++ .../chat-stateful-client/src/ChatContext.ts | 5 ++-- .../src/StatefulChatClient.test.ts | 23 ++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 change/@azure-communication-react-fix-read-receipt-sender-rejoin.json diff --git a/change/@azure-communication-react-fix-read-receipt-sender-rejoin.json b/change/@azure-communication-react-fix-read-receipt-sender-rejoin.json new file mode 100644 index 00000000000..3fdb393cc87 --- /dev/null +++ b/change/@azure-communication-react-fix-read-receipt-sender-rejoin.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "Chat", + "comment": "Fix read receipts so a sender's own read receipt does not mark their sent message as seen", + "packageName": "@azure/communication-react", + "email": "pkanukollu@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/chat-stateful-client/src/ChatContext.ts b/packages/chat-stateful-client/src/ChatContext.ts index 92eb64960bb..77dad0d838b 100644 --- a/packages/chat-stateful-client/src/ChatContext.ts +++ b/packages/chat-stateful-client/src/ChatContext.ts @@ -291,8 +291,9 @@ export class ChatContext { const thread = draft.threads[threadId]; const readReceipts = thread?.readReceipts; if (thread && readReceipts) { - // TODO(prprabhu): Replace `this.getState()` with `draft`? - if (readReceipt.sender !== this.getState().userId && thread.latestReadTime < readReceipt.readOn) { + const readReceiptSenderId = toFlatCommunicationIdentifier(readReceipt.sender); + const currentUserId = toFlatCommunicationIdentifier(draft.userId); + if (readReceiptSenderId !== currentUserId && thread.latestReadTime < readReceipt.readOn) { thread.latestReadTime = readReceipt.readOn; } readReceipts.push(readReceipt); diff --git a/packages/chat-stateful-client/src/StatefulChatClient.test.ts b/packages/chat-stateful-client/src/StatefulChatClient.test.ts index 101442cb1d4..dd1cee7f83a 100644 --- a/packages/chat-stateful-client/src/StatefulChatClient.test.ts +++ b/packages/chat-stateful-client/src/StatefulChatClient.test.ts @@ -319,13 +319,34 @@ describe('declarative chatClient subscribe to event properly after startRealtime chatMessageId: 'messageId1' }; - client.triggerEvent('readReceiptReceived', addedEvent); + await client.triggerEvent('readReceiptReceived', addedEvent); expect(client.getState().threads[threadId]?.readReceipts.length).toBe(1); expect(client.getState().threads[threadId]?.readReceipts[0]?.chatMessageId).toBe(messageId); expect(client.getState().threads[threadId]?.latestReadTime).toEqual(readOn); }); + + test('does not update latestReadTime for readReceiptReceived events from the current user', async () => { + const threadId = 'threadId1'; + const messageId = 'messageId1'; + const readOn = new Date(); + + const addedEvent: ReadReceiptReceivedEvent = { + threadId, + readOn, + recipient: { kind: 'communicationUser', communicationUserId: 'userId1' }, + sender: { kind: 'communicationUser', communicationUserId: 'userId1' }, + senderDisplayName: '', + chatMessageId: messageId + }; + + await client.triggerEvent('readReceiptReceived', addedEvent); + + expect(client.getState().threads[threadId]?.readReceipts.length).toBe(1); + expect(client.getState().threads[threadId]?.readReceipts[0]?.chatMessageId).toBe(messageId); + expect(client.getState().threads[threadId]?.latestReadTime).toEqual(new Date(0)); + }); }); describe('declarative chatClient unsubscribe', () => { From b0cd57ce8bbaa10cabccc86f2561c1e0ff787ff4 Mon Sep 17 00:00:00 2001 From: Pavan Kanukollu Date: Thu, 28 May 2026 14:25:24 -0700 Subject: [PATCH 2/2] Add fallback runner for CI workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 497580082af..db1cfc74bde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: # get matrix for ci-jobs get_matrix: name: Load CI Matrix Details - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} outputs: matrix: ${{ steps.get-matrix.outputs.matrix }} steps: @@ -43,7 +43,7 @@ jobs: build_packages: needs: get_matrix name: 'Build Packages (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} steps: @@ -124,7 +124,7 @@ jobs: jest-test-coverage: needs: [get_matrix, build_packages] name: 'Jest Test Coverage (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -221,7 +221,7 @@ jobs: name: Check if in progress feature can be removed separately (${{ matrix.flavor }}) strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} steps: # Checks-out your repository under $GITHUB_WORKSPACE so job can access it - uses: actions/checkout@v4 @@ -270,7 +270,7 @@ jobs: call_composite_automation_test: needs: get_matrix name: 'Call Composite automation test (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -337,7 +337,7 @@ jobs: chat_composite_automation_test: needs: get_matrix name: 'Chat Composite automation test (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -404,7 +404,7 @@ jobs: call_with_chat_composite_automation_test: needs: get_matrix name: 'Call With Chat Composite automation test (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -470,7 +470,7 @@ jobs: components_automation_test: needs: get_matrix name: 'Components automation test (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -528,7 +528,7 @@ jobs: name: Build Storybook v8 (${{ matrix.flavor }}) strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - name: Use Node.js @@ -557,7 +557,7 @@ jobs: build_calling_sample: needs: get_matrix name: 'Build Calling Sample (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} steps: @@ -589,7 +589,7 @@ jobs: build_chat_sample: needs: get_matrix name: 'Build Chat Sample (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} steps: @@ -621,7 +621,7 @@ jobs: build_call_with_chat_sample: needs: get_matrix name: 'Build CallWithChat Sample (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} steps: @@ -653,7 +653,7 @@ jobs: build_calling_stateful_samples: needs: get_matrix name: 'Build Calling Stateful Samples (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} strategy: matrix: ${{ fromJSON(needs.get_matrix.outputs.matrix) }} steps: @@ -679,7 +679,7 @@ jobs: build_static_html_composites_sample: needs: get_matrix name: 'Build And Test Static HTML Composites Sample (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -729,7 +729,7 @@ jobs: build_component_examples: needs: get_matrix name: 'Build And Test Component+Binding Examples (${{ matrix.flavor }})' - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write strategy: @@ -777,7 +777,7 @@ jobs: }) compare_base_bundle_stats: - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: pull-requests: write if: ${{ github.event_name == 'pull_request' && !startsWith(github.event.pull_request.base.ref, 'release/') }} @@ -849,7 +849,7 @@ jobs: fi echo "Bundle size diff for $app is below the threshold of $significantBundleSizeThreshold. All is good!" update_base_bundle_report: - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} name: Upload bundle size report to gist - ${{ matrix.app }} needs: [build_calling_sample, build_chat_sample, build_call_with_chat_sample] if: github.ref == 'refs/heads/main' @@ -877,7 +877,7 @@ jobs: file_path: report.json check_failure: - runs-on: ${{ vars.RUNS_ON }} + runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }} permissions: issues: write needs: