perf(ui): calm large-queue live updates

Throttle visible download snapshots at 500 ms from 250 queue items, align speed visualizations to the same cadence, preserve small-queue responsiveness, and retain the stronger protection for very large queues.
This commit is contained in:
Sucukdeluxe
2026-08-17 13:39:53 +02:00
parent a8a5ca4b40
commit fe3cb36731
7 changed files with 32 additions and 24 deletions
+8
View File
@@ -4,6 +4,14 @@ All notable changes to Multi-Debrid Downloader are documented in this file.
## [Unreleased] ## [Unreleased]
## [2.0.44] - 2026-08-17
### Live updates
- Reduced visible download, remaining-volume, speed, ETA, and availability refresh churn for queues with 250 or more items by using a calm 500 ms cadence.
- Updated live speed sparklines and the bandwidth chart to redraw every 500 ms instead of every 250 ms.
- Kept small queues responsive and retained the stronger 700 ms protection for queues with 1,500 or more items.
## [2.0.43] - 2026-08-16 ## [2.0.43] - 2026-08-16
### Product identity ### Product identity
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "multi-debrid-downloader", "name": "multi-debrid-downloader",
"version": "2.0.43", "version": "2.0.44",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "multi-debrid-downloader", "name": "multi-debrid-downloader",
"version": "2.0.43", "version": "2.0.44",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"adm-zip": "0.6.0", "adm-zip": "0.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "multi-debrid-downloader", "name": "multi-debrid-downloader",
"version": "2.0.43", "version": "2.0.44",
"description": "Desktop downloader", "description": "Desktop downloader",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",
+6 -10
View File
@@ -6551,11 +6551,9 @@ export class DownloadManager extends EventEmitter {
const emitDelay = this.session.running const emitDelay = this.session.running
? itemCount >= 1500 ? itemCount >= 1500
? 700 ? 700
: itemCount >= 700 : itemCount >= 250
? 500 ? 500
: itemCount >= 250 : 150
? 300
: 150
: 200; : 200;
this.stateEmitTimer = setTimeout(() => { this.stateEmitTimer = setTimeout(() => {
this.stateEmitTimer = null; this.stateEmitTimer = null;
@@ -10568,12 +10566,10 @@ export class DownloadManager extends EventEmitter {
let lastLoggedPercent = -1; let lastLoggedPercent = -1;
const itemCount = this.itemCount; const itemCount = this.itemCount;
const uiUpdateIntervalMs = itemCount >= 1500 const uiUpdateIntervalMs = itemCount >= 1500
? 500 ? 700
: itemCount >= 700 : itemCount >= 250
? 350 ? 500
: itemCount >= 250 : 120;
? 220
: 120;
let lastUiEmitAt = 0; let lastUiEmitAt = 0;
const stallTimeoutMs = getDownloadStallTimeoutMs(); const stallTimeoutMs = getDownloadStallTimeoutMs();
const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000)); const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000));
+3 -3
View File
@@ -880,7 +880,7 @@ const historyRetentionLabels: Record<RendererSettings["historyRetentionMode"], s
const AUTO_RENDER_PACKAGE_LIMIT = 260; const AUTO_RENDER_PACKAGE_LIMIT = 260;
export function getSnapshotRenderDelay(itemCount: number, running: boolean, activeTab: MainView): number { export function getSnapshotRenderDelay(itemCount: number, running: boolean, activeTab: MainView): number {
let delay = itemCount >= 700 ? 0 : itemCount >= 250 ? 50 : 100; let delay = itemCount >= 250 ? 0 : 100;
if (!running) delay = Math.min(delay, 200); if (!running) delay = Math.min(delay, 200);
if (activeTab !== "downloads") delay = Math.max(delay, 800); if (activeTab !== "downloads") delay = Math.max(delay, 800);
return delay; return delay;
@@ -1272,7 +1272,7 @@ const BandwidthChart = memo(function BandwidthChart({ running, paused, speedHist
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const interval = setInterval(() => { const interval = setInterval(() => {
drawChart(); drawChart();
}, reducedMotion ? 1000 : 250); }, reducedMotion ? 1000 : 500);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [drawChart, running, paused]); }, [drawChart, running, paused]);
@@ -1374,7 +1374,7 @@ const DownloadSpeedSparkline = memo(function DownloadSpeedSparkline({ speedBps,
draw(); draw();
}; };
const id = window.setInterval(tick, 250); const id = window.setInterval(tick, 500);
return () => window.clearInterval(id); return () => window.clearInterval(id);
}, []); }, []);
+4
View File
@@ -50,6 +50,10 @@ describe("desktop shell", () => {
expect(getSnapshotRenderDelay(2_470, true, "statistics")).toBe(800); expect(getSnapshotRenderDelay(2_470, true, "statistics")).toBe(800);
}); });
it("renders medium download queues immediately after their calm half-second snapshot", () => {
expect(getSnapshotRenderDelay(661, true, "downloads")).toBe(0);
});
it("keeps application menus mounted for animated opening and closing", () => { it("keeps application menus mounted for animated opening and closing", () => {
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8"); const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8");
const css = readFileSync(new URL("../src/renderer/styles.css", import.meta.url), "utf8"); const css = readFileSync(new URL("../src/renderer/styles.css", import.meta.url), "utf8");
+1 -1
View File
@@ -507,7 +507,7 @@ describe("bandwidth chart palette", () => {
expect(chartBlock).toContain('role="img"'); expect(chartBlock).toContain('role="img"');
expect(chartBlock).toContain('aria-label="Bandbreitenverlauf der letzten 60 Sekunden"'); expect(chartBlock).toContain('aria-label="Bandbreitenverlauf der letzten 60 Sekunden"');
expect(chartBlock).toContain('window.matchMedia("(prefers-reduced-motion: reduce)")'); expect(chartBlock).toContain('window.matchMedia("(prefers-reduced-motion: reduce)")');
expect(chartBlock).toContain("reducedMotion ? 1000 : 250"); expect(chartBlock).toContain("reducedMotion ? 1000 : 500");
}); });
it("asks for confirmation before deleting all saved download statistics", () => { it("asks for confirmation before deleting all saved download statistics", () => {