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);
|
||||
}
|
||||
}
|
||||
/* 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 {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getLocale, getMessages } from 'next-intl/server';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import { DesktopContextMenuGuard } from '@/components/desktop/desktop-context-menu-guard';
|
||||
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 './globals.css';
|
||||
|
||||
@@ -29,6 +30,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<DesktopContextMenuGuard />
|
||||
<DesktopExternalLinks />
|
||||
<DesktopThemeSync />
|
||||
<DesktopPlatformMarker />
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</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';
|
||||
/** Abstand der Abfrage beim Server. */
|
||||
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`. */
|
||||
const DOWN_KEYS: Record<string, string> = {
|
||||
@@ -110,7 +112,14 @@ export function NextcloudAlertNotifier() {
|
||||
void start();
|
||||
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 now = Date.now();
|
||||
if (now - lastResume < RESUME_MIN_MS) return;
|
||||
lastResume = now;
|
||||
pausedRef.current = false;
|
||||
void start();
|
||||
};
|
||||
|
||||
@@ -143,4 +143,16 @@ describe('ReminderNotifier', () => {
|
||||
});
|
||||
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,
|
||||
} from '@/lib/reminder-notify';
|
||||
import {
|
||||
listReminders,
|
||||
REMINDERS_CHANGED_EVENT,
|
||||
type Reminder,
|
||||
ReminderRequestError,
|
||||
listReminders,
|
||||
} from '@/lib/reminders-api';
|
||||
|
||||
/** Abstand der Abfrage beim Server. */
|
||||
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). */
|
||||
const TICK_MS = 10_000;
|
||||
const BODY_MAX = 200;
|
||||
@@ -87,7 +89,14 @@ export function ReminderNotifier() {
|
||||
const poll = setInterval(() => void load(), POLL_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 now = Date.now();
|
||||
if (now - lastResume < RESUME_MIN_MS) return;
|
||||
lastResume = now;
|
||||
pausedRef.current = false;
|
||||
void load();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user