fix(calls,matrix): address review findings from agent code review

- CallEmbed watchdog now SELF-HEALS: a genuine ready/joined signal arriving
  after the 25s timeout clears the error and notifies subscribers with
  undefined, so a slow-but-successful EC load no longer strands the user on
  the recovery screen over a live call. Listener dispatch wrapped in try/catch.
- ringtones: synth notes route through a per-session master gain; stop() ramps
  it to 0 so the ring is silenced instantly on answer instead of letting the
  last scheduled phrase ring out over call audio.
- IncomingCallBanner: ping fires exactly once per incoming call (guarded by
  refEventId) instead of re-pinging when ringtone settings change mid-banner.
- focusCameraParticipant: try multiple tile selectors (EC labels vary by
  version), defer the tile click past EC's async spotlight layout switch
  (rAF x2), and dev-warn when no tile matches so testers get signal.
- uploadContent: a cancelled upload (mx.cancelUpload -> AbortError) is no
  longer treated as retryable — previously the retry loop could resurrect an
  upload the user just cancelled. Also retry on 408.
- addRoomIdToMDirect/removeRoomIdFromMDirect: guard against a corrupt m.direct
  whose values aren't arrays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 18:15:51 -04:00
parent fc8eb70617
commit bc85cd4984
5 changed files with 131 additions and 31 deletions
+7 -2
View File
@@ -289,11 +289,16 @@ function IncomingCallBanner({ dm, info, onIgnore, onAnswer, onReject }: Incoming
);
// Single soft ping (non-looping) on arrival, respecting the chosen ringtone
// + volume. We intentionally do NOT loop here — the user is mid-call.
// + volume. We intentionally do NOT loop here — the user is mid-call — and we
// ping exactly once per incoming call, not again if the user happens to tweak
// ringtone settings while the banner is showing.
const pingedRef = useRef<string | undefined>(undefined);
useEffect(() => {
if (info.notificationType !== 'ring') return;
if (pingedRef.current === info.refEventId) return;
pingedRef.current = info.refEventId;
previewRingtone(ringtoneId, Math.max(0, Math.min(1, ringtoneVolume / 100)));
}, [info.notificationType, ringtoneId, ringtoneVolume]);
}, [info.notificationType, info.refEventId, ringtoneId, ringtoneVolume]);
useEffect(() => {
const remaining = info.senderTs + info.lifetime - Date.now();