From baa12823f7956fb0f2e3387ae0e068f76d42c1af Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Thu, 18 Jun 2026 15:32:17 -0400 Subject: [PATCH] fix: exponential backoff for 429 retries, cover thumbnails, 2 more modal dialogs - matrix.ts: rateLimitedActions fallback delay now uses capped exponential backoff (min(1000 * 2^n, 30s)) instead of flat 3000ms when server omits Retry-After; server header still takes precedence - RenderMessageContent: add objectFit:cover + 100% fill to video thumbnail so thumbnails fill their container without letterboxing (P5-6) - CreateRoomModal, CreateSpaceModal: apply useModalStyle(480) for fullscreen on mobile - LOTUS_BUGS: mark usePan memory leak + httpStatus check as FALSE POSITIVE; mark rateLimitedActions backoff as FIXED Co-Authored-By: Claude Sonnet 4.6 --- LOTUS_BUGS.md | 6 +++--- src/app/components/RenderMessageContent.tsx | 8 +++++++- src/app/features/create-room/CreateRoomModal.tsx | 4 +++- src/app/features/create-space/CreateSpaceModal.tsx | 4 +++- src/app/utils/matrix.ts | 3 ++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/LOTUS_BUGS.md b/LOTUS_BUGS.md index 14ae6251d..8531076aa 100644 --- a/LOTUS_BUGS.md +++ b/LOTUS_BUGS.md @@ -121,16 +121,16 @@ This document tracks identified bugs, edge cases, and architectural discrepancie | 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 | | 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` | OPEN | +| 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 | | Data Persistence | Non-atomic `localStorage` updates in session management can lead to inconsistent state. | `cinny/src/app/state/sessions.ts` | OPEN | | Data Persistence | Lack of cross-tab synchronization for `localStorage` updates in session management risks race conditions. | `cinny/src/app/state/sessions.ts` | OPEN | | Network Resilience | `uploadContent` lacks retry logic, failing immediately upon network error. | `cinny/src/app/utils/matrix.ts` | OPEN | -| Network Resilience | `rateLimitedActions` uses basic retry logic without exponential backoff, which may exacerbate 429 issues. | `cinny/src/app/utils/matrix.ts` | OPEN | +| Network Resilience | `rateLimitedActions` uses basic retry logic without exponential backoff, which may exacerbate 429 issues. | `cinny/src/app/utils/matrix.ts` | FIXED — fallback delay now uses capped exponential backoff (`min(1000 * 2^retryCount, 30_000)ms`) when server doesn't send `Retry-After`; server header still takes precedence via `getRetryAfterMs()`. | | Matrix Event Robustness | `useMatrixEventRenderer` handles unknown events gracefully by returning `null`, which may hide potentially important unrendered data. | `cinny/src/app/hooks/useMatrixEventRenderer.ts` | OPEN | | Data Contract | `MatrixError` instantiation with `UploadResponse` might be brittle. | `cinny/src/app/utils/matrix.ts` | OPEN | | Type Safety | `addRoomIdToMDirect` uses `as any` cast for `AccountDataEvent.Direct`, bypassing type contract validation. | `cinny/src/app/utils/matrix.ts` | OPEN | -| Robustness | `rateLimitedActions` relies on `MatrixError.httpStatus` which might not exist on all error variants. | `cinny/src/app/utils/matrix.ts` | OPEN | +| Robustness | `rateLimitedActions` relies on `MatrixError.httpStatus` which might not exist on all error variants. | `cinny/src/app/utils/matrix.ts` | FALSE POSITIVE — `MatrixError.httpStatus` is defined as `readonly httpStatus?: number` in `matrix-js-sdk/lib/http-api/errors.d.ts`. It is optional (not on all instances) but the `?.` optional chain already guards against undefined. No change needed. | | Type Contract | Custom types in `cinny/src/types/matrix` mirror SDK types instead of using them, risking drift and contract mismatches. | `cinny/src/types/matrix/` | OPEN | ## 🏗️ Architectural & Hygiene Audit diff --git a/src/app/components/RenderMessageContent.tsx b/src/app/components/RenderMessageContent.tsx index 1b2b7e234..5033d26ff 100644 --- a/src/app/components/RenderMessageContent.tsx +++ b/src/app/components/RenderMessageContent.tsx @@ -232,7 +232,13 @@ export function RenderMessageContent({ ( - {body} + {body} )} /> ) diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx index c175dd6cc..aed6a1117 100644 --- a/src/app/features/create-room/CreateRoomModal.tsx +++ b/src/app/features/create-room/CreateRoomModal.tsx @@ -24,6 +24,7 @@ import { import { CreateRoomModalState } from '../../state/createRoomModal'; import { stopPropagation } from '../../utils/keyboard'; import { CreateRoomType } from '../../components/create-room/types'; +import { useModalStyle } from '../../hooks/useModalStyle'; type CreateRoomModalProps = { state: CreateRoomModalState; @@ -31,6 +32,7 @@ type CreateRoomModalProps = { function CreateRoomModal({ state }: CreateRoomModalProps) { const { spaceId, type } = state; const closeDialog = useCloseCreateRoomModal(); + const modalStyle = useModalStyle(480); const allJoinedRooms = useAllJoinedRoomsSet(); const getRoom = useGetRoom(allJoinedRooms); @@ -48,7 +50,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) { escapeDeactivates: stopPropagation, }} > - +
- +
( return; } - const waitMS = err.getRetryAfterMs() ?? 3000; + // Respect server Retry-After header; fall back to capped exponential backoff. + const waitMS = err.getRetryAfterMs() ?? Math.min(1000 * 2 ** retryCount, 30_000); actionInterval = waitMS * 1.5; await sleepForMs(waitMS); retryCount += 1;