From 8654cda7dc5030f8a643f60627b7ef43bb5cb7fe Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Tue, 22 Sep 2026 06:25:00 +0200 Subject: [PATCH] Show publication time in larger update version message --- PROJECT_MEMORY.md | 1 + lib/updater.js | 1 + renderer/app.js | 11 ++++++++++- renderer/i18n.js | 2 ++ renderer/styles.css | 2 +- tests/i18n.test.js | 2 ++ tests/startup-renderer.test.js | 15 +++++++++++++++ tests/updater-version.test.js | 2 ++ 8 files changed, 34 insertions(+), 2 deletions(-) diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md index 604a0a6..e93e32c 100644 --- a/PROJECT_MEMORY.md +++ b/PROJECT_MEMORY.md @@ -97,6 +97,7 @@ npm audit --omit=dev ## Zuletzt verifiziert +- Update-Dialog nach `2.1.49`: Versionsmeldung von 12 auf 14 px erhöht. Updater reicht `published_at` als `publishedAt` weiter; Anzeige ergänzt lokale Veröffentlichungszeit als `TT.MM.JJJJ - HH:mm`, fehlende/ungültige Werte bleiben ausgeblendet. Deutsche und englische Übersetzung mit Datum geprüft. 40 Übersetzungs-/Updater-Tests, zwei gezielte Renderer-Tests und Lint ohne Warnungen erfolgreich. Laufende Entwicklung zeigt im lokalen Test `Update v2.1.50 verfügbar (22.09.2026 - 19:30)`; kein Release veröffentlicht. - Update-Button nach `2.1.49`: feste Breite für Button und sichtbaren Platzhalter von 146 auf 166 px erhöht; Beschriftung ohne Umbruch und ohne Schrumpfen. Gezielter Header-Regressionstest und Lint erfolgreich. In der laufenden Entwicklung für Deutsch und Englisch jeweils eine Textzeile und ausreichender Innenabstand per DOM-Messung bestätigt. Lokaler Anzeigetest für `2.1.50` im ignorierten Entwicklungslauncher aktiv; Download und Installation dort gesperrt, kein tatsächliches Release. Entwicklerversion automatisch neu gestartet. - Nachbesserung gegen abgeschnittenes letztes Zeichen: gezielter Electron-Layouttest für beide Sprachen und sechs Breiten sowie Lint erfolgreich. Screenshot mit vollständigem Text geprüft; automatischer Entwicklungsneustart bestätigt. Kein Release. - Einstellungssuche: auf Nutzerwunsch Lupe rechts (12 px Innenabstand), Suchtext mit symmetrischem 32-px-Padding zentriert. Gezielter Electron-Layouttest prüft Zentrierung, Symbolposition und vollständige Platzhalter in beiden Sprachen bei sechs Breiten; Lint erfolgreich, Screenshot visuell geprüft. Automatischer Entwicklungsneustart bestätigt; noch nicht veröffentlicht. diff --git a/lib/updater.js b/lib/updater.js index 3aebc59..db288c3 100644 --- a/lib/updater.js +++ b/lib/updater.js @@ -185,6 +185,7 @@ async function checkForUpdate(options = {}) { currentVersion, remoteVersion, transportTag, + publishedAt: release.published_at || null, releaseUrl: release.html_url, assetUrl: setupAsset.browser_download_url, assetSize: setupAsset.size, diff --git a/renderer/app.js b/renderer/app.js index 35ca8f6..f5f496b 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -9558,6 +9558,14 @@ function _renderUpdateReleaseNotes(container, value) { return container.childElementCount > 0; } +function formatUpdatePublicationDate(value) { + if (!value) return ''; + const date = new Date(value); + if (!Number.isFinite(date.getTime())) return ''; + const pad = number => String(number).padStart(2, '0'); + return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()} - ${pad(date.getHours())}:${pad(date.getMinutes())}`; +} + function showUpdateBanner(info) { if (!info) return; _knownUpdateInfo = { ...info, available: true }; @@ -9575,7 +9583,8 @@ function showUpdateBanner(info) { const installButton = document.getElementById('installUpdateBtn'); if (title) title.textContent = 'Eine neue Version ist verfügbar!'; if (message) { - message.textContent = `Update v${version} verfügbar`; + const publishedAt = formatUpdatePublicationDate(info.publishedAt); + message.textContent = `Update v${version} verfügbar${publishedAt ? ` (${publishedAt})` : ''}`; message.hidden = false; } if (notes && notesBody) { diff --git a/renderer/i18n.js b/renderer/i18n.js index 136541c..1fe5127 100644 --- a/renderer/i18n.js +++ b/renderer/i18n.js @@ -804,6 +804,7 @@ if (exact) return `${leading}${exact}${trailing}`; const patterns = target === 'en' ? [ + [/^Update v([\d.]+) verfügbar \(([^)]+)\)$/, 'Update v$1 available ($2)'], [/^Update v(.+) verfügbar$/, 'Update v$1 available'], [/^Wiederhergestellte Warteschlange startet in (.+) s \((.+) Jobs\)\.$/, 'Restored queue starts in $1 s ($2 jobs).'], [/^Login ok, Upload-Form bereit \(Dateifeld: (.+)\)$/, 'Login successful, upload form ready (file field: $1)'], @@ -912,6 +913,7 @@ [/^Update v(.+) verfügbar\. Klicken zum Installieren\.$/, 'Update v$1 available. Click to install.'] ] : [ + [/^Update v([\d.]+) available \(([^)]+)\)$/, 'Update v$1 verfügbar ($2)'], [/^Update v(.+) available$/, 'Update v$1 verfügbar'], [/^Sleep in (\d+)s\.\.\.$/, 'Ruhezustand in $1s...'], [/^Shut down in (\d+)s\.\.\.$/, 'Herunterfahren in $1s...'], diff --git a/renderer/styles.css b/renderer/styles.css index 3dbcd54..aac9449 100644 --- a/renderer/styles.css +++ b/renderer/styles.css @@ -2952,7 +2952,7 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; } .update-dialog-copy p { margin: 0; color: var(--text-muted); - font-size: 12px; + font-size: 14px; line-height: 1.55; } diff --git a/tests/i18n.test.js b/tests/i18n.test.js index 04d6608..9dc1252 100644 --- a/tests/i18n.test.js +++ b/tests/i18n.test.js @@ -176,6 +176,8 @@ test('English is the fallback language and German remains selectable', () => { test('translations cover static labels and interpolated status text in both languages', () => { assert.equal(translateText('Einstellungen', 'en'), 'Settings'); assert.equal(translateText('Update v2.1.0 verfügbar', 'en'), 'Update v2.1.0 available'); + assert.equal(translateText('Update v2.1.50 verfügbar (22.09.2026 - 19:30)', 'en'), 'Update v2.1.50 available (22.09.2026 - 19:30)'); + assert.equal(translateText('Update v2.1.50 available (22.09.2026 - 19:30)', 'de'), 'Update v2.1.50 verfügbar (22.09.2026 - 19:30)'); assert.equal(translateText('Settings', 'de'), 'Einstellungen'); assert.equal(translateText('Update v2.1.0 available', 'de'), 'Update v2.1.0 verfügbar'); assert.equal(translateText('Alle Status', 'en'), 'Any status'); diff --git a/tests/startup-renderer.test.js b/tests/startup-renderer.test.js index 937386d..4bd4979 100644 --- a/tests/startup-renderer.test.js +++ b/tests/startup-renderer.test.js @@ -5483,6 +5483,21 @@ test('upload sidebar renders and updates the remaining upload size', () => { assert.match(appSource, /_setUploadTelemetryText\(['"]uploadTelemetryRemainingSize['"],\s*formatBytes\(stats\.bytesRemaining\)\)/u); }); +test('update publication dates use local time and omit missing or invalid metadata', () => { + const vm = require('node:vm'); + const source = fs.readFileSync(path.join(__dirname, '../renderer/app.js'), 'utf8'); + const formatter = source.slice(source.indexOf('function formatUpdatePublicationDate('), source.indexOf('function showUpdateBanner(')); + const context = vm.createContext({}); + vm.runInContext(formatter, context); + assert.equal(vm.runInContext("formatUpdatePublicationDate('2026-09-22T19:30:00')", context), '22.09.2026 - 19:30'); + for (const value of [null, undefined, '', 'invalid']) { + context.value = value; + assert.equal(vm.runInContext('formatUpdatePublicationDate(value)', context), ''); + } + const css = fs.readFileSync(path.join(__dirname, '../renderer/styles.css'), 'utf8'); + assert.match(css, /\.update-dialog-copy p\s*\{[^}]*font-size:\s*14px;/su); +}); + test('header occupies its final geometry before asynchronous initialization', () => { const projectRoot = path.join(__dirname, '..'); const html = fs.readFileSync(path.join(projectRoot, 'renderer', 'index.html'), 'utf8'); diff --git a/tests/updater-version.test.js b/tests/updater-version.test.js index 09e3f4f..8137082 100644 --- a/tests/updater-version.test.js +++ b/tests/updater-version.test.js @@ -231,6 +231,7 @@ test('update preparation refreshes a cached release before downloading the insta name: `Multi-Hoster-Upload v${version}`, tag_name: `v${version}`, html_url: `https://update.invalid/releases/${version}`, + published_at: '2026-09-22T17:30:00Z', body: `Release ${version}`, assets: [ { @@ -288,6 +289,7 @@ test('update preparation refreshes a cached release before downloading the insta }; const oldCheck = await isolatedUpdater.checkForUpdate(); assert.equal(oldCheck.remoteVersion, oldVersion); + assert.equal(oldCheck.publishedAt, '2026-09-22T17:30:00Z'); global.fetch = async url => { const value = String(url);