wip: optimizations

This commit is contained in:
2026-05-23 15:28:40 +02:00
parent 5bf506af03
commit 155fe20862
89 changed files with 7431 additions and 392 deletions

View File

@@ -51,6 +51,7 @@ import { RoomsActions } from './store/rooms/rooms.actions';
import { selectCurrentRoom } from './store/rooms/rooms.selectors';
import { ROOM_URL_PATTERN } from './core/constants';
import { clearStoredCurrentUserId, getStoredCurrentUserId } from './core/storage/current-user-storage';
import { runWhenIdle } from './shared/rxjs';
import {
ThemeNodeDirective,
ThemePickerOverlayComponent,
@@ -234,10 +235,35 @@ export class App implements OnInit, OnDestroy {
}
async ngOnInit(): Promise<void> {
// Wire the router subscription first so we never miss the initial
// NavigationEnd while async bootstrap is still running.
this.router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
const url = evt.urlAfterRedirects || evt.url;
this.currentRouteUrl.set(url);
const roomMatch = url.match(ROOM_URL_PATTERN);
const currentRoomId = roomMatch ? roomMatch[1] : null;
this.voiceSession.checkCurrentRoute(currentRoomId);
}
});
// Synchronous theme bootstrap so first paint has the right tokens.
this.theme.initialize();
// Fire-and-forget work that must never block the render thread:
// - desktop auto-updater handshake
// - server-time offset (UI tolerates the local clock until it arrives)
// - notifications subsystem (depends on savedRooms signal which is
// populated by the rooms effect after dispatch)
// - desktop deep-link bridge (only relevant after first paint)
// - background presence + game activity loops
void this.desktopUpdates.initialize();
void this.kickOffBackgroundBootstrap();
// The only thing we genuinely must await before deciding which route
// to show is the local database (it owns the persisted current user).
let currentUserId = getStoredCurrentUserId();
await this.databaseService.initialize();
@@ -251,18 +277,6 @@ export class App implements OnInit, OnDestroy {
}
}
try {
const apiBase = this.servers.getApiBaseUrl();
await this.timeSync.syncWithEndpoint(apiBase);
} catch {}
await this.notifications.initialize();
await this.setupDesktopDeepLinks();
this.userStatus.start();
this.gameActivity.start();
const currentUrl = this.getCurrentRouteUrl();
if (!currentUserId) {
@@ -299,17 +313,28 @@ export class App implements OnInit, OnDestroy {
this.router.navigate(['/room', lastViewedChat.roomId], { replaceUrl: true }).catch(() => {});
}
}
}
this.router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
const url = evt.urlAfterRedirects || evt.url;
/**
* Runs services that the user does not actively wait on. Scheduled
* through `requestIdleCallback` so they yield to the renderer until
* the browser is idle, eliminating bootstrap stutter on Electron.
*/
private kickOffBackgroundBootstrap(): void {
runWhenIdle(() => {
try {
const apiBase = this.servers.getApiBaseUrl();
this.currentRouteUrl.set(url);
const roomMatch = url.match(ROOM_URL_PATTERN);
const currentRoomId = roomMatch ? roomMatch[1] : null;
this.voiceSession.checkCurrentRoute(currentRoomId);
void this.timeSync.syncWithEndpoint(apiBase).catch(() => {});
} catch {
// getApiBaseUrl can throw before endpoints are hydrated; ignore.
}
void this.notifications.initialize().catch(() => {});
void this.setupDesktopDeepLinks().catch(() => {});
this.userStatus.start();
this.gameActivity.start();
});
}