Speaker set is reallocated on every call-state push, re-rendering the app-wide call bar #32

Closed
opened 2026-09-12 01:51:04 -04:00 by jared · 0 comments
Owner

Severity: medium · Type: performance · Confidence: high

Location: src/app/hooks/useCallSpeakers.ts:43-56,114, src/app/features/call-status/CallStatus.tsx:28,54-72

Problem

syncState is subscribed to io.lotus.call_state, which the fork pushes on every speaking/level change (multiple times per second in an active call), and it unconditionally calls setSpeakers(new Set(...)). A fresh Set is never reference-equal, so React re-renders CallStatusMemberSpeaking + MemberGlance (which recomputes avatar mxc URLs and getMemberName per member) on every push, for the entire duration of the call, in the persistent app-wide status bar. The two MutationObservers in this file and in useRemoteAllMuted also stay attached to the whole EC document with attributes: true even when the fork path is active, so every EC style/class mutation runs a mutations.some(...) scan on the host main thread.

How to trigger

join a call with 3+ participants talking and profile the host; CallStatus re-renders continuously.

Suggested fix

bail out when the derived set is equal to the previous one (setSpeakers((prev) => sameSet(prev, next) ? prev : next)), and skip attaching the DOM MutationObserver once getLotusParticipants() has returned a non-empty list.


Filed from the September 2026 client audit (branch lotus @ 4bea4895).

**Severity:** medium · **Type:** performance · **Confidence:** high **Location:** `src/app/hooks/useCallSpeakers.ts:43-56,114`, `src/app/features/call-status/CallStatus.tsx:28,54-72` ### Problem `syncState` is subscribed to `io.lotus.call_state`, which the fork pushes on every speaking/level change (multiple times per second in an active call), and it unconditionally calls `setSpeakers(new Set(...))`. A fresh `Set` is never reference-equal, so React re-renders `CallStatus` → `MemberSpeaking` + `MemberGlance` (which recomputes avatar mxc URLs and `getMemberName` per member) on every push, for the entire duration of the call, in the persistent app-wide status bar. The two MutationObservers in this file and in `useRemoteAllMuted` also stay attached to the whole EC document with `attributes: true` even when the fork path is active, so every EC style/class mutation runs a `mutations.some(...)` scan on the host main thread. ### How to trigger join a call with 3+ participants talking and profile the host; `CallStatus` re-renders continuously. ### Suggested fix bail out when the derived set is equal to the previous one (`setSpeakers((prev) => sameSet(prev, next) ? prev : next)`), and skip attaching the DOM MutationObserver once `getLotusParticipants()` has returned a non-empty list. --- _Filed from the September 2026 client audit (branch `lotus` @ 4bea4895)._
jared added this to the Audit 2026-09 · Medium & Low milestone 2026-09-12 01:51:04 -04:00
jared added the bugpriority: mediumperformancearea: calls labels 2026-09-12 01:51:04 -04:00
jared self-assigned this 2026-09-12 01:51:04 -04:00
jared closed this issue 2026-09-15 22:43:33 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
2026-11-14
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#32