Reject sends an MSC4310 decline that points at the wrong event: the caller keeps "Calling…" for 90 s, and the declined call is logged as "Missed call" #313

Closed
opened 2026-10-05 01:08:37 -04:00 by jared · 0 comments
Owner

Where the decline points. handleReject (src/app/components/CallEmbedProvider.tsx, called from IncomingCall line 284 and IncomingCallBanner line 456 with info.refEventId) sends:

mx.sendEvent(roomId, EventType.RTCDecline, { 'm.relates_to': { rel_type: 'm.reference', event_id: info.refEventId } })

refEventId is what the notification references, i.e. the caller's call.member event. MSC4310, and the SDK type docs (IRTCDeclineContent: "Sent as a standard m.reference relation to an org.matrix.msc4075.rtc.notification event"), require the decline to reference the rtc.notification event itself. Element Call matches on exactly that (CallNotificationLifecycle.ts:148-153: event.getRelation()?.event_id === notificationEvent.event_id).

Effect, measured (Bob calls, you press Reject):

Bob's host:  "Declined your call" toast (Lotus's own room-level check, line 515), gone after a few s
Bob's call:  "Calling…" at +3 s, +15 s, +45 s: still waiting for the full 90 s lifetime
  • The caller sits in an empty "Calling…" call for up to 90 s after being told "Declined". Element Call never takes its decline auto-leave path.
  • Calls from other Matrix clients (Element X/Web) never see a Lotus decline.
  • The reverse also breaks. The "declined on another of my devices" dismissal (#161, line ~640) compares related === refEventId, so a decline sent by Element X on your phone, which correctly references the notification, doesn't stop Lotus ringing on your desktop.

Declined calls are logged as missed. The timeline renderer (RoomTimeline.tsx:2025, "[Gitea #162]") says it shows "Missed call" for a ring "that we neither answered nor declined", but it never checks for a decline. Measured: the call above shows "Missed call from Bob Builder" live and after a reload. A call you actively rejected isn't missed. Show nothing, or "Declined call", and skip it when a decline from you relates to the ring.

Fix:

  • Send the decline with event_id: <notification event id>. Keep the notification's id in IncomingCallInfo; refEventId is still useful for matching the call.
  • In the #161 dismissal and the caller-side toast, accept declines that reference either id, for compatibility with Lotus builds already deployed.
  • In the missed-call renderer, return null when there is an RTCDecline from mx.getUserId() relating to this notification.
  • Test: Reject → the caller's Element Call frame leaves "Calling…" within a few seconds, and no "Missed call" row appears.

Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

**Where the decline points.** `handleReject` (src/app/components/CallEmbedProvider.tsx, called from `IncomingCall` line 284 and `IncomingCallBanner` line 456 with `info.refEventId`) sends: ```ts mx.sendEvent(roomId, EventType.RTCDecline, { 'm.relates_to': { rel_type: 'm.reference', event_id: info.refEventId } }) ``` `refEventId` is what the **notification** references, i.e. the caller's `call.member` event. MSC4310, and the SDK type docs (`IRTCDeclineContent`: "Sent as a standard m.reference relation to an `org.matrix.msc4075.rtc.notification` event"), require the decline to reference the **`rtc.notification` event itself**. Element Call matches on exactly that (`CallNotificationLifecycle.ts:148-153`: `event.getRelation()?.event_id === notificationEvent.event_id`). **Effect, measured (Bob calls, you press Reject):** ``` Bob's host: "Declined your call" toast (Lotus's own room-level check, line 515), gone after a few s Bob's call: "Calling…" at +3 s, +15 s, +45 s: still waiting for the full 90 s lifetime ``` - The caller sits in an empty "Calling…" call for up to 90 s after being told "Declined". Element Call never takes its `decline` auto-leave path. - Calls from other Matrix clients (Element X/Web) never see a Lotus decline. - The reverse also breaks. The "declined on another of my devices" dismissal (#161, line ~640) compares `related === refEventId`, so a decline sent by Element X on your phone, which correctly references the notification, doesn't stop Lotus ringing on your desktop. **Declined calls are logged as missed.** The timeline renderer (`RoomTimeline.tsx:2025`, "[Gitea #162]") says it shows "Missed call" for a ring "that we neither answered nor declined", but it never checks for a decline. Measured: the call above shows **"Missed call from Bob Builder"** live and after a reload. A call you actively rejected isn't missed. Show nothing, or "Declined call", and skip it when a decline from you relates to the ring. **Fix:** - Send the decline with `event_id: <notification event id>`. Keep the notification's id in `IncomingCallInfo`; `refEventId` is still useful for matching the call. - In the #161 dismissal and the caller-side toast, accept declines that reference either id, for compatibility with Lotus builds already deployed. - In the missed-call renderer, return null when there is an `RTCDecline` from `mx.getUserId()` relating to this notification. - Test: Reject → the caller's Element Call frame leaves "Calling…" within a few seconds, and no "Missed call" row appears. --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the bugpriority: mediumarea: calls labels 2026-10-05 01:08:37 -04:00
jared closed this issue 2026-10-17 22:13:55 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#313