fix: work through LOTUS_BUGS.md audit items

- ExportRoomHistory: make addEvents() async, call decryptEventIfNeeded()
  before inspecting type/content so E2EE rooms export decrypted text
- UrlPreviewCard: remove Google S2 favicon (privacy leak); show
  generic Icons.Link instead — no third-party external calls
- Profile: add statusDirtyRef so server presence sync cannot clobber
  in-flight emoji insertions or keystrokes; cleared on save/clear
- useLocalMessageSearch: include m.sticker, m.poll.start, and
  org.matrix.msc3381.poll.start in encrypted room search; index poll
  question and answer bodies
- SeasonalEffect: z-index 9997 → 9999 so overlays render above
  animated chat backgrounds
- LOTUS_BUGS.md: mark all resolved, document remaining blocked items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 00:09:54 -04:00
parent 7f329e3b31
commit 6f9bdc4d50
6 changed files with 98 additions and 143 deletions
+11 -3
View File
@@ -4,6 +4,7 @@ import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
@@ -349,6 +350,9 @@ function ProfileStatus() {
const [statusMsg, setStatusMsg] = useState<string>(
presence?.status ?? localStorage.getItem(STATUS_MSG_KEY(userId)) ?? '',
);
// True while the user has unsaved local edits — prevents a server presence
// echo from overwriting what the user is currently typing/inserting.
const statusDirtyRef = useRef(false);
const [clearAfter, setClearAfter] = useState('0');
const [emojiAnchor, setEmojiAnchor] = useState<RectCords>();
@@ -359,10 +363,10 @@ function ProfileStatus() {
});
// Sync input when another device changes the status.
// Only update if the server actually has a value — ignore empty sync events
// caused by Synapse clearing status_msg on reconnect.
// Skipped while the user has unsaved local edits to avoid clobbering
// mid-flight input (e.g. an emoji being inserted).
useEffect(() => {
if (presence?.status) {
if (!statusDirtyRef.current && presence?.status) {
setStatusMsg(presence.status);
localStorage.setItem(STATUS_MSG_KEY(userId), presence.status);
}
@@ -399,17 +403,20 @@ function ProfileStatus() {
const saving = saveState.status === AsyncStatus.Loading;
const handleEmojiSelect = useCallback((unicode: string) => {
statusDirtyRef.current = true;
setStatusMsg((prev) => prev + unicode);
setEmojiAnchor(undefined);
}, []);
const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
statusDirtyRef.current = true;
setStatusMsg(evt.currentTarget.value);
};
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
evt.preventDefault();
if (saving) return;
statusDirtyRef.current = false;
const msg = statusMsg.trim();
saveStatus(msg).catch(() => undefined);
@@ -431,6 +438,7 @@ function ProfileStatus() {
};
const handleClear = () => {
statusDirtyRef.current = false;
setStatusMsg('');
localStorage.removeItem(STATUS_MSG_KEY(userId));
localStorage.removeItem(STATUS_EXPIRY_KEY(userId));