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:
@@ -1,5 +1,5 @@
|
||||
import React, { MouseEventHandler, forwardRef, useCallback, useRef, useState } from 'react';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import { MatrixClient, Room } from 'matrix-js-sdk';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@@ -263,27 +263,46 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
|
||||
}
|
||||
|
||||
// localStorage key for timed mute timers
|
||||
const MUTE_TIMERS_KEY = 'io.lotus.mute_timers';
|
||||
export const MUTE_TIMERS_KEY = 'io.lotus.mute_timers';
|
||||
|
||||
type MuteTimerEntry = { roomId: string; unmuteAt: number };
|
||||
// setTimeout's delay is a signed 32-bit int; larger values overflow and fire
|
||||
// immediately. Clamp long delays to this max (~24.8 days).
|
||||
export const MAX_MUTE_TIMEOUT_MS = 2_147_483_647;
|
||||
|
||||
function loadMuteTimers(): MuteTimerEntry[] {
|
||||
export type MuteTimerEntry = { roomId: string; unmuteAt: number };
|
||||
|
||||
export function loadMuteTimers(): MuteTimerEntry[] {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(MUTE_TIMERS_KEY) ?? '[]');
|
||||
const parsed = JSON.parse(localStorage.getItem(MUTE_TIMERS_KEY) ?? '[]');
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function saveMuteTimers(timers: MuteTimerEntry[]): void {
|
||||
export function saveMuteTimers(timers: MuteTimerEntry[]): void {
|
||||
localStorage.setItem(MUTE_TIMERS_KEY, JSON.stringify(timers));
|
||||
}
|
||||
|
||||
// Reverse a timed mute: restore the room's notification mode to Unset and drop
|
||||
// its persisted timer. Shared by the in-session timer and the boot-time restore.
|
||||
export async function unmuteRoom(mx: MatrixClient, roomId: string): Promise<void> {
|
||||
const { setRoomNotificationPreference } =
|
||||
await import('../../hooks/useRoomsNotificationPreferences');
|
||||
await setRoomNotificationPreference(
|
||||
mx,
|
||||
roomId,
|
||||
RoomNotificationMode.Unset,
|
||||
RoomNotificationMode.Mute,
|
||||
).catch(() => {});
|
||||
saveMuteTimers(loadMuteTimers().filter((e) => e.roomId !== roomId));
|
||||
}
|
||||
|
||||
function scheduleMuteTimer(roomId: string, durationMs: number, onUnmute: () => void): void {
|
||||
const unmuteAt = Date.now() + durationMs;
|
||||
const existing = loadMuteTimers().filter((e) => e.roomId !== roomId);
|
||||
saveMuteTimers([...existing, { roomId, unmuteAt }]);
|
||||
setTimeout(onUnmute, durationMs);
|
||||
setTimeout(onUnmute, Math.min(durationMs, MAX_MUTE_TIMEOUT_MS));
|
||||
}
|
||||
|
||||
type RoomNavItemMenuProps = {
|
||||
@@ -338,13 +357,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
||||
).catch(() => {});
|
||||
if (durationMs !== null) {
|
||||
scheduleMuteTimer(room.roomId, durationMs, () => {
|
||||
setRoomNotificationPreference(
|
||||
mx,
|
||||
room.roomId,
|
||||
RoomNotificationMode.Unset,
|
||||
RoomNotificationMode.Mute,
|
||||
).catch(() => {});
|
||||
saveMuteTimers(loadMuteTimers().filter((e) => e.roomId !== room.roomId));
|
||||
unmuteRoom(mx, room.roomId);
|
||||
});
|
||||
}
|
||||
requestClose();
|
||||
|
||||
Reference in New Issue
Block a user