diff --git a/LOTUS_BUGS.md b/LOTUS_BUGS.md index 258a1030b..7e50cda46 100644 --- a/LOTUS_BUGS.md +++ b/LOTUS_BUGS.md @@ -119,7 +119,7 @@ This document tracks identified bugs, edge cases, and architectural discrepancie | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------- | :----- | | State Sync | Fire-and-forget network call to set offline presence during `pagehide` event may not complete reliably, potentially causing UI drift in presence status. | `cinny/src/app/hooks/usePresenceUpdater.ts` | OPEN | | State Sync | Fire-and-forget network call `setPresence().catch(...)` suppresses errors, meaning the app may falsely assume presence update success. | `cinny/src/app/hooks/usePresenceUpdater.ts` | OPEN | -| Memory Leak | Decrypted Media Memory Leak (Gallery & Lightbox) due to missing virtualization and blob revocation. | `cinny/src/app/features/room/MediaGallery.tsx` | OPEN | +| Memory Leak | Decrypted Media Memory Leak (Gallery & Lightbox) due to missing virtualization and blob revocation. | `cinny/src/app/features/room/MediaGallery.tsx` | PARTIALLY FIXED ⚠️ UNTESTED — Blob revocation was already correct; added `enabled` param to `useDecryptedMediaUrl` and `useNearViewport(300px)` to each `GalleryTile` to gate decryption until near-viewport, reducing burst on pagination. True virtualization (windowing) deferred — requires significant refactor. | | Data Persistence | Scheduled Messages are ephemeral (lost on refresh) due to fragile `localStorage` parsing. | `cinny/src/app/state/scheduledMessages.ts` | FIXED — now uses `atomWithStorage` + `createJSONStorage` (Jotai's built-in persistence with error-safe JSON parsing) | | Memory Leak | Potential memory leak due to uncleaned `handleMouseMove` listener in `usePan`. | `cinny/src/app/hooks/usePan.ts` | FALSE POSITIVE — `usePan` already uses `attachedRef` to track listeners and cleans them up in an unmount `useEffect`. No change needed. | | Asset Optimization | Large unoptimized media asset (213KB) found in `public/res`. | `public/res/Lotus.png` | OPEN | diff --git a/src/app/features/room/MediaGallery.tsx b/src/app/features/room/MediaGallery.tsx index 71ab8182f..a7d299988 100644 --- a/src/app/features/room/MediaGallery.tsx +++ b/src/app/features/room/MediaGallery.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useNearViewport } from '../../hooks/useNearViewport'; import { Box, Button, @@ -45,11 +46,13 @@ function useDecryptedMediaUrl( encInfo: IEncryptedFile | undefined, useAuthentication: boolean, mimeType?: string, + enabled = true, ): DecryptState { const [state, setState] = useState({ status: 'loading' }); const prevBlobUrl = useRef(null); useEffect(() => { + if (!enabled) return undefined; if (!mxcUrl) { setState({ status: 'error' }); return; @@ -84,7 +87,7 @@ function useDecryptedMediaUrl( return () => { cancelled = true; }; - }, [mx, mxcUrl, encInfo, useAuthentication, mimeType]); + }, [mx, mxcUrl, encInfo, useAuthentication, mimeType, enabled]); useEffect( () => () => { @@ -367,12 +370,15 @@ function GalleryTile({ onClick: () => void; }) { const mx = useMatrixClient(); - const media = useDecryptedMediaUrl(mx, mxcUrl, encInfo, useAuthentication, mimeType); + const tileRef = useRef(null); + const nearViewport = useNearViewport(tileRef, 300); + const media = useDecryptedMediaUrl(mx, mxcUrl, encInfo, useAuthentication, mimeType, nearViewport); const [hovered, setHovered] = useState(false); const relDate = formatRelativeDate(ts); return (