fix: emoji picker works + silence 429 presence rate-limit errors

Emoji bug root cause: EmojiBoard wraps itself in a FocusTrap with
clickOutsideDeactivates:true. When the picker was rendered inside
Input's 'after' prop, the FocusTrap treated clicks on the emoji items
as outside-clicks and deactivated (calling requestClose) before the
onEmojiSelect callback fired. Fixed by moving the emoji PopOut to be
a direct sibling of Input in the form row instead of nesting it inside
Input.after — matching the established pattern used in MessageEditor.

429 rate-limit: mx.setPresence() calls in handleClear and the
auto-clear timer effect had no rejection handling, causing unhandled
promise rejections logged to Sentry when Synapse rate-limits presence
updates. Added .catch(() => undefined) to both call sites. Sentry
issue JAVASCRIPT-REACT-E resolved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 15:20:42 -04:00
parent 90cb70c128
commit 60cbfec951
+16 -18
View File
@@ -365,17 +365,16 @@ function ProfileStatus() {
useEffect(() => { useEffect(() => {
if (!expiryTs) return undefined; if (!expiryTs) return undefined;
const remaining = expiryTs - Date.now(); const remaining = expiryTs - Date.now();
if (remaining <= 0) { const clearStatus = () => {
localStorage.removeItem(STATUS_EXPIRY_KEY(userId)); localStorage.removeItem(STATUS_EXPIRY_KEY(userId));
setExpiryTs(0); setExpiryTs(0);
mx.setPresence({ presence: 'online', status_msg: '' }); mx.setPresence({ presence: 'online', status_msg: '' }).catch(() => undefined);
};
if (remaining <= 0) {
clearStatus();
return undefined; return undefined;
} }
const timer = window.setTimeout(() => { const timer = window.setTimeout(clearStatus, remaining);
localStorage.removeItem(STATUS_EXPIRY_KEY(userId));
setExpiryTs(0);
mx.setPresence({ presence: 'online', status_msg: '' });
}, remaining);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [expiryTs, userId, mx]); }, [expiryTs, userId, mx]);
@@ -421,7 +420,7 @@ function ProfileStatus() {
setStatusMsg(''); setStatusMsg('');
localStorage.removeItem(STATUS_EXPIRY_KEY(userId)); localStorage.removeItem(STATUS_EXPIRY_KEY(userId));
setExpiryTs(0); setExpiryTs(0);
mx.setPresence({ presence: 'online', status_msg: '' }); mx.setPresence({ presence: 'online', status_msg: '' }).catch(() => undefined);
}; };
const hasChanges = statusMsg !== (presence?.status ?? ''); const hasChanges = statusMsg !== (presence?.status ?? '');
@@ -440,7 +439,7 @@ function ProfileStatus() {
} }
> >
<Box direction="Column" grow="Yes" gap="100"> <Box direction="Column" grow="Yes" gap="100">
<Box as="form" onSubmit={handleSubmit} gap="200" aria-disabled={saving}> <Box as="form" onSubmit={handleSubmit} gap="200" alignItems="Center" aria-disabled={saving}>
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
<Input <Input
name="statusMsgInput" name="statusMsgInput"
@@ -450,9 +449,9 @@ function ProfileStatus() {
placeholder="What's on your mind?" placeholder="What's on your mind?"
variant="Secondary" variant="Secondary"
radii="300" radii="300"
style={{ paddingRight: config.space.S200 }}
readOnly={saving} readOnly={saving}
after={ />
</Box>
<PopOut <PopOut
anchor={emojiAnchor} anchor={emojiAnchor}
position="Top" position="Top"
@@ -470,9 +469,11 @@ function ProfileStatus() {
> >
<IconButton <IconButton
type="button" type="button"
size="300" size="400"
radii="300" radii="400"
variant="Secondary" variant="Surface"
fill="Soft"
outlined
aria-label="Insert emoji" aria-label="Insert emoji"
aria-expanded={!!emojiAnchor} aria-expanded={!!emojiAnchor}
aria-haspopup="dialog" aria-haspopup="dialog"
@@ -481,12 +482,9 @@ function ProfileStatus() {
setEmojiAnchor((prev) => (prev ? undefined : rect)); setEmojiAnchor((prev) => (prev ? undefined : rect));
}} }}
> >
<Icon src={Icons.Smile} size="100" /> <Icon src={Icons.Smile} size="400" />
</IconButton> </IconButton>
</PopOut> </PopOut>
}
/>
</Box>
<Button <Button
size="400" size="400"
variant={hasChanges ? 'Success' : 'Secondary'} variant={hasChanges ? 'Success' : 'Secondary'}