perf(desktop): Dashboard-Aufbau in der Linux-App ohne Einblend-Animation, Melder gegen Fokus-Schuebe gedrosselt
Gemessen in der echten Linux-App (WebKitGTK): Haenger beim Oeffnen des Dashboards von 550-1100 ms auf 250-350 ms. Im alpha-Log kamen beim Oeffnen bis zu 50 Abfragen je Sekunde von Erinnerungs- und Nextcloud-Melder; Fokus-/ Sichtbarkeitswechsel loesen jetzt hoechstens alle 10 s eine Abfrage aus. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -489,6 +489,13 @@ input[type="checkbox"], input[type="radio"] {
|
|||||||
animation-delay: var(--enter-delay, 0ms);
|
animation-delay: var(--enter-delay, 0ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Linux-Desktop-App (WebKitGTK, ohne Grafikbeschleunigung): keine
|
||||||
|
Einblend-Animation und kein Pulsieren der Ladeplatzhalter — beides liess den
|
||||||
|
Aufbau des Dashboards stocken (DesktopPlatformMarker setzt das Attribut). */
|
||||||
|
html[data-desktop-os="linux"] .widget-card,
|
||||||
|
html[data-desktop-os="linux"] .animate-pulse {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
@keyframes widget-enter {
|
@keyframes widget-enter {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getLocale, getMessages } from 'next-intl/server';
|
|||||||
import { ThemeProvider } from 'next-themes';
|
import { ThemeProvider } from 'next-themes';
|
||||||
import { DesktopContextMenuGuard } from '@/components/desktop/desktop-context-menu-guard';
|
import { DesktopContextMenuGuard } from '@/components/desktop/desktop-context-menu-guard';
|
||||||
import { DesktopExternalLinks } from '@/components/desktop/desktop-external-links';
|
import { DesktopExternalLinks } from '@/components/desktop/desktop-external-links';
|
||||||
|
import { DesktopPlatformMarker } from '@/components/desktop/desktop-platform-marker';
|
||||||
import { DesktopThemeSync } from '@/components/desktop/desktop-theme-sync';
|
import { DesktopThemeSync } from '@/components/desktop/desktop-theme-sync';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
<DesktopContextMenuGuard />
|
<DesktopContextMenuGuard />
|
||||||
<DesktopExternalLinks />
|
<DesktopExternalLinks />
|
||||||
<DesktopThemeSync />
|
<DesktopThemeSync />
|
||||||
|
<DesktopPlatformMarker />
|
||||||
{children}
|
{children}
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { cleanup, render } from '@testing-library/react';
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
import { DESKTOP_CLIENT_COOKIE_NAME } from '@/lib/desktop-client';
|
||||||
|
import { DesktopPlatformMarker } from './desktop-platform-marker';
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
document.cookie = `${DESKTOP_CLIENT_COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DesktopPlatformMarker', () => {
|
||||||
|
it('setzt das Betriebssystem der Desktop-App am html-Element', () => {
|
||||||
|
document.cookie = `${DESKTOP_CLIENT_COOKIE_NAME}=${encodeURIComponent('1.9.2|abc1234|linux')}; path=/`;
|
||||||
|
render(<DesktopPlatformMarker />);
|
||||||
|
expect(document.documentElement.dataset.desktopOs).toBe('linux');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setzt im Browser nichts', () => {
|
||||||
|
render(<DesktopPlatformMarker />);
|
||||||
|
expect(document.documentElement.dataset.desktopOs).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { getDesktopClientInfo } from '@/lib/desktop-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DesktopPlatformMarker (quick-261002) — setzt `data-desktop-os` am
|
||||||
|
* `<html>` (z. B. `linux`), damit CSS die Desktop-App je Betriebssystem
|
||||||
|
* anpassen kann. Anlass: Die Linux-Webview (WebKitGTK) zeichnet ohne
|
||||||
|
* Grafikbeschleunigung; die gestaffelte Einblend-Animation der Kacheln liess
|
||||||
|
* den Aufbau des Dashboards spuerbar stocken (gemessen: Haenger von
|
||||||
|
* 550-1100 ms auf ~230 ms ohne Animation). Im Browser ohne Wirkung.
|
||||||
|
*/
|
||||||
|
export function DesktopPlatformMarker() {
|
||||||
|
useEffect(() => {
|
||||||
|
const os = getDesktopClientInfo()?.os;
|
||||||
|
if (!os) return;
|
||||||
|
document.documentElement.dataset.desktopOs = os;
|
||||||
|
return () => {
|
||||||
|
delete document.documentElement.dataset.desktopOs;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
|||||||
const MODULE_SLUG = 'nextcloud-status';
|
const MODULE_SLUG = 'nextcloud-status';
|
||||||
/** Abstand der Abfrage beim Server. */
|
/** Abstand der Abfrage beim Server. */
|
||||||
const POLL_MS = 60_000;
|
const POLL_MS = 60_000;
|
||||||
|
/** Mindestabstand fuer Abfragen nach Fokus-/Sichtbarkeitswechsel. */
|
||||||
|
export const RESUME_MIN_MS = 10_000;
|
||||||
|
|
||||||
/** Rote Grundkennung der API -> Schluessel unter `nextcloudStatus.notify.down`. */
|
/** Rote Grundkennung der API -> Schluessel unter `nextcloudStatus.notify.down`. */
|
||||||
const DOWN_KEYS: Record<string, string> = {
|
const DOWN_KEYS: Record<string, string> = {
|
||||||
@@ -110,7 +112,14 @@ export function NextcloudAlertNotifier() {
|
|||||||
void start();
|
void start();
|
||||||
const interval = setInterval(() => void poll(), POLL_MS);
|
const interval = setInterval(() => void poll(), POLL_MS);
|
||||||
|
|
||||||
|
// Fokus/Sichtbarkeit loesen hoechstens alle RESUME_MIN_MS eine Abfrage aus:
|
||||||
|
// In der Linux-Desktop-App kamen diese Ereignisse beim Oeffnen des
|
||||||
|
// Dashboards in Schueben (bis zu 50 Abfragen je Sekunde im API-Log).
|
||||||
|
let lastResume = Date.now();
|
||||||
const resume = () => {
|
const resume = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastResume < RESUME_MIN_MS) return;
|
||||||
|
lastResume = now;
|
||||||
pausedRef.current = false;
|
pausedRef.current = false;
|
||||||
void start();
|
void start();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -143,4 +143,16 @@ describe('ReminderNotifier', () => {
|
|||||||
});
|
});
|
||||||
expect(mockList.mock.calls.length).toBe(after401 + 1);
|
expect(mockList.mock.calls.length).toBe(after401 + 1);
|
||||||
});
|
});
|
||||||
|
it('viele Fokus-Ereignisse kurz hintereinander loesen nur eine Abfrage aus', async () => {
|
||||||
|
mockList.mockResolvedValue([]);
|
||||||
|
render(<ReminderNotifier />);
|
||||||
|
await advance(0);
|
||||||
|
await advance(15_000);
|
||||||
|
const before = mockList.mock.calls.length;
|
||||||
|
await act(async () => {
|
||||||
|
for (let i = 0; i < 50; i++) window.dispatchEvent(new Event('focus'));
|
||||||
|
await vi.advanceTimersByTimeAsync(0);
|
||||||
|
});
|
||||||
|
expect(mockList.mock.calls.length).toBe(before + 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import {
|
|||||||
withNotifyLock,
|
withNotifyLock,
|
||||||
} from '@/lib/reminder-notify';
|
} from '@/lib/reminder-notify';
|
||||||
import {
|
import {
|
||||||
|
listReminders,
|
||||||
REMINDERS_CHANGED_EVENT,
|
REMINDERS_CHANGED_EVENT,
|
||||||
type Reminder,
|
type Reminder,
|
||||||
ReminderRequestError,
|
ReminderRequestError,
|
||||||
listReminders,
|
|
||||||
} from '@/lib/reminders-api';
|
} from '@/lib/reminders-api';
|
||||||
|
|
||||||
/** Abstand der Abfrage beim Server. */
|
/** Abstand der Abfrage beim Server. */
|
||||||
const POLL_MS = 60_000;
|
const POLL_MS = 60_000;
|
||||||
|
/** Mindestabstand fuer Abfragen nach Fokus-/Sichtbarkeitswechsel. */
|
||||||
|
export const RESUME_MIN_MS = 10_000;
|
||||||
/** Abstand der lokalen Faelligkeitspruefung gegen die geladene Liste (ohne Netz). */
|
/** Abstand der lokalen Faelligkeitspruefung gegen die geladene Liste (ohne Netz). */
|
||||||
const TICK_MS = 10_000;
|
const TICK_MS = 10_000;
|
||||||
const BODY_MAX = 200;
|
const BODY_MAX = 200;
|
||||||
@@ -87,7 +89,14 @@ export function ReminderNotifier() {
|
|||||||
const poll = setInterval(() => void load(), POLL_MS);
|
const poll = setInterval(() => void load(), POLL_MS);
|
||||||
const tick = setInterval(() => void notifyDue(), TICK_MS);
|
const tick = setInterval(() => void notifyDue(), TICK_MS);
|
||||||
|
|
||||||
|
// Fokus/Sichtbarkeit loesen hoechstens alle RESUME_MIN_MS eine Abfrage aus:
|
||||||
|
// In der Linux-Desktop-App kamen diese Ereignisse beim Oeffnen des
|
||||||
|
// Dashboards in Schueben (bis zu 50 Abfragen je Sekunde im API-Log).
|
||||||
|
let lastResume = Date.now();
|
||||||
const resume = () => {
|
const resume = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastResume < RESUME_MIN_MS) return;
|
||||||
|
lastResume = now;
|
||||||
pausedRef.current = false;
|
pausedRef.current = false;
|
||||||
void load();
|
void load();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user