From fd957badfff8b3aa8be0a7b20ffb497b79d9ecc0 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Sat, 12 Sep 2026 11:34:08 -0400 Subject: [PATCH] fix(lotus): call_state skips standalone mode; dedupe before throttle startLotusCallState gated only on the URL flag, so a standalone (non-widget) load built the whole per-member pipeline and stringified it at up to 8 Hz for a host that doesn't exist. Add the same `if (!widget)` guard the sibling modules use. Also move distinctUntilChanged ahead of throttleTime so an unchanged value no longer spends a throttle window (first half of #20). Fixes #31 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/lotus/lotusCallState.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/lotus/lotusCallState.ts b/src/lotus/lotusCallState.ts index 86c7ea88..11ca6d78 100644 --- a/src/lotus/lotusCallState.ts +++ b/src/lotus/lotusCallState.ts @@ -6,9 +6,15 @@ Please see LICENSE in the repository root for full details. */ import { combineLatest, of, type Subscription } from "rxjs"; -import { distinctUntilChanged, map, switchMap, throttleTime } from "rxjs/operators"; +import { + distinctUntilChanged, + map, + switchMap, + throttleTime, +} from "rxjs/operators"; import { type CallViewModel } from "../state/CallViewModel/CallViewModel"; +import { widget } from "../widget"; import { LotusWidgetActions, lotusFlag, lotusSendToHost } from "./lotusWidget"; interface ParticipantState { @@ -32,6 +38,10 @@ interface ParticipantState { */ export function startLotusCallState(vm: CallViewModel): () => void { if (!lotusFlag("lotusCallState")) return () => undefined; + // [lotus] Standalone (non-widget) mode has no host to send state to; + // skip building the whole stream pipeline, mirroring lotusFocus.ts / + // lotusDecorations.ts. + if (!widget) return () => undefined; const sub: Subscription = vm.userMedia$ .pipe( @@ -46,7 +56,11 @@ export function startLotusCallState(vm: CallViewModel): () => void { m.videoEnabled$, ]).pipe( map( - ([speaking, audioEnabled, videoEnabled]): ParticipantState => ({ + ([ + speaking, + audioEnabled, + videoEnabled, + ]): ParticipantState => ({ id: m.id, userId: m.userId, speaking, @@ -58,11 +72,12 @@ export function startLotusCallState(vm: CallViewModel): () => void { ), ), ), - // `speaking` flips rapidly; cap the send rate and drop no-op repeats. - // 250ms is plenty for speaking rings / mute badges and keeps the - // request/response widget traffic modest. - throttleTime(250, undefined, { leading: true, trailing: true }), + // `speaking` flips rapidly; drop no-op repeats BEFORE throttling so + // the throttle window isn't spent re-emitting an unchanged value, then + // cap the send rate. 250ms is plenty for speaking rings / mute badges + // and keeps the request/response widget traffic modest. distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), + throttleTime(250, undefined, { leading: true, trailing: true }), ) .subscribe((participants) => { lotusSendToHost(LotusWidgetActions.CallState, { participants });