From 631a20c9b3ae1ca1aef6682d7cd2ec0af9007b52 Mon Sep 17 00:00:00 2001
From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com>
Date: Fri, 21 Aug 2026 00:33:56 +0200
Subject: [PATCH] fix: stabilize update telemetry layout
Render size, speed, and ETA in dedicated fixed-width columns with tabular numerals so rapidly changing values no longer shift neighboring metrics horizontally.
---
renderer/app.js | 11 +++++++++--
renderer/index.html | 8 +++++++-
renderer/styles.css | 17 +++++++++++++++--
tests/startup-renderer.test.js | 3 ++-
tests/ui-smoke.js | 4 ++--
5 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/renderer/app.js b/renderer/app.js
index 35b857e..b40c078 100644
--- a/renderer/app.js
+++ b/renderer/app.js
@@ -7714,16 +7714,23 @@ function _setUpdateProgress(percent, text, state = 'ready') {
function _setUpdateProgressDetails(progress) {
const details = document.getElementById('updateProgressDetails');
if (!details) return;
+ const size = document.getElementById('updateProgressSize');
+ const speed = document.getElementById('updateProgressSpeed');
+ const eta = document.getElementById('updateProgressEta');
const downloaded = Math.max(0, Number(progress?.bytesDownloaded) || 0);
const total = Math.max(0, Number(progress?.bytesTotal) || 0);
if (total <= 0) {
- details.textContent = '';
+ if (size) size.textContent = '';
+ if (speed) speed.textContent = '';
+ if (eta) eta.textContent = '';
details.hidden = true;
return;
}
const bytesPerSecond = Math.max(0, Number(progress?.bytesPerSecond) || 0);
const etaSeconds = Number.isFinite(Number(progress?.etaSeconds)) ? Math.max(0, Number(progress.etaSeconds)) : 0;
- details.textContent = `${formatSize(downloaded)} / ${formatSize(total)} · ${formatSize(bytesPerSecond)}/s · ETA ${formatTime(etaSeconds)}`;
+ if (size) size.textContent = `${formatSize(downloaded)} / ${formatSize(total)}`;
+ if (speed) speed.textContent = `${formatSize(bytesPerSecond)}/s`;
+ if (eta) eta.textContent = `ETA ${formatTime(etaSeconds)}`;
details.hidden = false;
}
diff --git a/renderer/index.html b/renderer/index.html
index 209581a..7636850 100644
--- a/renderer/index.html
+++ b/renderer/index.html
@@ -162,7 +162,13 @@
diff --git a/renderer/styles.css b/renderer/styles.css
index 984ca45..3c730ec 100644
--- a/renderer/styles.css
+++ b/renderer/styles.css
@@ -2520,19 +2520,32 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
display: flex;
align-items: baseline;
justify-content: space-between;
+ flex-wrap: wrap;
gap: 12px;
}
#updateProgressDetails {
min-width: 0;
- overflow: hidden;
+ display: grid;
+ grid-template-columns: 19ch auto 11ch auto 10ch;
+ align-items: baseline;
+ gap: 5px;
color: var(--text-dim);
font-size: 11px;
+ font-variant-numeric: tabular-nums;
line-height: 1.35;
- text-overflow: ellipsis;
white-space: nowrap;
}
+.update-progress-separator {
+ text-align: center;
+}
+
+#updateProgressSpeed,
+#updateProgressEta {
+ text-align: right;
+}
+
#updateProgressText {
min-width: 34px;
max-width: 100%;
diff --git a/tests/startup-renderer.test.js b/tests/startup-renderer.test.js
index bd05f09..14c771f 100644
--- a/tests/startup-renderer.test.js
+++ b/tests/startup-renderer.test.js
@@ -176,5 +176,6 @@ test('header occupies its final geometry before asynchronous initialization', ()
assert.match(css, /\.header-update-button\.update-available:hover\s*\{[^}]*background:\s*var\(--success-end\);[^}]*color:\s*#000;/su);
assert.match(css, /\.update-dialog\s*\{[^}]*width:\s*min\(576px,\s*100%\);/su);
assert.match(css, /\.update-release-notes\s*\{[^}]*height:\s*min\(220px,\s*42vh\);/su);
- assert.match(html, /class="update-progress-footer"[\s\S]*id="updateProgressDetails"[\s\S]*id="updateProgressText"/u);
+ assert.match(html, /class="update-progress-footer"[\s\S]*id="updateProgressDetails"[\s\S]*id="updateProgressSize"[\s\S]*id="updateProgressSpeed"[\s\S]*id="updateProgressEta"[\s\S]*id="updateProgressText"/u);
+ assert.match(css, /#updateProgressDetails\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*19ch auto 11ch auto 10ch;[^}]*font-variant-numeric:\s*tabular-nums;/su);
});
diff --git a/tests/ui-smoke.js b/tests/ui-smoke.js
index 38d06a7..1225f11 100644
--- a/tests/ui-smoke.js
+++ b/tests/ui-smoke.js
@@ -2480,7 +2480,7 @@ setTimeout(async () => {
messageText: document.getElementById('updateMessage').textContent,
progressLabel: progress.getAttribute('aria-label'),
progressText: progress.getAttribute('aria-valuetext'),
- progressDetails: document.getElementById('updateProgressDetails').textContent,
+ progressDetails: ['updateProgressSize', 'updateProgressSpeed', 'updateProgressEta'].map(id => document.getElementById(id).textContent).join('|'),
progressState: progress.dataset.state,
progressColor: getComputedStyle(progress).backgroundColor,
progressTrackWidth: progress.parentElement.getBoundingClientRect().width,
@@ -2490,7 +2490,7 @@ setTimeout(async () => {
})()\`);
check('Busy update keeps its progress dialog open with a dangerous download cancellation action', busyUpdateState.display === 'flex' && busyUpdateState.hidden === 'false' && busyUpdateState.closeDisabled === true && busyUpdateState.dismissDisabled === false && busyUpdateState.dismissText === 'Download abbrechen' && busyUpdateState.dismissDanger === true && busyUpdateState.headerHidden === false);
check('Update progress exposes an accessible live value', busyUpdateState.progressLabel === 'Update-Fortschritt' && busyUpdateState.progressText === 'Download 50%');
- check('Update progress shows downloaded size, total size, speed, and ETA', busyUpdateState.progressDetails === '40.0 MB / 100.0 MB · 8.0 MB/s · ETA 00:08');
+ check('Update progress shows downloaded size, total size, speed, and ETA in stable columns', busyUpdateState.progressDetails === '40.0 MB / 100.0 MB|8.0 MB/s|ETA 00:08');
check('Update download progress is green, wide, and places its status below the line', busyUpdateState.progressState === 'downloading' && busyUpdateState.progressColor === 'rgb(117, 211, 155)' && busyUpdateState.progressTrackWidth >= 390 && busyUpdateState.progressTextTop >= busyUpdateState.progressTrackBottom);
check('Busy update preserves the available version subtitle while progress stays below the bar', busyUpdateState.messageHidden === false && busyUpdateState.messageText === 'Update v9.9.9 verfügbar');