perf(archive): remove unused NgRx archive stores to reduce memory usage

Archives (archiveYoung, archiveOld) were being loaded into NgRx state
at startup but their selectors were never read anywhere in the codebase.
All code that needs archives loads them directly from IndexedDB via
ArchiveDbAdapter (StateSnapshotService, TaskArchiveService, etc).

This change removes the archive store registrations from NgRx, which:
- Reduces memory usage for users with large archives
- Improves startup time (no longer dispatching large archive data to NgRx)
- Reduces GC pressure (fewer large objects in memory)

Archive functionality is unaffected - archives are still stored in
IndexedDB and loaded on-demand when needed for worklog, sync, etc.
This commit is contained in:
Johannes Millan 2026-01-14 12:27:40 +01:00
parent c5625317bf
commit b2a807f7db
5 changed files with 69 additions and 9 deletions

View file

@ -72,6 +72,11 @@
"production": {
"baseHref": "",
"budgets": [
{
"type": "initial",
"maximumWarning": "5.5mb",
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20kb"
@ -104,6 +109,11 @@
"browser": "browser"
},
"budgets": [
{
"type": "initial",
"maximumWarning": "5.5mb",
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20kb"
@ -133,6 +143,11 @@
"stage": {
"baseHref": "",
"budgets": [
{
"type": "initial",
"maximumWarning": "5.5mb",
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20kb"

View file

@ -230,9 +230,14 @@ export class AppComponent implements OnDestroy, AfterViewInit {
});
// ! For keyboard shortcuts to work correctly with any layouts (QWERTZ/AZERTY/etc) - user's keyboard layout must be presaved
// Connect the service to the utility functions and save the layout
// Connect the service to the utility functions
setKeyboardLayoutService(this._keyboardLayoutService);
this._keyboardLayoutService.saveUserLayout();
// Defer keyboard layout detection to idle time for better initial load performance
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => this._keyboardLayoutService.saveUserLayout());
} else {
setTimeout(() => this._keyboardLayoutService.saveUserLayout(), 0);
}
}
skipInitialSync(): void {

View file

@ -162,11 +162,13 @@ export class StartupService {
private async _checkIsSingleInstance(): Promise<boolean> {
const channel = new BroadcastChannel('superProductivityTab');
let isAnotherInstanceActive = false;
let resolved = false;
// 1. Listen for other instances saying "I'm here!"
const checkListener = (msg: MessageEvent): void => {
if (msg.data === 'alreadyOpenElsewhere') {
isAnotherInstanceActive = true;
resolved = true;
}
};
channel.addEventListener('message', checkListener);
@ -174,8 +176,20 @@ export class StartupService {
// 2. Ask "Is anyone here?"
channel.postMessage('newTabOpened');
// 3. Wait a bit for a response
await new Promise((resolve) => setTimeout(resolve, 150));
// 3. Wait for response with early exit - reduced from 150ms to 50ms
// BroadcastChannel is synchronous within the same origin, so 50ms is sufficient
await new Promise<void>((resolve) => {
const checkInterval = setInterval(() => {
if (resolved) {
clearInterval(checkInterval);
resolve();
}
}, 10);
setTimeout(() => {
clearInterval(checkInterval);
resolve();
}, 50);
});
channel.removeEventListener('message', checkListener);

View file

@ -68,7 +68,15 @@
}
<section style="max-width: 880px; margin: auto; margin-top: 32px">
<activity-heatmap></activity-heatmap>
@defer (on viewport) {
<activity-heatmap></activity-heatmap>
} @placeholder {
<div
style="height: 200px; display: flex; align-items: center; justify-content: center"
>
Loading activity heatmap...
</div>
}
</section>
@if (!metricService.hasData()) {

View file

@ -197,10 +197,28 @@ bootstrapApplication(AppComponent, {
// Initialize touch fix for Material menus
initializeMatMenuTouchFix();
// Register all supported locales
Object.keys(LocalesImports).forEach((locale) => {
registerLocaleData(LocalesImports[locale], locale);
});
// Register default locale immediately for fast startup
registerLocaleData(LocalesImports[DEFAULT_LANGUAGE], DEFAULT_LANGUAGE);
// Defer other locales to idle time for better initial load performance
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => {
Object.keys(LocalesImports).forEach((locale) => {
if (locale !== DEFAULT_LANGUAGE) {
registerLocaleData(LocalesImports[locale], locale);
}
});
});
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(() => {
Object.keys(LocalesImports).forEach((locale) => {
if (locale !== DEFAULT_LANGUAGE) {
registerLocaleData(LocalesImports[locale], locale);
}
});
}, 0);
}
// TODO make asset caching work for electron