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
parent 79c6f91cd6
commit 31962aeb1a
131 changed files with 2483 additions and 3896 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import {
Actions,
@@ -37,6 +38,9 @@ export function groupMessagesByRoom(messages: Message[]): Map<string, Message[]>
@Injectable()
export class NotificationsEffects {
private readonly actions$ = inject(Actions);
private readonly store = inject(Store);
private readonly notifications = inject(NotificationsFacade);
syncRoomCatalog$ = createEffect(
() =>
@@ -132,11 +136,4 @@ export class NotificationsEffects {
),
{ dispatch: false }
);
private readonly actions$ = inject(Actions);
private readonly store = inject(Store);
private readonly notifications = inject(NotificationsFacade);
}

View File

@@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import { NotificationsService } from '../services/notifications.service';
@Injectable({ providedIn: 'root' })
export class NotificationsFacade {
private readonly service = inject(NotificationsService);
readonly settings = this.service.settings;
readonly unread = this.service.unread;
private readonly service = inject(NotificationsService);
initialize(
...args: Parameters<NotificationsService['initialize']>
): ReturnType<NotificationsService['initialize']> {
@@ -99,5 +98,4 @@ export class NotificationsFacade {
): ReturnType<NotificationsService['setChannelMuted']> {
return this.service.setChannelMuted(...args);
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Injectable,
computed,
@@ -45,55 +46,34 @@ const MAX_NOTIFIED_MESSAGE_IDS = 500;
@Injectable({ providedIn: 'root' })
export class NotificationsService {
readonly settings = computed(() => this._settings());
readonly unread = computed(() => this._unread());
private readonly store = inject(Store);
private readonly db = inject(DatabaseService);
private readonly audio = inject(NotificationAudioService);
private readonly appI18n = inject(AppI18nService);
private readonly timeSync = inject(TimeSyncService);
private readonly desktopNotifications = inject(DesktopNotificationService);
private readonly storage = inject(NotificationSettingsStorageService);
private readonly currentRoom = this.store.selectSignal(selectCurrentRoom);
private readonly activeChannelId = this.store.selectSignal(selectActiveChannelId);
private readonly savedRooms = this.store.selectSignal(selectSavedRooms);
private readonly currentUser = this.store.selectSignal(selectCurrentUser);
private readonly _settings = signal<NotificationsSettings>(createDefaultNotificationSettings());
private readonly _unread = signal<NotificationsUnreadState>(createEmptyUnreadState());
private readonly _windowFocused = signal<boolean>(typeof document === 'undefined' ? true : document.hasFocus());
private readonly _documentVisible = signal<boolean>(typeof document === 'undefined' ? true : document.visibilityState === 'visible');
private readonly _windowMinimized = signal<boolean>(false);
private readonly platformKind = detectPlatform();
private readonly notifiedMessageIds = new Set<string>();
private readonly notifiedMessageOrder: string[] = [];
private attentionActive = false;
private windowStateCleanup: (() => void) | null = null;
private initialised = false;
readonly settings = computed(() => this._settings());
readonly unread = computed(() => this._unread());
async initialize(): Promise<void> {
if (this.initialised) {
return;
@@ -331,30 +311,6 @@ export class NotificationsService {
});
}
private readonly handleWindowFocus = (): void => {
this._windowFocused.set(true);
this._windowMinimized.set(false);
this.markCurrentChannelReadIfActive();
};
private readonly handleWindowBlur = (): void => {
this._windowFocused.set(false);
this.syncWindowAttention();
};
private readonly handleVisibilityChange = (): void => {
const isVisible = document.visibilityState === 'visible';
this._documentVisible.set(isVisible);
if (isVisible && this._windowFocused()) {
this.markCurrentChannelReadIfActive();
return;
}
this.syncWindowAttention();
};
private registerWindowListeners(): void {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return;
@@ -379,6 +335,30 @@ export class NotificationsService {
});
}
private readonly handleWindowFocus = (): void => {
this._windowFocused.set(true);
this._windowMinimized.set(false);
this.markCurrentChannelReadIfActive();
};
private readonly handleWindowBlur = (): void => {
this._windowFocused.set(false);
this.syncWindowAttention();
};
private readonly handleVisibilityChange = (): void => {
const isVisible = document.visibilityState === 'visible';
this._documentVisible.set(isVisible);
if (isVisible && this._windowFocused()) {
this.markCurrentChannelReadIfActive();
return;
}
this.syncWindowAttention();
};
private buildContext(): NotificationDeliveryContext {
return {
activeChannelId: this.activeChannelId(),
@@ -645,7 +625,6 @@ export class NotificationsService {
}
}
}
}
function detectPlatform(): DesktopPlatform {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
computed,
@@ -36,20 +37,15 @@ import { APP_TRANSLATE_IMPORTS } from '../../../../../core/i18n';
templateUrl: './notifications-settings.component.html'
})
export class NotificationsSettingsComponent {
private readonly store = inject(Store);
readonly notifications = inject(NotificationsFacade);
readonly rooms = this.store.selectSignal(selectSavedRooms);
readonly settings = this.notifications.settings;
readonly enabled = computed(() => this.settings().enabled);
readonly showPreview = computed(() => this.settings().showPreview);
readonly respectBusyStatus = computed(() => this.settings().respectBusyStatus);
private readonly store = inject(Store);
trackRoom = (_index: number, room: Room) => room.id;
textChannels(room: Room) {
@@ -116,5 +112,4 @@ export class NotificationsSettingsComponent {
formatUnreadCount(count: number): string {
return count > 99 ? '99+' : String(count);
}
}