speaking$ here is raw LiveKit IsSpeakingChanged (not the 1 s/60 s debounced observeSpeaker$ used for the auto-spotlight), so it flips constantly in an active conversation. throttleTime(250, undefined, {leading:true, trailing:true}) therefore allows both a leading and a trailing emission per window — up to ~8 postMessage round trips per second, each one a full re-serialisation of every participant, and each one waking every host subscriber (CallEmbed.lotusCallStateListeners, i.e. useCallSpeakers + useAfkAutoMute). distinctUntilChanged(JSON.stringify) is placed after the throttle, so the (cheap) dedupe never gets the chance to prevent the throttle window from being spent, and the stringify of the whole array runs on every throttled emission (O(participants) at 8 Hz in a 30-person call).
15+ person call with several people talking; instrument lotusSendToHost or the host's listenAction.
Suggested fix
move distinctUntilChangedbeforethrottleTime (and compare field-wise rather than via JSON.stringify), and consider dropping the trailing emission or raising the interval for the speaking field specifically.
Filed from the September 2026 audit (branch lotus).
**Severity:** medium · **Type:** performance · **Confidence:** high
**Location:** `src/lotus/lotusCallState.ts:38-70`, `src/state/media/UserMediaViewModel.ts:112-123`
### Problem
`speaking$` here is raw LiveKit `IsSpeakingChanged` (not the 1 s/60 s debounced `observeSpeaker$` used for the auto-spotlight), so it flips constantly in an active conversation. `throttleTime(250, undefined, {leading:true, trailing:true})` therefore allows both a leading and a trailing emission per window — up to ~8 postMessage round trips per second, each one a full re-serialisation of *every* participant, and each one waking every host subscriber (`CallEmbed.lotusCallStateListeners`, i.e. `useCallSpeakers` + `useAfkAutoMute`). `distinctUntilChanged(JSON.stringify)` is placed *after* the throttle, so the (cheap) dedupe never gets the chance to prevent the throttle window from being spent, and the stringify of the whole array runs on every throttled emission (O(participants) at 8 Hz in a 30-person call).
**Cross-reference:** the host-side re-render cost is LotusGuild/cinny#32.
### How to trigger
15+ person call with several people talking; instrument `lotusSendToHost` or the host's `listenAction`.
### Suggested fix
move `distinctUntilChanged` *before* `throttleTime` (and compare field-wise rather than via `JSON.stringify`), and consider dropping the `trailing` emission or raising the interval for the `speaking` field specifically.
---
_Filed from the September 2026 audit (branch `lotus`)._
jared
added this to the EC fork audit 2026-09 · Medium & Low milestone 2026-09-12 02:13:07 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: medium · Type: performance · Confidence: high
Location:
src/lotus/lotusCallState.ts:38-70,src/state/media/UserMediaViewModel.ts:112-123Problem
speaking$here is raw LiveKitIsSpeakingChanged(not the 1 s/60 s debouncedobserveSpeaker$used for the auto-spotlight), so it flips constantly in an active conversation.throttleTime(250, undefined, {leading:true, trailing:true})therefore allows both a leading and a trailing emission per window — up to ~8 postMessage round trips per second, each one a full re-serialisation of every participant, and each one waking every host subscriber (CallEmbed.lotusCallStateListeners, i.e.useCallSpeakers+useAfkAutoMute).distinctUntilChanged(JSON.stringify)is placed after the throttle, so the (cheap) dedupe never gets the chance to prevent the throttle window from being spent, and the stringify of the whole array runs on every throttled emission (O(participants) at 8 Hz in a 30-person call).Cross-reference: the host-side re-render cost is LotusGuild/cinny#32.
How to trigger
15+ person call with several people talking; instrument
lotusSendToHostor the host'slistenAction.Suggested fix
move
distinctUntilChangedbeforethrottleTime(and compare field-wise rather than viaJSON.stringify), and consider dropping thetrailingemission or raising the interval for thespeakingfield specifically.Filed from the September 2026 audit (branch
lotus).