feat(calls): EC iframe load watchdog + recovery UI; avatar decorations on call tiles

- CallEmbed: 25s load watchdog that fails fast on iframe error / preparing-error /
  timeout instead of hanging on a permanent spinner; additive onLoadError API,
  cleared on ready/capabilities/joined.
- CallView: user-visible "call failed to load" overlay with Retry/Leave (folds +
  tokens) via a new useCallLoadError hook.
- CallMemberCard: wrap the participant avatar in AvatarDecoration so decorations
  render in the call roster (the tile rendered UserAvatar bare while member lists
  already wrapped it).

Addresses LOTUS_BUGS item 3 (avatar decorations in calls) and EC iframe failure monitoring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 08:22:01 -04:00
parent d2946c00ce
commit 0394fce929
4 changed files with 163 additions and 13 deletions
+21
View File
@@ -3,6 +3,7 @@ import { MatrixClient, Room } from 'matrix-js-sdk';
import { useSetAtom } from 'jotai';
import {
CallEmbed,
CallLoadErrorReason,
ElementCallThemeKind,
ElementWidgetActions,
useClientWidgetApiEvent,
@@ -156,6 +157,26 @@ export const useCallJoined = (embed?: CallEmbed): boolean => {
return joined;
};
/**
* Surfaces a load failure (watchdog timeout or iframe error) from the embedded
* Element Call iframe so the UI can show a recovery affordance instead of an
* indefinite "Loading..." spinner.
*/
export const useCallLoadError = (embed?: CallEmbed): CallLoadErrorReason | undefined => {
const [error, setError] = useState<CallLoadErrorReason | undefined>(() => embed?.loadFailed);
useEffect(() => {
if (!embed) {
setError(undefined);
return undefined;
}
setError(embed.loadFailed);
return embed.onLoadError((reason) => setError(reason));
}, [embed]);
return error;
};
export const useCallHangupEvent = (embed: CallEmbed, callback: () => void) => {
useClientWidgetApiEvent(embed.call, ElementWidgetActions.HangupCall, callback);
useClientWidgetApiEvent(embed.call, ElementWidgetActions.Close, callback);