fix(wave-2): audit fixes — account-data races, search-cache wipe, export, media
Web fixes from the Wave-2 bug-hunt (findings in LOTUS_TODO): - F1 (security): wipe the decrypted-plaintext search index on SERVER-FORCED logout too (token expiry / remote sign-out) — only manual logout did before. F4: the delete no longer reports success while onblocked (waits, 3s cap). - M1/M2 (data-loss): useBookmarks + useUserNotes account-data writes are now serialized at MODULE scope (single queue + latestRef per client, echo-driven), fixing the cross-instance lost-update clobber (useBookmarks mounts per message row, so a per-instance queue was insufficient — caught in review). - M6: room-history export gets a 200-page cap + Cancel + unmount-abort + correct date-range early-break (raw paginated ts). M4: image compression skips PNG (was flattening transparency to black), bakes EXIF orientation via createImageBitmap, .jpg-renames, and falls back to the original on decode failure instead of dropping the file. M5: MediaGallery lightbox opens the right item (shared thumb guard). M8: audio speed survives async decrypt. - Desktop web wiring: D2 badge sums leaf rooms only (space double-count, like the favicon fix); D3 useTauriDnd re-hydrates from get_tray_dnd on mount; D5 updater has a terminal state. Reviewed; M7 reverted (past-time clamp is an intentional, tested contract). tsc/eslint/prettier clean, build OK, 678 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,18 @@ function getSenderName(room: Room, userId: string): string {
|
||||
return room.getMember(userId)?.name ?? userId.split(':')[0]?.slice(1) ?? userId;
|
||||
}
|
||||
|
||||
// Resolve the thumbnail/display MXC for an image/video event, mirroring the
|
||||
// grid's preference order (encrypted thumb > file > thumbnail_url > url). Both
|
||||
// the grid and the lightbox must use this so their positional indices stay in
|
||||
// lockstep — otherwise a tile skipped for lack of a thumb would shift the
|
||||
// lightbox and open the wrong media.
|
||||
function getThumbMxc(mEvent: MatrixEvent): string | undefined {
|
||||
const c = mEvent.getContent();
|
||||
const isEnc = !!c.file;
|
||||
const info: (IImageInfo & IThumbnailContent) | undefined = c.info;
|
||||
return isEnc ? (info?.thumbnail_file?.url ?? c.file?.url) : (info?.thumbnail_url ?? c.url);
|
||||
}
|
||||
|
||||
// ── Lightbox ──────────────────────────────────────────────────────────────────
|
||||
|
||||
type LightboxItem = {
|
||||
@@ -585,7 +597,10 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) {
|
||||
const lightboxItems: LightboxItem[] = events
|
||||
.filter((ev) => {
|
||||
const c = ev.getContent();
|
||||
return c.msgtype === MsgType.Image || c.msgtype === MsgType.Video;
|
||||
if (c.msgtype !== MsgType.Image && c.msgtype !== MsgType.Video) return false;
|
||||
// Match the grid's guard exactly: tiles without a thumb are not rendered,
|
||||
// so they must not occupy a lightbox slot either.
|
||||
return !!getThumbMxc(ev);
|
||||
})
|
||||
.map((ev) => {
|
||||
const c = ev.getContent();
|
||||
@@ -712,9 +727,7 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) {
|
||||
const info: (IImageInfo & IThumbnailContent) | undefined = c.info;
|
||||
|
||||
// Prefer thumbnail_file (encrypted thumb) > file > thumbnail_url > url
|
||||
const thumbMxc: string | undefined = isEnc
|
||||
? (info?.thumbnail_file?.url ?? c.file?.url)
|
||||
: (info?.thumbnail_url ?? c.url);
|
||||
const thumbMxc: string | undefined = getThumbMxc(mEvent);
|
||||
const thumbEnc: IEncryptedFile | undefined = isEnc
|
||||
? (info?.thumbnail_file ?? c.file)
|
||||
: undefined;
|
||||
|
||||
Reference in New Issue
Block a user