diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md index 25dd58610..b5ada32e2 100644 --- a/LOTUS_TODO.md +++ b/LOTUS_TODO.md @@ -91,6 +91,50 @@ Agent-surveyed findings, each **verified against the code before fixing**, then - [DEFERRED] **SEC-5 β€” embeds' `allow-popups-to-escape-sandbox`** β€” informational; main-app hijack already prevented (no `allow-top-navigation`), and popups are arguably needed for "open in provider." Revisit with per-provider verification if dropped. - **KE-1 preventive (`navigator.storage.persist()`)** is **already implemented** (`initClient` β†’ `requestPersistentStorage()` + `src/index.tsx` boot). The rest of the KE cluster stays under **Encryption / E2EE** below (needs live capture). +### πŸ” Feature bug hunt (2026-07, 5-agent, LOTUS_FEATURES surface) β€” open findings + +Per-slice bug hunt (5 agents: theming Β· calls Β· messaging Β· threads/presence/UX Β· rooms/mod/notif/infra/desktop), each **verified against current code** (already-fixed items not re-flagged; the heavily-audited hot paths came back clean). Residual findings below. `[live]` / `[desktop]` = needs a real call / the desktop app to confirm. + +**Embeds / URL previews** + +- [ ] **[Med] Desktop (Tauri) CSP `frame-src` is missing `store.steampowered.com`, `www.mixcloud.com`, `widget.deezer.com`** β†’ the Steam widget (shipped) + new Mixcloud/Deezer embeds are silently blocked (blank iframe) **in the desktop app** (`cinny-desktop/src-tauri/tauri.conf.json`). Web is fine (`frame-src 'self' https:`). Add the three hosts (`frame-src` only β€” no `connect-src`; these don't do a client oEmbed fetch). **Verified.** +- [ ] **[Low]** `searchCache.ts` encrypted-search index has no size/count cap β€” unbounded on-disk growth (mitigated by the manual "Clear cached index" + logout wipe). +- [ ] **[Low]** `MsgTypeRenderers.tsx` `MLocation` OSM permalink uses raw `geo:` lat/lon substrings, not the validated floats β€” harmless (URL context, malformed input only). + +**Voice / video calls** + +- [ ] **[Med]** `DenoiseTester.play()` (Settings β†’ Calls A/B model test) leaks the denoise model node β€” calls `ctx.close()` but never `denoise.dispose()` (inconsistent with `stopLive`, which disposes) β†’ leaks the DeepFilterNet/DTLN worker/WASM per press. `DenoiseTester.tsx:267-300`. +- [ ] **[Med] [live]** PiP auto-spotlight never released on return to the call room β€” the release branch sits inside the `if (!pipMode) return` guard, so screenshareβ†’PiPβ†’back leaves spotlight forced on and `pipAutoSpotlightRef` stuck `true`. `CallEmbedProvider.tsx:733-744`. +- [ ] **[Low]** DenoiseTester async paths (`getUserMedia`) have no mounted-guard β†’ ctx/stream leak + setState-after-unmount if Settings closes during the mic prompt. +- [ ] **[Low]** Soundboard 30s safety timeout never cleared on natural clip end (`CallSoundboard.tsx:115`); `PrescreenControls` `PermissionStatus.onchange` not removed on unmount (`PrescreenControls.tsx:22-28`). +- [ ] **[Low] [live]** Call-to-call switch disposes the embed without an explicit `HangupCall` β†’ possible transient ghost RTC membership until EC's unload-leave fires. + +**Theming / visuals** + +- [ ] **[Med]** `invalidateDecorationCache` clears the module cache but has no pub/sub β†’ changing **your own** avatar decoration doesn't update live in already-mounted avatars (timeline/members) until remount. Add a listener set / bump counter. `useAvatarDecoration.ts:67`. +- [ ] **[Med/Low]** Decoration picker grid thumbnails use the raw `DECORATION_CDN` constant instead of `decorationUrl()`, ignoring the `VITE_DECORATION_CDN` override β†’ broken thumbnails if decorations are repointed. `ProfileDecoration.tsx:51`. +- [ ] **[Low]** Seasonal "Auto" is computed once at mount (no ticker, unlike NightLight) β†’ won't flip across a holiday-window boundary in a long-lived session. `SeasonalEffect.tsx:100`. +- [ ] **[Low]** Selecting seasonal "Auto" while a chat background is set is a silent no-op (asymmetric mutual exclusion β€” SeasonalEffect early-returns when `chatBackground !== 'none'`). `General.tsx:550`. +- [ ] **[Low]** Decoration settings fetch the `/{field}` sub-resource β†’ console 404 for users with no decoration set. `ProfileDecoration.tsx:79`. + +**Threads / presence / UX** + +- [ ] **[Med]** `PresenceBadge` renders DND (`unavailable` + `status_msg:'dnd'`) as a **yellow "Idle"** badge + label, while `PresenceRingAvatar` correctly shows **red** β€” inconsistent. Give the badge the same `status === 'dnd' β†’ Critical` + "Do Not Disturb" branch. `Presence.tsx:17-59`. +- [ ] **[Med]** Collapsible-message threshold is hardcoded (`COLLAPSE_MAX_HEIGHT = 320`), but the docs claim it's "configurable in Settings β†’ Appearance (default 20 lines)" β€” unimplemented. Add the setting + control, or fix the doc. `MsgTypeRenderers.tsx:38`. +- [ ] **[Med/Low]** In-app toast container has no visible cap / scroll β€” a burst of messages across rooms while focused stacks toasts unbounded and can cover the viewport. Cap visible N or `overflow-y:auto` + max-height. `LotusToastContainer.tsx:223-247`. +- [ ] **[Low]** "Unread First" room sort leaves the (larger) read portion unordered β€” no activity fallback for the equal-unread case. `Home.tsx:213-222`. +- [ ] **[Low]** Tab title "(N)" counts mentions, not unread messages (doc says unread) β€” reconcile doc vs. code. `ClientNonUIFeatures.tsx:120-123`. + +**Rooms / moderation / notifications / infra / desktop** + +- [ ] **[Med] [desktop]** `useTauriFocusAssist` never queries the initial OS Focus-Assist state on mount (unlike `useTauriDnd`, which rehydrates via `get_tray_dnd`) β†’ if Focus Assist is already ON at launch, notifications/sounds leak through until the OS state next flips. Add a `get_focus_assist` mount query (confirm whether the native poll emits an initial reading). `useTauriFocusAssist.ts:18-24`. +- [ ] **[Low]** Push-rule enable toggle holds stale local `useState` after an external rule change (toggled on another device) β€” sync from the `pushRule.enabled` prop. `PushRuleEditor.tsx:55-79`. +- [ ] **[Low]** Server-support `.well-known/matrix/support` is fetched from `mx.getHomeserverUrl()` (client-API host) instead of the MXID **server-name** host β†’ silently missing on delegated/split-domain servers. `About.tsx:45-47`. +- [ ] **[Low]** Cleared/partial quiet-hours `time` input (`''` β†’ window inactive) silently disables the window while the toggle still reads "on" β€” no feedback. `SystemNotification.tsx:364-382`. +- [ ] **[Low] [desktop]** Native quick-reply swallows send errors (`.catch(() => undefined)`); the `show_rich_toast` trigger has no verified web-side caller. `useTauriToastActions.ts:35-38`. +- [ ] **[Low]** Export-history date-range early-break can over-paginate + mislabel "truncated" in E2EE rooms (`oldestRawTs` only advances on decrypted `m.room.message`, so undecryptable old events never move it). `ExportRoomHistory.tsx:104,136`. +- [ ] **[Info/doc]** `PolicyListViewer` is a manual room-ID/alias viewer with **no** subscribe/unsubscribe controls and no subscribed-lists listing β€” `LOTUS_FEATURES.md:1287` describes both. Docs oversell; not a runtime bug. + ### βœ… Unread/read-receipt flakiness (reported 2026-07) β€” FIXED (pending prod QA) Room unread dots were inconsistent: reading a message sometimes cleared the dot, sometimes left it stuck, sometimes it resurrected. Root cause (confirmed by tracing + diffing upstream cinny `dev`): **our own "N4" change.** `handleReceipt` recomputed via `getUnreadInfo`, which reads `room.getUnreadNotificationCount()` β€” server-computed and **stale on the synchronous synthetic receipt echo** (SDK only zeroes it immediately when the last event is your own message) β†’ it PUT the stale non-zero count back β†’ stuck/resurrecting. Compounded by `hasUnread = !!unread` lighting the dot on any present map entry, incl. phantom `{0,0}` PUTs from our `UnreadNotifications` listener. Plus a Mark-as-Unread (MSC2867) flag that never cleared on opening an already-read room (no receipt β†’ no auto-clear).