fix(wave-3): audit fixes — ACL guards, presence, moderation, theming perf

Wave-3 bug-hunt fixes (findings in LOTUS_TODO), reviewed + gate-green:
- 🔴 ACL editor [H1–H4]: block saving an empty allow-list (was a one-click
  federation brick), warn on self-ban (case-insensitive glob match of
  mx.getDomain() vs allow/deny), accept real globs (1.2.3.*, *.evil.*), and
  gate Save behind a confirm dialog.
- 🔴 [P1] room context menu no longer acts on the wrong room after a live
  reorder (key by roomId, not list index). 🔴 [P2] status writes no longer
  force presence to online over Invisible/DND (shared presenceStateFromSetting).
- 🟠 [P3] timed mutes restored on boot; [P4] custom-status auto-clear now fires
  (always-mounted StatusExpiryMonitor); [P5] timezone also PUT to the m.tz
  profile field so it's visible to others; [H6] RoomInsights single-pass
  min/max (was Math.min(...spread) stack overflow); [H7/H8] mod-log labels.
- 🟡 [P6/P7] favorites collapse+filter, [P8] charCount reset, [P9] DM preview
  refresh on decrypt; theming [T-P1] lazy decorations, [T-P2] drop the redundant
  always-on body animation, [T-P4] live useReducedMotion, [T-P5] decoration key.
- NATIVE-CINNY LAW: notification presets + Powers permissions use folds icons.

DEFERRED: [H5] invite-QR is fetched from api.qrserver.com (third-party leak);
local generation needs a bundled QR lib (not added). tsc/eslint/prettier clean,
build OK, 677 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 21:40:07 -04:00
parent 41149db685
commit dcd8201e16
22 changed files with 527 additions and 116 deletions
+26 -18
View File
@@ -275,15 +275,27 @@ export function Home() {
return { favoriteRooms: favs, otherRooms: others };
}, [mx, rooms]);
const sortedFavoriteRooms = useMemo(
() =>
Array.from(favoriteRooms).sort(
closedCategories.has(FAVORITES_CATEGORY_ID)
? factoryRoomIdByActivity(mx)
: factoryRoomIdByAtoZ(mx),
),
[mx, favoriteRooms, closedCategories],
);
const sortedFavoriteRooms = useMemo(() => {
const isClosed = closedCategories.has(FAVORITES_CATEGORY_ID);
const items = Array.from(favoriteRooms).sort(
isClosed ? factoryRoomIdByActivity(mx) : factoryRoomIdByAtoZ(mx),
);
if (isClosed) {
return items.filter((rId) => roomsWithUnreadSet.has(rId) || rId === selectedRoomId);
}
return items;
}, [mx, favoriteRooms, closedCategories, roomsWithUnreadSet, selectedRoomId]);
const filteredFavoriteRooms = useMemo(() => {
if (!filterQuery.trim()) return sortedFavoriteRooms;
const query = filterQuery.toLowerCase();
const localNames = getLocalRoomNamesContent(mx);
return sortedFavoriteRooms.filter((rId) => {
const localName = localNames.rooms[rId];
const matrixName = mx.getRoom(rId)?.name ?? '';
return (localName ?? matrixName).toLowerCase().includes(query);
});
}, [mx, sortedFavoriteRooms, filterQuery]);
const sortedRooms = useMemo(() => {
const isClosed = closedCategories.has(DEFAULT_CATEGORY_ID);
@@ -324,7 +336,7 @@ export function Home() {
}, [mx, sortedRooms, filterQuery]);
const favVirtualizer = useVirtualizer({
count: sortedFavoriteRooms.length,
count: filteredFavoriteRooms.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 38,
overscan: 10,
@@ -453,7 +465,7 @@ export function Home() {
/>
</Box>
</NavCategory>
{sortedFavoriteRooms.length > 0 && (
{favoriteRooms.length > 0 && (
<NavCategory>
<NavCategoryHeader>
<RoomNavCategoryButton
@@ -466,13 +478,13 @@ export function Home() {
</NavCategoryHeader>
<div style={{ position: 'relative', height: favVirtualizer.getTotalSize() }}>
{favVirtualizer.getVirtualItems().map((vItem) => {
const roomId = sortedFavoriteRooms[vItem.index];
const roomId = filteredFavoriteRooms[vItem.index];
const room = mx.getRoom(roomId);
if (!room) return null;
return (
<VirtualTile
virtualItem={vItem}
key={vItem.index}
key={roomId}
ref={favVirtualizer.measureElement}
>
<RoomNavItem
@@ -611,11 +623,7 @@ export function Home() {
const selected = selectedRoomId === roomId;
return (
<VirtualTile
virtualItem={vItem}
key={vItem.index}
ref={virtualizer.measureElement}
>
<VirtualTile virtualItem={vItem} key={roomId} ref={virtualizer.measureElement}>
<RoomNavItem
room={room}
selected={selected}