diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index 78a0deece..cf2aade5c 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -363,12 +363,17 @@ function ProfileStatus() { const [presenceStatus] = useSetting(settingsAtom, 'presenceStatus'); const [hidePresence] = useSetting(settingsAtom, 'hidePresence'); - const [statusMsg, setStatusMsg] = useState( - presence?.status ?? localStorage.getItem(STATUS_MSG_KEY(userId)) ?? '', - ); + const initialStatus = presence?.status ?? localStorage.getItem(STATUS_MSG_KEY(userId)) ?? ''; + const [statusMsg, setStatusMsg] = useState(initialStatus); // True while the user has unsaved local edits — prevents a server presence // echo from overwriting what the user is currently typing/inserting. const statusDirtyRef = useRef(false); + // The last remote status we synced into the input. Presence heartbeats fire + // every few seconds carrying the SAME status; the sync effect must only react + // when the remote value actually changes, otherwise a repeated heartbeat can + // overwrite an unsaved local edit (e.g. an emoji just inserted) the instant + // the dirty flag is out of sync. Seeded with the value the input started on. + const lastSyncedRemoteRef = useRef(initialStatus); // The value we most recently applied (with the apply time). A presence // heartbeat can echo the PREVIOUS status just after we save the new one; that // stale echo would otherwise revert the input. We ignore non-matching echoes @@ -390,6 +395,10 @@ function ProfileStatus() { // wipe the saved status on every invisible toggle. if (presence.presence === Presence.Offline) return; const remoteStatus = presence.status ?? ''; + // Only act on an actual remote change. Repeated heartbeats carrying the same + // status are ignored so they can never clobber an unsaved local edit. + if (remoteStatus === lastSyncedRemoteRef.current) return; + lastSyncedRemoteRef.current = remoteStatus; const pending = pendingAppliedRef.current; if (pending) { if (remoteStatus === pending.value) {