Skip to content

fix(calls): harden video join without pairwise WebRTC churn#2310

Merged
webguru-hypha merged 2 commits into
mainfrom
fix/call-join-hardening
Jun 9, 2026
Merged

fix(calls): harden video join without pairwise WebRTC churn#2310
webguru-hypha merged 2 commits into
mainfrom
fix/call-join-hardening

Conversation

@webguru-hypha

@webguru-hypha webguru-hypha commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Cherry-picks safe call improvements from fix(calls): stop auto leave/re-enter on remote media stall #2307 onto the production baseline (calls already work on main after server TURN fix).
  • Avoid duplicate camera/mic permission prompts: query navigator.permissions before enter() instead of pre-opening getUserMedia.
  • Voice preset bootstrap reuses applyConstraints on the existing mic track (no second capture stream) and preserves camera tracks still in new state after enter().
  • Adds hypha.group_call.ice_transport diagnostics (ICE state, candidate types, inbound RTP bytes) for support sessions.
  • Voice wave UI only animates when a live audio track exists.

Explicitly excluded from #2307: pairwise hangup/republish loops (restartAllPairwiseCallsForVideo, schedulePairwiseVideoResync, CallsChanged republish) — those broke preview video while production worked.

Context

Production calls work; PR #2307 added ~2,200 lines of pairwise WebRTC repair logic that destroyed ICE sessions mid-negotiation. This PR keeps production’s SDK-managed pairwise path and lands only the join-quality fixes.

Test plan

  • Two accounts, two browsers on preview — video + audio stable 2+ minutes
  • Video join shows one camera/mic permission prompt (not two)
  • Console: no pairwise_restart spam; optional ice_transport shows inboundVideoBytes growing
  • Toggle camera mid-call still uses requestLocalCameraAccess once
  • pnpm --filter @hypha-platform/core exec vitest run src/matrix/client/hooks/__tests__/call-camera-access.test.ts

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes

    • Improved camera permission detection when starting video
    • Enhanced media-stream handling for reliable audio/video track readiness
    • Refined voice-activity indicator logic for accurate audio status
  • New Features

    • Extended call diagnostics to capture richer WebRTC transport and connection metrics
    • Added UI flow for requesting/approving screenshare takeover during calls
    • Prefer main-content area for screen-share capture and updated browser picker constraints
    • Added localized takeover prompt strings (en/de/es/fr/pt)
  • Tests

    • New/updated tests covering camera-permission checks and screenshare capture behavior

Cherry-pick safe call improvements from #2307 onto production baseline:
query camera permission without a pre-enter getUserMedia probe, reuse
applyConstraints for voice preset instead of opening a second mic stream,
preserve camera tracks still in `new` state after enter(), add ICE transport
diagnostics, and only show voice waves when a live audio track exists.

Excludes pairwise hangup/republish loops that broke preview video while
production calls already work with server TURN fixed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds camera permission detection, richer ICE transport telemetry and diagnostics, media track readiness fixes, screenshare capture-root adjustments, and a screenshare takeover flow with UI, tests, and i18n updates.

Changes

WebRTC Diagnostics and Call Quality Improvements

