fix(errors): surface previously-silent async failures (DP1/DP4/DP6)

Several fire-and-forget promises failed silently:
- RoomInput slash-command exe() rejections reset the editor as if they
  succeeded; now caught and shown via an error toast.
- Favourite / low-priority room-tag toggles (setRoomTag/deleteRoomTag)
  swallowed rejections; now surfaced via the same error toast.
- Call-decline sendEvent(RTCDecline) was uncaught; now logged best-effort
  while the local UI still dismisses.

Adds a shared createErrorToast builder mirroring createDownloadToast.
/kick and /ban route through rateLimitedActions, whose to() helper
swallows non-429 errors, so those two can still resolve on failure — noted
in code; the top-level exe() catch covers everything that does reject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 22:29:39 -04:00
parent 2614e6b92d
commit 8eb961b682
5 changed files with 66 additions and 10 deletions
+17 -7
View File
@@ -26,7 +26,7 @@ import {
} from 'folds';
import { useFocusWithin, useHover } from 'react-aria';
import FocusTrap from 'focus-trap-react';
import { useAtom, useAtomValue } from 'jotai';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import dayjs from 'dayjs';
import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday';
@@ -68,6 +68,7 @@ import {
import { useCallMembers, useCallSession } from '../../hooks/useCall';
import { useCallEmbed, useCallStart } from '../../hooks/useCallEmbed';
import { callChatAtom } from '../../state/callEmbed';
import { createErrorToast, toastQueueAtom } from '../../state/toast';
import { useCallPreferencesAtom } from '../../state/hooks/callPreferences';
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
import { livekitSupport } from '../../hooks/useLivekitSupport';
@@ -324,6 +325,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const canInvite = permissions.action('invite', mx.getSafeUserId());
const openRoomSettings = useOpenRoomSettings();
const space = useSpaceOptionally();
const setToast = useSetAtom(toastQueueAtom);
const [invitePrompt, setInvitePrompt] = useState(false);
const [muteMenuAnchor, setMuteMenuAnchor] = useState<RectCords>();
@@ -332,23 +334,31 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const isFavorite = !!room.tags?.['m.favourite'];
const isLowPriority = !!room.tags?.['m.lowpriority'];
// Surface a room-tag write failure instead of letting the toggle fail silently.
const notifyTagFailure = (err: unknown) => {
console.error('Failed to update room tag:', err);
setToast(
createErrorToast('Could not update this room. Please try again.', Icons.Warning, 'Failed'),
);
};
const handleToggleFavorite = () => {
if (isFavorite) {
mx.deleteRoomTag(room.roomId, 'm.favourite');
mx.deleteRoomTag(room.roomId, 'm.favourite').catch(notifyTagFailure);
} else {
// Favourite and low-priority are mutually exclusive.
if (isLowPriority) mx.deleteRoomTag(room.roomId, 'm.lowpriority');
mx.setRoomTag(room.roomId, 'm.favourite', { order: 0.5 });
if (isLowPriority) mx.deleteRoomTag(room.roomId, 'm.lowpriority').catch(notifyTagFailure);
mx.setRoomTag(room.roomId, 'm.favourite', { order: 0.5 }).catch(notifyTagFailure);
}
requestClose();
};
const handleToggleLowPriority = () => {
if (isLowPriority) {
mx.deleteRoomTag(room.roomId, 'm.lowpriority');
mx.deleteRoomTag(room.roomId, 'm.lowpriority').catch(notifyTagFailure);
} else {
if (isFavorite) mx.deleteRoomTag(room.roomId, 'm.favourite');
mx.setRoomTag(room.roomId, 'm.lowpriority', { order: 0.5 });
if (isFavorite) mx.deleteRoomTag(room.roomId, 'm.favourite').catch(notifyTagFailure);
mx.setRoomTag(room.roomId, 'm.lowpriority', { order: 0.5 }).catch(notifyTagFailure);
}
requestClose();
};