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
@@ -63,15 +63,49 @@ export const useLocalMessageSearch = () => {
for (let i = 0; i < events.length; i += 1) {
const event = events[i];
if (event.getType() !== EventType.RoomMessage) continue;
const evType = event.getType();
const isMessage = evType === EventType.RoomMessage;
const isSticker = evType === 'm.sticker';
const isPoll =
evType === 'm.poll.start' || evType === 'org.matrix.msc3381.poll.start';
if (!isMessage && !isSticker && !isPoll) continue;
if (event.isDecryptionFailure()) continue;
if (event.isRedacted()) continue;
if (senderSet && !senderSet.has(event.getSender() ?? '')) continue;
// getContent() returns decrypted plaintext regardless of encryption
const content = event.getContent();
const body = (content.body as string | undefined) ?? '';
const formattedBody = (content.formatted_body as string | undefined) ?? '';
let body = '';
let formattedBody = '';
if (isMessage || isSticker) {
body = (content.body as string | undefined) ?? '';
formattedBody = (content.formatted_body as string | undefined) ?? '';
} else {
// Poll — index question text and all answer options
const poll = (content['m.poll'] ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content['org.matrix.msc3381.poll.start']) as any;
if (poll) {
const qBody =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(poll.question?.['m.text'] as Array<{ body: string }> | undefined)?.[0]?.body ??
(poll.question?.body as string | undefined) ??
'';
const answerBodies = ((poll.answers ?? []) as Array<Record<string, unknown>>)
.map(
(a) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
((a['m.text'] as Array<{ body: string }> | undefined)?.[0]?.body ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(a['org.matrix.msc3381.poll.answer'] as any)?.body ??
'') as string,
)
.join(' ');
body = `${qBody} ${answerBodies}`.trim();
}
}
if (
!body.toLowerCase().includes(termLower) &&