Compare commits

..
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 85a2abc46a fix(logout): clear the call-volume list and settings-sync stamp (#347)
CI / Build & Quality Checks (pull_request) Successful in 3m24s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 8s
CI / Playwright smoke (e2e) (pull_request) Successful in 13m33s
Logout left lotus-per-user-volume (Element Call's per-person volume map:
whom the previous user called and turned up/down; same origin on the web)
and settings-sync-meta:<userId> (a key naming the account) in
localStorage. clearPlaintextCaches now removes both; device preferences
(`settings`) are kept as before.

Verified: after joining/leaving a call and logging out via Settings, both
keys are gone (unit test added).

Fixes #347

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-12 15:28:41 -04:00
7 changed files with 46 additions and 51 deletions
@@ -48,7 +48,6 @@ import {
addRecentForwardTarget,
} from '../../../state/recentForwardTargets';
import { buildForwardContent, buildPlaintextAttachmentContent } from './forwardContent';
import { useLocalRoomNames } from '../../../hooks/useRoomMeta';
type RoomRowProps = {
room: Room;
@@ -59,7 +58,6 @@ type RoomRowProps = {
sending: boolean;
};
function RoomRow({ room, dm, useAuthentication, selected, onToggle, sending }: RoomRowProps) {
const localNames = useLocalRoomNames();
const mx = useMatrixClient();
const avatarMxc = room.getMxcAvatarUrl();
const avatarUrl = avatarMxc
@@ -99,7 +97,7 @@ function RoomRow({ room, dm, useAuthentication, selected, onToggle, sending }: R
>
<Box direction="Column">
<Text size="T300" truncate>
{localNames[room.roomId] ?? room.name}
{room.name}
</Text>
{dm && (
<Text size="T200" priority="300" truncate>
@@ -214,7 +212,6 @@ function RecentChip({
onToggle: () => void;
sending: boolean;
}) {
const localNames = useLocalRoomNames();
const mx = useMatrixClient();
const avatarMxc = room.getMxcAvatarUrl();
const avatarUrl = avatarMxc
@@ -243,7 +240,7 @@ function RecentChip({
}
>
<Text size="B300" truncate style={{ maxWidth: toRem(120) }}>
{localNames[room.roomId] ?? room.name}
{room.name}
</Text>
</Chip>
);
@@ -255,7 +252,6 @@ type Props = {
};
export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const localNames = useLocalRoomNames();
const mx = useMatrixClient();
const modalStyle = useModalStyle(400);
const directs = useAtomValue(mDirectAtom);
@@ -300,10 +296,8 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const filtered = useMemo(() => {
if (!query) return allRooms;
const q = query.toLowerCase();
return allRooms.filter((r) =>
[r.name, localNames[r.roomId]].some((nm) => nm?.toLowerCase().includes(q)),
);
}, [allRooms, query, localNames]);
return allRooms.filter((r) => r.name.toLowerCase().includes(q));
}, [allRooms, query]);
// Resolve recent target ids to still-joined rooms (drop ones left/gone).
const recentRooms = useMemo(
+5 -11
View File
@@ -37,7 +37,6 @@ import {
useAsyncSearch,
UseAsyncSearchOptions,
} from '../../hooks/useAsyncSearch';
import { useLocalRoomNames } from '../../hooks/useRoomMeta';
import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom';
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
import {
@@ -158,20 +157,17 @@ export function Search({ requestClose }: SearchProps) {
const topActiveRooms = useTopActiveRooms(searchRoomType, rooms, directs, spaces);
const targetRooms = useSearchTargetRooms(searchRoomType, rooms, directs, spaces);
const localNames = useLocalRoomNames();
const getTargetStr: SearchItemStrGetter<string> = useCallback(
(roomId: string) => {
const roomName = getRoom(roomId)?.name ?? roomId;
// [Gitea #336] Match the user's own "Rename for me" name as well.
const names = localNames[roomId] ? [localNames[roomId], roomName] : [roomName];
if (mDirects.has(roomId)) {
const targetUserId = getDmUserId(roomId, getRoom, mx.getSafeUserId());
const targetUsername = targetUserId && getMxIdLocalPart(targetUserId);
if (targetUsername) return [...names, targetUsername];
if (targetUsername) return [roomName, targetUsername];
}
return names.length === 1 ? names[0] : names;
return roomName;
},
[getRoom, mDirects, mx, localNames],
[getRoom, mDirects, mx],
);
const [result, search, resetSearch] = useAsyncSearch(targetRooms, getTargetStr, SEARCH_OPTIONS);
@@ -392,10 +388,8 @@ export function Search({ requestClose }: SearchProps) {
<Box grow="Yes" alignItems="Center" gap="100">
<Text size="T400" truncate>
{queryHighlighRegex
? highlightText(queryHighlighRegex, [
localNames[room.roomId] ?? room.name,
])
: (localNames[room.roomId] ?? room.name)}
? highlightText(queryHighlighRegex, [room.name])
: room.name}
</Text>
{dmUsername && (
<Text as="span" size="T200" priority="300" truncate>
-16
View File
@@ -102,22 +102,6 @@ export const useLocalRoomName = (room: Room): string => {
return localNames[room.roomId] ?? name;
};
/**
* [Gitea #336] The whole "Rename for me" map, for lists that show many rooms
* (quick switcher, forward/share pickers) so they use the name the user sees in
* the sidebar, not the server name.
*/
export const useLocalRoomNames = (): Readonly<Record<string, string>> => {
const mx = useMatrixClient();
return roomNamesStore.useValue(mx);
};
/** Non-hook read for notification paths (toast / OS notification titles). */
export const getLocalRoomName = (
mx: ReturnType<typeof useMatrixClient>,
roomId: string,
): string | undefined => roomNamesStore.getLatest(mx)[roomId];
export const useHasLocalRoomName = (roomId: string): boolean => {
const mx = useMatrixClient();
const localNames = roomNamesStore.useValue(mx);
+2 -4
View File
@@ -14,7 +14,6 @@ import {
ThreadEvent,
} from 'matrix-js-sdk';
import { useNotificationsQuiet } from '../../hooks/useNotificationsQuiet';
import { getLocalRoomName } from '../../hooks/useRoomMeta';
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import NotificationSound from '../../../../public/sound/notification.ogg';
import InviteSound from '../../../../public/sound/invite.ogg';
@@ -640,8 +639,7 @@ function MessageNotifications() {
const avatarMxc =
room.getAvatarFallbackMember()?.getMxcAvatarUrl() ?? room.getMxcAvatarUrl();
notify({
// [Gitea #336] the user's own "Rename for me" name when they set one
roomName: getLocalRoomName(mx, room.roomId) ?? room.name ?? 'Unknown',
roomName: room.name ?? 'Unknown',
roomAvatar: avatarMxc
? (mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined)
: undefined,
@@ -810,7 +808,7 @@ function ReminderMonitor() {
if (!firedRef.current.has(key)) {
firedRef.current.add(key);
const room = mx.getRoom(r.roomId);
const roomName = getLocalRoomName(mx, r.roomId) ?? room?.name ?? 'Unknown Room';
const roomName = room?.name ?? 'Unknown Room';
// Reminders for E2EE rooms carry no text (account data is server-
// readable), so resolve the body from the local timeline at fire time.
const localBody = room?.findEventById(r.eventId)?.getContent()?.body;
+3 -10
View File
@@ -38,7 +38,6 @@ import {
import { BlockType } from '../../../components/editor';
import { bytesToSize } from '../../../utils/common';
import { getHomePath } from '../../pathUtils';
import { useLocalRoomNames } from '../../../hooks/useRoomMeta';
type Shared = { payload: SharePayload; files: File[] };
@@ -48,7 +47,6 @@ type Shared = { payload: SharePayload; files: File[] };
* as the draft) — the user still presses Send.
*/
export function Share() {
const localNames = useLocalRoomNames();
const mx = useMatrixClient();
const navigate = useNavigate();
const { navigateRoom } = useRoomNavigate();
@@ -71,12 +69,8 @@ export function Share() {
.filter((r) => r.getMyMembership() === 'join' && !r.isSpaceRoom())
.sort((a, b) => (b.getLastActiveTimestamp() ?? 0) - (a.getLastActiveTimestamp() ?? 0));
const q = query.trim().toLowerCase();
return q
? all.filter((r) =>
[r.name, localNames[r.roomId]].some((nm) => nm?.toLowerCase().includes(q)),
)
: all;
}, [mx, query, localNames]);
return q ? all.filter((r) => r.name.toLowerCase().includes(q)) : all;
}, [mx, query]);
// Written straight into the room's draft atoms so its composer shows the
// upload board + text as soon as it mounts.
@@ -216,7 +210,6 @@ function ShareRoomRow({
disabled: boolean;
onPick: (room: Room) => void;
}) {
const localNames = useLocalRoomNames();
const mx = useMatrixClient();
const avatarMxc = room.getMxcAvatarUrl();
const avatarUrl = avatarMxc
@@ -244,7 +237,7 @@ function ShareRoomRow({
>
<Box direction="Column">
<Text size="T300" truncate>
{localNames[room.roomId] ?? room.name}
{room.name}
</Text>
{dm && (
<Text size="T200" priority="300" truncate>
+11
View File
@@ -83,3 +83,14 @@ test('clearPlaintextCaches wipes drafts (#41) and the status message (#204) but
assert.ok(!removed.includes('cinny_session'), 'session key not this module’s concern');
assert.ok(store.has('cinny_session'));
});
test('clearPlaintextCaches removes the call-volume list and the settings-sync stamp (#347)', () => {
store.clear();
store.set('lotus-per-user-volume', '{"@bob:server":0.3}');
store.set('settings-sync-meta:@me:server', '{"lastSyncedAt":1}');
store.set('settings', '{}');
clearPlaintextCaches('@me:server');
assert.equal(store.has('lotus-per-user-volume'), false);
assert.equal(store.has('settings-sync-meta:@me:server'), false);
assert.equal(store.has('settings'), true, 'device preferences are kept');
});
+21
View File
@@ -47,6 +47,8 @@ const clearMsgDrafts = (): void => {
* - `cinny_recent_gifs_v1` / `cinny_recent_stickers_v1` — media the user sent
* - `navToActivePath<userId>` — per-space last-visited room paths (needs userId)
* - `lotus_outbox_v1` — decrypted content of messages not yet sent ([Gitea #112])
* - `lotus-per-user-volume`, `settings-sync-meta:<userId>` — whom you adjusted in
* calls, and a key naming the account ([Gitea #347])
* - `draft-msg-*` — unsent composer drafts (decrypted message text, unscoped by
* user — see [Gitea #41]; previously deliberately preserved across logout
* (N98), which let the next account on this device see/send a prior user's
@@ -92,6 +94,24 @@ const clearStatusMessage = (): void => {
});
};
/**
* [Gitea #347] Per-person residue that isn't message text but still says whom
* the previous user dealt with: Element Call's per-user volume memory (same
* origin on the web — element-call `lotusVolumeMemory.ts`) and the settings-sync
* stamp, whose key names the account.
*/
export const CALL_VOLUME_KEY = 'lotus-per-user-volume';
const clearCallVolumesAndSyncMeta = (userId?: string): void => {
const keys = [CALL_VOLUME_KEY, ...(userId ? [`settings-sync-meta:${userId}`] : [])];
keys.forEach((key) => {
try {
localStorage.removeItem(key);
} catch {
// best-effort
}
});
};
export const clearPlaintextCaches = (userId?: string): void => {
clearTranslationCache();
clearScheduledMessages();
@@ -103,5 +123,6 @@ export const clearPlaintextCaches = (userId?: string): void => {
clearOutbox();
clearCallSession();
clearStatusMessage();
clearCallVolumesAndSyncMeta(userId);
if (userId) clearNavToActivePathStore(userId);
};