fix(rooms): serialize per-room rename writes so back-to-back renames survive

Local room names did a read-modify-write of io.lotus.room_names against
the SDK's local cache, which is stale until the /sync echo, so a second
rename issued before the first echoed overwrote it. Route through
createAccountDataListStore like user notes. Unit-tested with a client
whose setAccountData does not update the local store.

Fixes #17

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-12 14:48:35 -04:00
co-authored by Claude Opus 5
parent 2344c8273e
commit 7c52027afb
3 changed files with 148 additions and 69 deletions
+7 -14
View File
@@ -42,7 +42,6 @@ import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../componen
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
import { getDirectRoomAvatarUrl, getRoomAvatarUrl, getStateEvent } from '../../utils/room';
import { setAccountData } from '../../utils/accountData';
import { nameInitials } from '../../utils/common';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomUnread } from '../../state/hooks/unread';
@@ -74,8 +73,8 @@ import { getRoomCreatorsForRoomId, useRoomCreators } from '../../hooks/useRoomCr
import { getRoomPermissionsAPI, useRoomPermissions } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt';
import {
LOCAL_ROOM_NAMES_KEY,
getLocalRoomNamesContent,
setLocalRoomName,
useHasLocalRoomName,
useLocalRoomName,
} from '../../hooks/useRoomMeta';
@@ -138,22 +137,16 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
const handleSave = useCallback(() => {
const newName = inputRef.current?.value.trim() ?? '';
if (newName.length > 255) return;
const existing = getLocalRoomNamesContent(mx);
if (newName === '') {
const { [room.roomId]: _removed, ...rest } = existing.rooms;
setAccountData(mx, LOCAL_ROOM_NAMES_KEY, { rooms: rest });
} else {
setAccountData(mx, LOCAL_ROOM_NAMES_KEY, {
rooms: { ...existing.rooms, [room.roomId]: newName },
});
}
// Routed through the shared write queue (setLocalRoomName) instead of a
// read-modify-write against the SDK's local cache, which stays stale
// until the /sync echo lands and would otherwise let a second rename
// clobber a still-in-flight first rename.
setLocalRoomName(mx, room.roomId, newName);
onClose();
}, [mx, room.roomId, onClose]);
const handleClear = useCallback(() => {
const existing = getLocalRoomNamesContent(mx);
const { [room.roomId]: _removed, ...rest } = existing.rooms;
setAccountData(mx, LOCAL_ROOM_NAMES_KEY, { rooms: rest });
setLocalRoomName(mx, room.roomId, '');
onClose();
}, [mx, room.roomId, onClose]);