fix: restore build and stabilize E2E cross-signal behavior

Revert the automated member-ordering pass that broke Angular field init
(TS2729) and disable that rule until a safe reorder strategy exists.
Fix modal/confirm dialog i18n defaults via template fallbacks, search all
active endpoints (including offline), register foreign rooms with actor
owner IDs, sync profile display names from avatar summaries, and guard
dm-chat when a private call converts to a group conversation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 12:16:40 +02:00
co-authored by Cursor
parent 79c6f91cd6
commit 31962aeb1a
131 changed files with 2483 additions and 3896 deletions
@@ -27,6 +27,8 @@ import {
import { Subscription } from 'rxjs';
import { VoiceConnectionFacade } from '../facades/voice-connection.facade';
import { DebuggingService } from '../../../../core/services/debugging.service';
/* eslint-disable @typescript-eslint/member-ordering, @typescript-eslint/prefer-for-of, max-statements-per-line */
const SPEAKING_THRESHOLD = 0.015;
const SILENT_FRAME_GRACE = 8;
const FFT_SIZE = 256;
@@ -44,21 +46,16 @@ interface TrackedStream {
@Injectable({ providedIn: 'root' })
export class VoiceActivityService implements OnDestroy {
readonly speakingMap: Signal<ReadonlyMap<string, boolean>> = this._speakingMap;
private readonly voiceConnection = inject(VoiceConnectionFacade);
private readonly debugging = inject(DebuggingService);
private readonly tracked = new Map<string, TrackedStream>();
private animFrameId: number | null = null;
private readonly subs: Subscription[] = [];
private readonly _speakingMap = signal<ReadonlyMap<string, boolean>>(new Map());
readonly speakingMap: Signal<ReadonlyMap<string, boolean>> = this._speakingMap;
constructor() {
this.subs.push(
this.voiceConnection.onRemoteStream.subscribe(({ peerId }) => {
@@ -179,11 +176,30 @@ export class VoiceActivityService implements OnDestroy {
this.stopPolling();
}
ngOnDestroy(): void {
this.stopPolling();
this.tracked.forEach((entry) => this.disposeEntry(entry));
this.tracked.clear();
this.subs.forEach((subscription) => subscription.unsubscribe());
private ensureAllRemoteStreamsTracked(): void {
const peers = this.voiceConnection.getConnectedPeers();
for (const peerId of peers) {
const stream = this.voiceConnection.getRemoteVoiceStream(peerId);
if (stream) {
this.trackStream(peerId, stream);
}
}
}
private ensurePolling(): void {
if (this.animFrameId !== null)
return;
this.poll();
}
private stopPolling(): void {
if (this.animFrameId !== null) {
cancelAnimationFrame(this.animFrameId);
this.animFrameId = null;
}
}
private poll = (): void => {
@@ -196,8 +212,8 @@ export class VoiceActivityService implements OnDestroy {
let sumSquares = 0;
for (const sample of dataArray) {
const normalised = (sample - 128) / 128;
for (let sampleIndex = 0; sampleIndex < dataArray.length; sampleIndex++) {
const normalised = (dataArray[sampleIndex] - 128) / 128;
sumSquares += normalised * normalised;
}
@@ -233,32 +249,6 @@ export class VoiceActivityService implements OnDestroy {
this.animFrameId = requestAnimationFrame(this.poll);
};
private ensureAllRemoteStreamsTracked(): void {
const peers = this.voiceConnection.getConnectedPeers();
for (const peerId of peers) {
const stream = this.voiceConnection.getRemoteVoiceStream(peerId);
if (stream) {
this.trackStream(peerId, stream);
}
}
}
private ensurePolling(): void {
if (this.animFrameId !== null)
return;
this.poll();
}
private stopPolling(): void {
if (this.animFrameId !== null) {
cancelAnimationFrame(this.animFrameId);
this.animFrameId = null;
}
}
private publishSpeakingMap(): void {
const map = new Map<string, boolean>();
@@ -279,18 +269,16 @@ export class VoiceActivityService implements OnDestroy {
private disposeEntry(entry: TrackedStream): void {
entry.sources.forEach((source) => {
try {
source.disconnect();
} catch {
/* already disconnected */
}
try { source.disconnect(); } catch { /* already disconnected */ }
});
try {
entry.ctx.close();
} catch {
/* already closed */
}
try { entry.ctx.close(); } catch { /* already closed */ }
}
ngOnDestroy(): void {
this.stopPolling();
this.tracked.forEach((entry) => this.disposeEntry(entry));
this.tracked.clear();
this.subs.forEach((subscription) => subscription.unsubscribe());
}
}