fix(state): correct invite re-notify, status clear sync, soundboard resync (DP2/DP3/DP5)

- Invites: seed the notify baseline with the invite count at mount instead
  of 0, so a warm reload (allInvitesAtom populates synchronously from cache
  while SYNCING) no longer re-fires the toast+sound for pre-existing invites.
  Only a genuine increase notifies.
- Status: the cross-device sync effect gated on a truthy presence.status, so
  a remote CLEAR never reset the input or localStorage[STATUS_MSG_KEY] and
  usePresenceUpdater.readStatus() re-sent the stale status. Now mirror an
  empty status as a clear (skipping offline/invisible, which carries an empty
  status_msg by design).
- Soundboard: useState initializers ran once and never recomputed when the
  room/rooms arg changed. Add a resync effect keyed on the arg while keeping
  the live state-event update path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 22:29:47 -04:00
co-authored by Claude Opus 4.8
parent 8eb961b682
commit db86432644
3 changed files with 44 additions and 8 deletions
+19 -6
View File
@@ -52,7 +52,7 @@ import { ModalWide } from '../../../styles/Modal.css';
import { createUploadAtom, UploadSuccess } from '../../../state/upload';
import { CompactUploadCardRenderer } from '../../../components/upload-card';
import { useCapabilities } from '../../../hooks/useCapabilities';
import { useUserPresence } from '../../../hooks/useUserPresence';
import { Presence, useUserPresence } from '../../../hooks/useUserPresence';
import { ProfileDecoration } from './ProfileDecoration';
import { EmojiBoard } from '../../../components/emoji-board';
@@ -365,13 +365,26 @@ function ProfileStatus() {
// Sync input when another device changes the status.
// Skipped while the user has unsaved local edits to avoid clobbering
// mid-flight input (e.g. an emoji being inserted).
// mid-flight input (e.g. an emoji being inserted), and while presence data has
// not loaded yet (presence === undefined is "no info", NOT a clear).
useEffect(() => {
if (!statusDirtyRef.current && presence?.status) {
setStatusMsg(presence.status);
localStorage.setItem(STATUS_MSG_KEY(userId), presence.status);
if (statusDirtyRef.current || !presence) return;
// An offline/invisible presence carries an empty status_msg by design (see
// usePresenceUpdater.setOffline), so ignore it — reading it as a clear would
// wipe the saved status on every invisible toggle.
if (presence.presence === Presence.Offline) return;
const remoteStatus = presence.status ?? '';
if (remoteStatus) {
setStatusMsg(remoteStatus);
localStorage.setItem(STATUS_MSG_KEY(userId), remoteStatus);
} else {
// Another device CLEARED the status. Mirror it locally AND drop the stored
// value, otherwise usePresenceUpdater.readStatus() re-sends the stale
// message on the next presence heartbeat.
setStatusMsg('');
localStorage.removeItem(STATUS_MSG_KEY(userId));
}
}, [presence?.status, userId]);
}, [presence, userId]);
const [saveState, saveStatus] = useAsyncCallback(
useCallback(