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:
@@ -365,17 +365,16 @@ function ProfileStatus() {
|
||||
useEffect(() => {
|
||||
if (!expiryTs) return undefined;
|
||||
const remaining = expiryTs - Date.now();
|
||||
if (remaining <= 0) {
|
||||
const clearStatus = () => {
|
||||
localStorage.removeItem(STATUS_EXPIRY_KEY(userId));
|
||||
setExpiryTs(0);
|
||||
mx.setPresence({ presence: 'online', status_msg: '' });
|
||||
mx.setPresence({ presence: 'online', status_msg: '' }).catch(() => undefined);
|
||||
};
|
||||
if (remaining <= 0) {
|
||||
clearStatus();
|
||||
return undefined;
|
||||
}
|
||||
const timer = window.setTimeout(() => {
|
||||
localStorage.removeItem(STATUS_EXPIRY_KEY(userId));
|
||||
setExpiryTs(0);
|
||||
mx.setPresence({ presence: 'online', status_msg: '' });
|
||||
}, remaining);
|
||||
const timer = window.setTimeout(clearStatus, remaining);
|
||||
return () => clearTimeout(timer);
|
||||
}, [expiryTs, userId, mx]);
|
||||
|
||||
@@ -421,7 +420,7 @@ function ProfileStatus() {
|
||||
setStatusMsg('');
|
||||
localStorage.removeItem(STATUS_EXPIRY_KEY(userId));
|
||||
setExpiryTs(0);
|
||||
mx.setPresence({ presence: 'online', status_msg: '' });
|
||||
mx.setPresence({ presence: 'online', status_msg: '' }).catch(() => undefined);
|
||||
};
|
||||
|
||||
const hasChanges = statusMsg !== (presence?.status ?? '');
|
||||
@@ -440,7 +439,7 @@ function ProfileStatus() {
|
||||
}
|
||||
>
|
||||
<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">
|
||||
<Input
|
||||
name="statusMsgInput"
|
||||
@@ -450,43 +449,42 @@ function ProfileStatus() {
|
||||
placeholder="What's on your mind?"
|
||||
variant="Secondary"
|
||||
radii="300"
|
||||
style={{ paddingRight: config.space.S200 }}
|
||||
readOnly={saving}
|
||||
after={
|
||||
<PopOut
|
||||
anchor={emojiAnchor}
|
||||
position="Top"
|
||||
align="End"
|
||||
content={
|
||||
<Suspense fallback={<Spinner size="100" />}>
|
||||
<EmojiBoard
|
||||
imagePackRooms={[]}
|
||||
returnFocusOnDeactivate={false}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
requestClose={() => setEmojiAnchor(undefined)}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
type="button"
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Secondary"
|
||||
aria-label="Insert emoji"
|
||||
aria-expanded={!!emojiAnchor}
|
||||
aria-haspopup="dialog"
|
||||
onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const rect = evt.currentTarget.getBoundingClientRect();
|
||||
setEmojiAnchor((prev) => (prev ? undefined : rect));
|
||||
}}
|
||||
>
|
||||
<Icon src={Icons.Smile} size="100" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<PopOut
|
||||
anchor={emojiAnchor}
|
||||
position="Top"
|
||||
align="End"
|
||||
content={
|
||||
<Suspense fallback={<Spinner size="100" />}>
|
||||
<EmojiBoard
|
||||
imagePackRooms={[]}
|
||||
returnFocusOnDeactivate={false}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
requestClose={() => setEmojiAnchor(undefined)}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
type="button"
|
||||
size="400"
|
||||
radii="400"
|
||||
variant="Surface"
|
||||
fill="Soft"
|
||||
outlined
|
||||
aria-label="Insert emoji"
|
||||
aria-expanded={!!emojiAnchor}
|
||||
aria-haspopup="dialog"
|
||||
onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const rect = evt.currentTarget.getBoundingClientRect();
|
||||
setEmojiAnchor((prev) => (prev ? undefined : rect));
|
||||
}}
|
||||
>
|
||||
<Icon src={Icons.Smile} size="400" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
<Button
|
||||
size="400"
|
||||
variant={hasChanges ? 'Success' : 'Secondary'}
|
||||
|
||||
Reference in New Issue
Block a user