Layer / File(s) Summary
Camera permission detection
packages/core/src/matrix/client/hooks/call-camera-access.ts, packages/core/src/matrix/client/hooks/__tests__/call-camera-access.test.ts, packages/core/src/matrix/client/hooks/use-space-group-call.ts
New isLocalCameraPermissionDenied() queries navigator.permissions.query({ name: 'camera' }); used during video call entry to set cameraAccessBlocked and force initial muted video when denied. Tests added for denied/granted/prompt outcomes.
ICE transport telemetry schema
packages/core/src/matrix/client/hooks/space-group-call-telemetry.ts
Extended SpaceGroupCallTelemetryEvent with optional fields: iceConnectionState, connectionState, localCandidateType, remoteCandidateType, pairState, inboundAudioBytes, inboundVideoBytes, and added hypha.group_call.ice_transport event variant.
ICE transport diagnostics implementation
packages/core/src/matrix/client/hooks/group-call-webrtc-diagnostics.ts
Implemented readIceTransportSummary to parse RTCPeerConnection.getStats() for transport/candidate/pair state and inbound byte counts; logIceTransportForPeerConnection emits telemetry and is scheduled alongside other diagnostics intervals.
Media track readiness handling
packages/core/src/matrix/client/hooks/use-space-group-call.ts, packages/epics/src/common/human-chat-panel/human-chat-panel-call-stage.tsx
Video/audio track selection now requires enabled and readyState of 'live' or 'new'. Microphone constraint updates prefer applying to existing live audio tracks before falling back to getUserMedia. Call tile voice-wave gating requires a live audio track.
Screenshare capture root & constraints
packages/core/src/matrix/client/hooks/screenshare-capture-exclusion.ts, packages/core/src/matrix/client/hooks/screenshare-capture.ts, packages/core/src/matrix/client/hooks/index.ts, tests
Adds HYPHA_SCREEN_SHARE_MAIN_CONTENT_ID and prefers the main-content element for Element Capture restrictions; buildDisplayMediaConstraints returns explicit browser-surface constraints (preferCurrentTab: false, selfBrowserSurface: 'exclude'). Tests updated to assert the new behavior.
Screenshare takeover logic in hook
packages/core/src/matrix/client/hooks/use-space-group-call.ts
Imports takeover resolution helpers, syncs incoming takeover events in a connected-only effect, triggers takeover request when remote owner differs, and adjusts approve/deny scheduling and batching behavior.
Screenshare takeover UI, overlays, panels, tests, and i18n
packages/epics/src/common/global-call-dock-overlay.tsx, packages/epics/src/common/human-right-panel.tsx, packages/epics/src/common/human-chat-panel/*, packages/i18n/src/messages/*
Wires HumanChatPanelScreenshareTakeoverDialog into global dock and right panel; updates screenshare menu text/disabled logic, tab-audio prompt hook behavior, manual-gate tests to expect takeover flows, and adds i18n strings for takeover prompt in multiple locales. Also sets the main-content ID on primary scroll containers.

Sequence Diagram(s)

sequenceDiagram
  participant Hook as use-space-group-call
  participant PermCheck as isLocalCameraPermissionDenied
  participant Nav as navigator.permissions
  participant GroupCall as GroupCall
  Hook->>PermCheck: Check camera permission
  PermCheck->>Nav: Query camera permission state
  Nav-->>PermCheck: Return permission state
  alt Permission Denied
    PermCheck-->>Hook: true
    Hook->>Hook: Set cameraAccessBlocked
    Hook->>Hook: Force initWithVideoMuted
  else Permission Granted or Prompt
    PermCheck-->>Hook: false
    Hook->>GroupCall: Enter with normal state
  end
Loading
sequenceDiagram
  participant Scheduler as attachGroupCallWebRtcDiagnostics
  participant Logger as logIceTransportForPeerConnection
  participant Reader as readIceTransportSummary
  participant Stats as RTCPeerConnection.getStats
  participant Telemetry as logSpaceGroupCallEvent
  Scheduler->>Logger: Per interval, each peer connection
  Logger->>Reader: Read ICE transport state
  Reader->>Stats: Get peer connection stats
  Stats-->>Reader: RTCStatsReport
  Reader-->>Logger: IceTransportSummary
  Logger->>Telemetry: Emit hypha.group_call.ice_transport
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • plitzenberger
  • alexprate
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title follows the conventional commits format with type(scope): description. It uses a valid type 'fix', includes scope 'calls', and has a clear, descriptive summary of the main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/call-join-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🔗 Custom preview URL: https://pr-2310.preview-app.hypha.earth

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/matrix/client/hooks/__tests__/call-camera-access.test.ts`:
- Around line 8-32: The tests for isLocalCameraPermissionDenied mutate the
global navigator.permissions and never restore it; capture the original
navigator.permissions reference before mutating (e.g., in the describe block or
beforeAll) and add an afterEach (or afterAll) that restores it using
Object.defineProperty or deletes the property if it was originally undefined so
other tests don't see the modified value; apply this to the tests that mock
navigator.permissions in the describe('isLocalCameraPermissionDenied') block so
each test resets global state.

In `@packages/core/src/matrix/client/hooks/group-call-webrtc-diagnostics.ts`:
- Around line 386-390: The interval currently calls logFrameSizes() and
logIceTransport() separately causing two getStats() calls per peer per tick;
change the implementation to collect a single RTCPeerConnection.getStats()
snapshot per peer/tick and pass that snapshot into both logFrameSizes and
logIceTransport (or refactor those helpers to accept a stats parameter) so each
peer uses the same time-aligned stats object for both diagnostics and avoids
duplicate getStats() calls.
- Around line 239-240: peerConnection.getStats() can reject during teardown and
currently runs as fire-and-forget, so wrap the call in a local try/catch inside
the function that calls it (the block invoking peerConnection.getStats() and
readIceTransportSummary) to prevent unhandled rejections; specifically, surround
the await peerConnection.getStats() call with try/catch, log or silently ignore
the error (using the existing diagnostics logger) and return early so
readIceTransportSummary is only called on success.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0bf5905d-b741-4d7a-a184-98d0178c0d0a

📥 Commits

Reviewing files that changed from the base of the PR and between a58ed6f and 7077dd8.

📒 Files selected for processing (6)
  • packages/core/src/matrix/client/hooks/__tests__/call-camera-access.test.ts
  • packages/core/src/matrix/client/hooks/call-camera-access.ts
  • packages/core/src/matrix/client/hooks/group-call-webrtc-diagnostics.ts
  • packages/core/src/matrix/client/hooks/space-group-call-telemetry.ts
  • packages/core/src/matrix/client/hooks/use-space-group-call.ts
  • packages/epics/src/common/human-chat-panel/human-chat-panel-call-stage.tsx

Comment on lines +8 to +32
describe('isLocalCameraPermissionDenied', () => {
it('returns true when the Permissions API reports denied', async () => {
Object.defineProperty(navigator, 'permissions', {
configurable: true,
value: {
query: vi.fn().mockResolvedValue({ state: 'denied' }),
},
});
await expect(isLocalCameraPermissionDenied()).resolves.toBe(true);
});

it('returns false when permission is granted or prompt', async () => {
Object.defineProperty(navigator, 'permissions', {
configurable: true,
value: {
query: vi
.fn()
.mockResolvedValueOnce({ state: 'granted' })
.mockResolvedValueOnce({ state: 'prompt' }),
},
});
await expect(isLocalCameraPermissionDenied()).resolves.toBe(false);
await expect(isLocalCameraPermissionDenied()).resolves.toBe(false);
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore navigator.permissions after each test to prevent global-state bleed.

These tests overwrite a global but never reset it, which can cause order-dependent failures as this file grows.

Suggested fix
-import { describe, expect, it, vi } from 'vitest';
+import { afterEach, describe, expect, it, vi } from 'vitest';
 ...
+const originalPermissions = navigator.permissions;
+
+afterEach(() => {
+  Object.defineProperty(navigator, 'permissions', {
+    configurable: true,
+    value: originalPermissions,
+  });
+});
+
 describe('isLocalCameraPermissionDenied', () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/matrix/client/hooks/__tests__/call-camera-access.test.ts`
around lines 8 - 32, The tests for isLocalCameraPermissionDenied mutate the
global navigator.permissions and never restore it; capture the original
navigator.permissions reference before mutating (e.g., in the describe block or
beforeAll) and add an afterEach (or afterAll) that restores it using
Object.defineProperty or deletes the property if it was originally undefined so
other tests don't see the modified value; apply this to the tests that mock
navigator.permissions in the describe('isLocalCameraPermissionDenied') block so
each test resets global state.

Comment on lines +239 to +240
const stats = await peerConnection.getStats();
const transport = readIceTransportSummary(peerConnection, stats);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Can RTCPeerConnection.getStats() reject after a connection is closed or during teardown, and what error behavior is documented across major browsers?

💡 Result:

The behavior of RTCPeerConnection.getStats when called on a closed PeerConnection varies by browser implementation, despite the W3C WebRTC Statistics API specification intending for stats to remain available even after a connection is closed [1][2]. According to the W3C specification, monitored objects should generally persist for the duration of the associated RTCPeerConnection, allowing their statistics to be accessed via getStats even after the connection has been closed [2]. However, implementation has historically been inconsistent across major browsers [1][3][4][5]. In Firefox, historically, calling getStats on a closed RTCPeerConnection resulted in a promise rejection with an InvalidStateError or similar DOMException [3][4]. Recent updates (circa April 2026) have moved toward allowing getStats on closed connections, mirroring the intended specification behavior and avoiding the previous rejection/hanging behavior [1][6][5]. In Chromium-based browsers, the behavior generally allows calling getStats on a closed connection, which typically returns a resolution with the final state of the statistics, rather than an error [5]. Due to these historical inconsistencies, many WebRTC applications use polyfills (such as those found in the adapter.js library) to normalize this behavior across browsers, often by guarding the call or manually resolving a promise with an empty map if the connection state is 'closed' [4]. Summary of error behavior: - Specification: Intends for stats to be accessible after closing; getStats should not reject solely because the connection is closed [2]. - Chromium: Generally allows the call and returns the final available statistics [5]. - Firefox: Historically rejected with an InvalidStateError, but recent versions have been updated to support calling getStats after closure without throwing [1][4][6]. If your application relies on consistent behavior, check the connectionState of the RTCPeerConnection before invoking getStats and handle the state accordingly [4].

Citations:


Handle RTCPeerConnection.getStats() rejections in fire-and-forget logging

RTCPeerConnection.getStats() has historically been inconsistent across browsers when the connection is closed/tearing down (e.g., Firefox has rejected with InvalidStateError in past versions). Since callers fire-and-forget with void, a rejection can surface as unhandled/noisy async errors—wrap the getStats() call with a local try/catch.

Proposed fix
 async function logIceTransportForPeerConnection(options: {
   roomId: string;
   groupCallId: string;
   userId: string | null;
   peerConnection: RTCPeerConnection;
 }): Promise<void> {
   const { roomId, groupCallId, userId, peerConnection } = options;
-  const stats = await peerConnection.getStats();
-  const transport = readIceTransportSummary(peerConnection, stats);
-  logSpaceGroupCallEvent({
-    name: 'hypha.group_call.ice_transport',
-    roomId,
-    groupCallId,
-    remoteUserId: userId ?? undefined,
-    iceConnectionState: transport.iceConnectionState,
-    iceGatherState: transport.iceGatheringState,
-    connectionState: transport.connectionState,
-    localCandidateType: transport.localCandidateType,
-    remoteCandidateType: transport.remoteCandidateType,
-    pairState: transport.pairState,
-    inboundAudioBytes: transport.inboundAudioBytes,
-    inboundVideoBytes: transport.inboundVideoBytes,
-  });
+  try {
+    const stats = await peerConnection.getStats();
+    const transport = readIceTransportSummary(peerConnection, stats);
+    logSpaceGroupCallEvent({
+      name: 'hypha.group_call.ice_transport',
+      roomId,
+      groupCallId,
+      remoteUserId: userId ?? undefined,
+      iceConnectionState: transport.iceConnectionState,
+      iceGatherState: transport.iceGatheringState,
+      connectionState: transport.connectionState,
+      localCandidateType: transport.localCandidateType,
+      remoteCandidateType: transport.remoteCandidateType,
+      pairState: transport.pairState,
+      inboundAudioBytes: transport.inboundAudioBytes,
+      inboundVideoBytes: transport.inboundVideoBytes,
+    });
+  } catch {
+    // Peer connection may close between interval ticks; ignore transient diagnostics failure.
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/matrix/client/hooks/group-call-webrtc-diagnostics.ts`
around lines 239 - 240, peerConnection.getStats() can reject during teardown and
currently runs as fire-and-forget, so wrap the call in a local try/catch inside
the function that calls it (the block invoking peerConnection.getStats() and
readIceTransportSummary) to prevent unhandled rejections; specifically, surround
the await peerConnection.getStats() call with try/catch, log or silently ignore
the error (using the existing diagnostics logger) and return early so
readIceTransportSummary is only called on success.

Comment on lines +386 to +390
logIceTransport();
frameLogInterval = setInterval(() => {
logFrameSizes();
logIceTransport();
}, inboundRtpFrameLogIntervalMs);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Avoid double getStats() calls per peer per interval tick.

In the support-debug interval path, logFrameSizes() and logIceTransport() each call getStats() for the same peer. Reusing one stats snapshot per peer/tick would cut diagnostics overhead and keep measurements time-aligned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/matrix/client/hooks/group-call-webrtc-diagnostics.ts`
around lines 386 - 390, The interval currently calls logFrameSizes() and
logIceTransport() separately causing two getStats() calls per peer per tick;
change the implementation to collect a single RTCPeerConnection.getStats()
snapshot per peer/tick and pass that snapshot into both logFrameSizes and
logIceTransport (or refactor those helpers to accept a stats parameter) so each
peer uses the same time-aligned stats object for both diagnostics and avoids
duplicate getStats() calls.

Crop tab capture to the main content column so call UI is not mirrored,
and wire the takeover request/approve flow so a second participant can share.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/epics/src/common/human-chat-panel/__tests__/call-regression-manual-gates.test.ts`:
- Around line 128-130: The current assertions check for exact spacing in the
string "sendScreenshareTakeoverEvent(\n              'request'" which is
brittle; update the two expectations that reference that exact formatting (the
assertions against the variable source in the test file) to be
whitespace-tolerant by either using a RegExp that ignores whitespace (e.g.,
/\bsendScreenshareTakeoverEvent\(\s*'request'/) or normalizing source before
asserting (e.g., collapse whitespace with .replace(/\s+/g,' ') and assert the
normalized string contains "sendScreenshareTakeoverEvent('request'"). Apply the
same change to both occurrences of the assertion in the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 195df230-33d2-4a39-a2b7-e47c6df36d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 7077dd8 and ca9ed55.

📒 Files selected for processing (18)
  • packages/core/src/matrix/client/hooks/__tests__/screenshare-capture-exclusion.test.ts
  • packages/core/src/matrix/client/hooks/__tests__/screenshare-capture.test.ts
  • packages/core/src/matrix/client/hooks/index.ts
  • packages/core/src/matrix/client/hooks/screenshare-capture-exclusion.ts
  • packages/core/src/matrix/client/hooks/screenshare-capture.ts
  • packages/core/src/matrix/client/hooks/use-space-group-call.ts
  • packages/epics/src/common/global-call-dock-overlay.tsx
  • packages/epics/src/common/human-chat-panel/__tests__/call-regression-manual-gates.test.ts
  • packages/epics/src/common/human-chat-panel/human-chat-panel-call-screenshare-menu.tsx
  • packages/epics/src/common/human-chat-panel/use-screenshare-tab-audio-prompt.tsx
  • packages/epics/src/common/human-right-panel.tsx
  • packages/epics/src/common/panel-main-column-scroll-bridge.tsx
  • packages/epics/src/common/panel-scroll-inset.tsx
  • packages/i18n/src/messages/de.json
  • packages/i18n/src/messages/en.json
  • packages/i18n/src/messages/es.json
  • packages/i18n/src/messages/fr.json
  • packages/i18n/src/messages/pt.json

Comment on lines +128 to 130
expect(source).toContain(
"sendScreenshareTakeoverEvent(\n 'request'",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make source-scan assertions whitespace-tolerant to avoid false CI failures.

Line 129 and Line 146 assert exact formatting/spacing of source text. Non-functional reformatting can break this test even when takeover behavior is unchanged.

Suggested update
-    expect(source).toContain(
-      "sendScreenshareTakeoverEvent(\n              'request'",
-    );
+    expect(source).toMatch(/sendScreenshareTakeoverEvent\(\s*'request'/);

-    expect(menu).toContain('const shareStartDisabled = disabled;');
+    expect(menu).toMatch(/\bshareStartDisabled\s*=\s*disabled\b/);

Also applies to: 146-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/epics/src/common/human-chat-panel/__tests__/call-regression-manual-gates.test.ts`
around lines 128 - 130, The current assertions check for exact spacing in the
string "sendScreenshareTakeoverEvent(\n              'request'" which is
brittle; update the two expectations that reference that exact formatting (the
assertions against the variable source in the test file) to be
whitespace-tolerant by either using a RegExp that ignores whitespace (e.g.,
/\bsendScreenshareTakeoverEvent\(\s*'request'/) or normalizing source before
asserting (e.g., collapse whitespace with .replace(/\s+/g,' ') and assert the
normalized string contains "sendScreenshareTakeoverEvent('request'"). Apply the
same change to both occurrences of the assertion in the file.

@webguru-hypha webguru-hypha merged commit 8dd2f3a into main Jun 9, 2026
10 checks passed
@webguru-hypha webguru-hypha deleted the fix/call-join-hardening branch June 9, 2026 15:10
webguru-hypha added a commit that referenced this pull request Jun 9, 2026
fix(calls): apply CodeRabbit diagnostics and test hygiene from #2310
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant