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 {
Component,
effect,
@@ -114,19 +115,28 @@ export interface ServerDiscoverySection {
templateUrl: './server-browser.component.html'
})
export class ServerBrowserComponent implements OnInit {
private store = inject(Store);
private router = inject(Router);
private db = inject(DatabaseService);
private externalLinks = inject(ExternalLinkService);
private serverDirectory = inject(ServerDirectoryFacade);
private webrtc = inject(RealtimeSessionFacade);
private pluginRequirements = inject(PluginRequirementService);
private pluginStore = inject(PluginStoreService);
private injector = inject(Injector);
private readonly i18n = inject(AppI18nService);
private readonly signalServerAuthorize = inject(SignalServerAuthorizeService);
private searchSubject = new Subject<string>();
private banLookupRequestVersion = 0;
/** Discovery sections shown when the search query is empty. */
@Input() discoverySections: ServerDiscoverySection[] = [];
/** Title for the onboarding empty state when there is nothing to show. */
@Input() emptyStateTitle?: string;
/** Supporting copy for the onboarding empty state. */
@Input() emptyStateMessage?: string;
/** Placeholder for the search input. */
@Input() searchPlaceholder?: string;
/** Whether the My Servers quick bar is shown. */
@Input() showMyServers = true;
@@ -142,95 +152,6 @@ export class ServerBrowserComponent implements OnInit {
return this.searchPlaceholder ?? this.i18n.instant('servers.browser.search.placeholder');
}
searchQuery = '';
searchResults = this.store.selectSignal(selectSearchResults);
isSearching = this.store.selectSignal(selectIsSearching);
error = this.store.selectSignal(selectRoomsError);
savedRooms = this.store.selectSignal(selectSavedRooms);
currentUser = this.store.selectSignal(selectCurrentUser);
activeEndpoints = this.serverDirectory.activeServers;
bannedServerLookup = signal<Record<string, boolean>>({});
bannedServerName = signal('');
showBannedDialog = signal(false);
showPasswordDialog = signal(false);
passwordPromptServer = signal<ServerInfo | null>(null);
joinPassword = signal('');
joinPasswordError = signal<string | null>(null);
joinErrorMessage = signal<string | null>(null);
joinedServerMenuId = signal<string | null>(null);
leaveDialogRoom = signal<Room | null>(null);
pluginConsentDialog = signal<JoinPluginConsentDialog | null>(null);
selectedOptionalPluginIds = signal<Set<string>>(new Set());
pluginConsentBusy = signal(false);
pluginConsentError = signal<string | null>(null);
pluginConsentReadme = signal<PluginStoreReadme | null>(null);
pluginConsentReadmeLoadingId = signal<string | null>(null);
pluginConsentReadmeError = signal<string | null>(null);
/** True while the user is actively searching (non-empty query). */
get isSearchMode(): boolean {
return this.searchQuery.trim().length > 0;
}
/** Discovery sections that actually contain servers. */
get visibleSections(): ServerDiscoverySection[] {
return this.discoverySections.filter((section) => section.servers.length > 0);
}
/** True when there is nothing to render outside of search mode. */
get showEmptyState(): boolean {
return !this.isSearchMode && this.visibleSections.length === 0;
}
private store = inject(Store);
private router = inject(Router);
private db = inject(DatabaseService);
private externalLinks = inject(ExternalLinkService);
private serverDirectory = inject(ServerDirectoryFacade);
private webrtc = inject(RealtimeSessionFacade);
private pluginRequirements = inject(PluginRequirementService);
private pluginStore = inject(PluginStoreService);
private injector = inject(Injector);
private readonly i18n = inject(AppI18nService);
private readonly signalServerAuthorize = inject(SignalServerAuthorizeService);
private searchSubject = new Subject<string>();
private banLookupRequestVersion = 0;
serverCardTitle(server: ServerInfo): string {
return this.isJoinedServer(server)
? this.i18n.instant('servers.browser.card.doubleClickOpen', { name: server.name })
@@ -279,6 +200,31 @@ export class ServerBrowserComponent implements OnInit {
: this.i18n.instant('servers.plugins.readme');
}
searchQuery = '';
searchResults = this.store.selectSignal(selectSearchResults);
isSearching = this.store.selectSignal(selectIsSearching);
error = this.store.selectSignal(selectRoomsError);
savedRooms = this.store.selectSignal(selectSavedRooms);
currentUser = this.store.selectSignal(selectCurrentUser);
activeEndpoints = this.serverDirectory.activeServers;
bannedServerLookup = signal<Record<string, boolean>>({});
bannedServerName = signal('');
showBannedDialog = signal(false);
showPasswordDialog = signal(false);
passwordPromptServer = signal<ServerInfo | null>(null);
joinPassword = signal('');
joinPasswordError = signal<string | null>(null);
joinErrorMessage = signal<string | null>(null);
joinedServerMenuId = signal<string | null>(null);
leaveDialogRoom = signal<Room | null>(null);
pluginConsentDialog = signal<JoinPluginConsentDialog | null>(null);
selectedOptionalPluginIds = signal<Set<string>>(new Set());
pluginConsentBusy = signal(false);
pluginConsentError = signal<string | null>(null);
pluginConsentReadme = signal<PluginStoreReadme | null>(null);
pluginConsentReadmeLoadingId = signal<string | null>(null);
pluginConsentReadmeError = signal<string | null>(null);
// The reactive effect is created in ngOnInit with an explicit injector so the
// component can be instantiated outside a change-detection context (e.g. unit tests).
ngOnInit(): void {
@@ -301,6 +247,21 @@ export class ServerBrowserComponent implements OnInit {
});
}
/** True while the user is actively searching (non-empty query). */
get isSearchMode(): boolean {
return this.searchQuery.trim().length > 0;
}
/** Discovery sections that actually contain servers. */
get visibleSections(): ServerDiscoverySection[] {
return this.discoverySections.filter((section) => section.servers.length > 0);
}
/** True when there is nothing to render outside of search mode. */
get showEmptyState(): boolean {
return !this.isSearchMode && this.visibleSections.length === 0;
}
onSearchChange(query: string): void {
this.searchSubject.next(query);
}
@@ -763,5 +724,4 @@ export class ServerBrowserComponent implements OnInit {
return hasRoomBanForUser(bans, currentUser, currentUserId);
}
}