Files
cinny/src/app/utils/speakerSet.ts
T
jaredandClaude Opus 5 61dfdea9e9
CI / Build & Quality Checks (push) Successful in 1m31s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 10s
CI / Trigger Desktop Build (push) Successful in 15s
CI / Playwright smoke (e2e) (push) Successful in 2m22s
perf(calls): speaker set only updates when it changes; DOM fallback detaches once the fork streams
Every io.lotus.call_state push allocated a new Set, re-rendering the
app-wide call bar for the whole call. nextSpeakerSet() returns the
previous reference when membership is unchanged (pure helpers in
utils/speakerSet.ts, unit-tested), and the DOM MutationObserver fallback
in useCallSpeakers/useRemoteAllMuted is attached only while the fork's
participant list is unavailable.

Fixes #32

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-15 22:43:31 -04:00

32 lines
1.0 KiB
TypeScript

import { isUserId } from './matrix';
/** Minimal shape of a `io.lotus.call_state` participant entry we need here. */
export type SpeakingParticipant = { userId: string; speaking: boolean };
/** True when both sets contain exactly the same members. */
export const sameSet = <T>(a: Set<T>, b: Set<T>): boolean => {
if (a === b) return true;
if (a.size !== b.size) return false;
for (const v of a) {
if (!b.has(v)) return false;
}
return true;
};
/**
* Derives the speaking-users set from a `io.lotus.call_state` participant
* list. Returns `prev` unchanged (same reference) when the derived set is
* equal to it, so callers using the functional `setState` form can bail out
* of a re-render (#32 — the fork pushes this multiple times per second).
*/
export const nextSpeakerSet = (
prev: Set<string>,
participants: readonly SpeakingParticipant[],
): Set<string> => {
const next = new Set<string>();
participants.forEach((p) => {
if (p.speaking && isUserId(p.userId)) next.add(p.userId);
});
return sameSet(prev, next) ? prev : next;
};