From 6d721d88bee535f0c8e8e6c1c4980b49a64b0aac Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Mon, 7 Sep 2026 03:04:12 +0200 Subject: [PATCH] fix(queue): honor visible order and keep transfer metrics on one line --- CHANGELOG.md | 5 +++ PROJECT_MEMORY.md | 11 +++++ scripts/smoke-test-queue-cards.js | 25 ++++++++++- scripts/smoke-test-workspace-ui.js | 4 +- src/index.html | 1 - src/main.ts | 44 +++---------------- .../queue-addition.production-path.test.ts | 28 ++++++++++++ src/renderer-locale-de.ts | 2 - src/renderer-locale-en.ts | 2 - src/renderer-queue.production-path.test.ts | 11 +++++ src/renderer-queue.ts | 19 +++++--- src/renderer-settings-autosave.test.ts | 2 +- src/renderer-settings.ts | 4 -- src/renderer-texts.ts | 3 -- src/renderer.ts | 1 - src/style-modules.production-path.test.ts | 2 +- src/styles-workflows.css | 25 +++++++++-- 17 files changed, 124 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa3010..e6c87b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Start pending downloads in the visible queue order, including manually reordered entries. Retire the automatic short-job prioritization, including for existing configurations. +- Keep transfer size, percentage, speed and remaining time on one compact line below 1 GB as well. Preserve wrapping for error messages and full values in tooltips. + ## 1.0.22 - 2026-09-06 - Show small round profile pictures beside streamer names in the sidebar, with an initial fallback when an image is unavailable. diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md index 0bb6280..0cbf329 100644 --- a/PROJECT_MEMORY.md +++ b/PROJECT_MEMORY.md @@ -8,6 +8,8 @@ Am 6. September 2026 nach einem Festplatten-Reset aus den vorhandenen Remote-Rep Aktueller veröffentlichter Stand: **1.0.22**, Tag `v1.0.22`, veröffentlicht am 6. September 2026 auf GitHub und Forgejo. Der Tag zeigt auf `a526bd3541fd1d909dae0060ba92d64347668099`; nachfolgende Memory-Commits gehören nicht zum Installer. +Unveröffentlichte Änderungen vom 7. September 2026: Queue startet wartende Downloads strikt in der sichtbaren Reihenfolge; kompakte, einzeilige Fortschrittsanzeige auch unter 1 GB. + ## Git und maßgeblicher Stand - Arbeitsbranch: `public-v1`, Tracking: `origin/public-v1`. @@ -21,6 +23,8 @@ Aktueller veröffentlichter Stand: **1.0.22**, Tag `v1.0.22`, veröffentlicht am ## Letzte Änderungen - Release 1.0.22 abgeschlossen: runde Profilbilder in der Streamerliste; auf GitHub und Forgejo mit identischen Assets sowie englischem/deutschem Changelog veröffentlicht und geprüft. +- 7. September 2026: Automatische Bevorzugung kurzer Downloads/Clips entfernt, damit die Queue von oben nach unten startet und manuelles Umsortieren respektiert. Die frühere Scheduler-Einstellung ist aus der Oberfläche entfernt; vorhandene Konfigurationen werden auf deaktiviert normalisiert, das alte Feld bleibt kompatibel lesbar. Parallele Downloads werden weiterhin in sichtbarer Reihenfolge gestartet. +- 7. September 2026: Transferzeile zeigt die Datenmenge ohne redundantes „heruntergeladen“, daneben Prozent, Tempo und Restzeit in 11 px ohne Zeilenumbruch. Vollständige Angaben bleiben im Tooltip, Fehlermeldungen weiterhin mehrzeilig in 12 px. 500 MB, 1003 MB und 1,35 GB in beiden Sprachen und Themes ohne abgeschnittene Werte gemessen. - Linke Streamerliste zeigt runde Profilbilder in 24 × 24 px direkt vor dem Namen. Verwendet den bestehenden Profil-Cache und dessen Vorladen/Hintergrundaktualisierung ohne zusätzliche Profilanfragen. Bilder werden in der vorhandenen Zeile nachgeladen und erhalten den Tastaturfokus; fehlende oder defekte Bilder zeigen den Anfangsbuchstaben. Bildklick wählt den Streamer, lange Namen werden einzeilig gekürzt und bleiben im Tooltip lesbar. Build, 37 gezielte Tests und vollständige Workspace-UI-Prüfung inklusive Laden, Fallback, Auswahl und Hell/Dunkel erfolgreich; Lint ohne Fehler bei 15 bestehenden Warnungen. Screenshots unter `tmp_streamer-avatar-artifacts/`. Mit 1.0.22 veröffentlicht, auch im Hot-Dev aktiv. - Release 1.0.21 abgeschlossen: alle Queue-Nachbesserungen seit 1.0.20 enthalten; auf GitHub und Forgejo mit identischen Assets sowie englischem/deutschem Changelog veröffentlicht und geprüft. - Streamer in Queue-Details verwendet jetzt den aufgelösten Twitch-Anzeigenamen mit originaler Groß-/Kleinschreibung, etwa `xRohat` statt `xrohat`. Gespeicherter Login bleibt unverändert; noch unbekannte Anzeigenamen fallen auf den Login zurück. Nachgeladene Profilnamen aktualisieren vorhandene Queue-Cards sofort und werden bei der Render-Erkennung berücksichtigt. Build, 43 gezielte Tests, Queue-UI-Test einschließlich nachgeladener Groß-/Kleinschreibung und Lint erfolgreich (15 bestehende Warnungen). Mit 1.0.21 veröffentlicht. @@ -145,3 +149,10 @@ Abschließende Verifikation von Release 1.0.22 am 6. September 2026: - Alle Assets von beiden Plattformen heruntergeladen und bytegenau per SHA-256/SHA-512 gegen den lokalen Build geprüft. Nach Veröffentlichung öffentliche Asset-URLs, Latest-Release und Update-Metadaten beider Plattformen einschließlich GitHubs Update-Kanal erfolgreich verifiziert. - Installer: 110.441.936 Bytes, SHA-256 `bbfc1ab5ac2e7e7d0d5b5e86c7c757ab2c3e03e854a4e0a830156283af2131ba`. Blockmap: 117.025 Bytes. Update-Metadaten: 368 Bytes mit passender Version, Dateigröße und SHA-512-Prüfsumme. - Keine produktiven Datenbankänderungen und keine vollständige Live-Update-Installation einer alten Version auf diesem Rechner. Hot-Dev bleibt aktiv. Ignorierte Prüfdateien liegen in `tmp_release_1.0.22/`, Testprotokolle im Windows-Temp-Verzeichnis. + +7. September 2026, Queue-Reihenfolge und Transferzeile (noch nicht veröffentlicht): + +- Build und alle 635 Unit-Tests in 83 Testdateien erfolgreich. Neuer Produktionstest prüft die sichtbare Startreihenfolge trotz früher aktivierter Priorisierung, manuelles Umsortieren und das Überspringen nicht wartender Einträge. +- Queue-UI-Prüfung in Deutsch/Englisch und Hell/Dunkel einschließlich einzeiliger Mengen-/Tempo-/Restzeitanzeige erfolgreich; Screenshots visuell geprüft. Der Animationstest liest Transitions nun direkt nach dem Umschalten aus, damit langsame Frames die kurze Animation nicht vor der Messung beenden. +- Vollständiger Workspace-UI-Test und isolierter Einstellungen-Autosave-Test erfolgreich, keine gemeldeten Laufzeitfehler. Die Checkbox-Layoutprüfung verwendet nach Entfernen der Scheduler-Option den vorhandenen Duplikatschutz. +- Lint ohne Fehler bei 15 bestehenden Warnungen; Security-/Public-Manifest-Prüfung erfolgreich. Keine Live-Downloads oder produktiven Datenbankänderungen für die Tests. Release-Version bleibt 1.0.22; Änderungen auf public-v1 für die nächste Veröffentlichung vorbereitet. diff --git a/scripts/smoke-test-queue-cards.js b/scripts/smoke-test-queue-cards.js index ba3d8d5..cd92c1e 100644 --- a/scripts/smoke-test-queue-cards.js +++ b/scripts/smoke-test-queue-cards.js @@ -79,6 +79,29 @@ async function main() { assert(card.overflow <= 1, `Card overflow: ${JSON.stringify(card)}`); } await win.locator('.queue-section').screenshot({ path: path.join(artifacts, `queue-${theme}-${language}.png`) }); + for (const size of ['500.0 MB', '1003.0 MB', '1.35 GB']) { + const transfer = await win.evaluate(({ size, language }) => { + const item = queue.find((candidate) => candidate.id === 'running'); + item.progress = 32.2; + item.progressStatus = `${size} ${language === 'de' ? 'heruntergeladen' : 'downloaded'}`; + item.speed = '23.1 MB/s'; + item.eta = '12m 34s'; + updateQueueItemProgress({ id: item.id }); + const row = document.querySelector('[data-id="running"] .queue-progress-info'); + const status = row.querySelector('.queue-progress-status'); + const metrics = row.querySelector('.queue-progress-metrics'); + return { + height: row.getBoundingClientRect().height, + sameLine: status.getBoundingClientRect().top === metrics.getBoundingClientRect().top, + statusVisible: status.scrollWidth <= status.clientWidth, + metricsVisible: metrics.scrollWidth <= metrics.clientWidth, + status: status.textContent, + tooltip: status.title, + }; + }, { size, language }); + assert(transfer.height <= 18 && transfer.sameLine && transfer.statusVisible && transfer.metricsVisible && transfer.status === size && transfer.tooltip.includes(size), `Transfer line (${theme}/${language}): ${JSON.stringify(transfer)}`); + } + await win.locator('[data-id="running"]').screenshot({ path: path.join(artifacts, `transfer-${theme}-${language}.png`) }); const expansionLayout = await win.locator('#queueList').evaluate(async (list) => { const items = [...list.querySelectorAll('.queue-item')]; const originalStyles = [list, ...items].map((element) => element.getAttribute('style')); @@ -139,8 +162,6 @@ async function main() { const details = item.querySelector('.queue-details'); const frame = () => new Promise((resolve) => requestAnimationFrame(resolve)); const sample = async () => { - await frame(); - await frame(); const animations = details.getAnimations(); const slide = animations.find((animation) => animation.transitionProperty === 'grid-template-rows'); if (!slide) throw new Error('Queue details have no height transition'); diff --git a/scripts/smoke-test-workspace-ui.js b/scripts/smoke-test-workspace-ui.js index 24fcd29..0fb5175 100644 --- a/scripts/smoke-test-workspace-ui.js +++ b/scripts/smoke-test-workspace-ui.js @@ -979,14 +979,14 @@ async function run() { window.setSettingsPane('downloads'); window.changeLanguage('de'); updateStatus(UI_TEXT.status.noLogin, false, 'public'); - document.getElementById('smartSchedulerToggle').checked = true; + document.getElementById('duplicatePreventionToggle').checked = true; }); await win.waitForTimeout(240); const downloadSettingsWide = await win.evaluate(() => { const tab = document.getElementById('settingsTab'); const card = tab?.querySelector('.settings-card[data-settings-pane="downloads"]'); const layout = card?.querySelector('.download-settings-layout'); - const checkbox = card?.querySelector('#smartSchedulerToggle'); + const checkbox = card?.querySelector('#duplicatePreventionToggle'); const label = checkbox?.closest('.toggle-row')?.querySelector('span'); const dot = document.getElementById('statusDot'); const text = document.getElementById('statusText'); diff --git a/src/index.html b/src/index.html index 19ef8e1..7187e31 100644 --- a/src/index.html +++ b/src/index.html @@ -900,7 +900,6 @@

Queue und Automatisierung

- diff --git a/src/main.ts b/src/main.ts index 33a2712..ae1ed4e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -447,7 +447,7 @@ const defaultConfig: Config = { filename_template_vod: DEFAULT_FILENAME_TEMPLATE_VOD, filename_template_parts: DEFAULT_FILENAME_TEMPLATE_PARTS, filename_template_clip: DEFAULT_FILENAME_TEMPLATE_CLIP, - smart_queue_scheduler: true, + smart_queue_scheduler: false, performance_mode: DEFAULT_PERFORMANCE_MODE, prevent_duplicate_downloads: true, persist_queue_on_restart: true, @@ -521,7 +521,7 @@ function normalizeConfigTemplates(input: Config): Config { filename_template_parts: normalizeFilenameTemplate(input.filename_template_parts, DEFAULT_FILENAME_TEMPLATE_PARTS), filename_template_clip: normalizeFilenameTemplate(input.filename_template_clip, DEFAULT_FILENAME_TEMPLATE_CLIP), sidebar_split_view: input.sidebar_split_view !== false, - smart_queue_scheduler: input.smart_queue_scheduler !== false, + smart_queue_scheduler: false, performance_mode: normalizePerformanceMode(input.performance_mode), prevent_duplicate_downloads: input.prevent_duplicate_downloads !== false, persist_queue_on_restart: input.persist_queue_on_restart !== false, @@ -1786,7 +1786,7 @@ function getRuntimeMetricsSnapshot(): RuntimeMetricsSnapshot { }, config: { performanceMode: normalizePerformanceMode(config.performance_mode), - smartScheduler: config.smart_queue_scheduler !== false, + smartScheduler: false, metadataCacheMinutes: normalizeMetadataCacheMinutes(config.metadata_cache_minutes), duplicatePrevention: config.prevent_duplicate_downloads !== false } @@ -1810,39 +1810,8 @@ function hasActiveDuplicate(candidate: Pick item.status === 'pending'); - if (!pendingItems.length) return null; - - if (!config.smart_queue_scheduler) { - return pendingItems[0]; - } - - let best = pendingItems[0]; - let bestScore = getQueuePriorityScore(best); - - for (let i = 1; i < pendingItems.length; i += 1) { - const candidate = pendingItems[i]; - const score = getQueuePriorityScore(candidate); - if (score > bestScore) { - best = candidate; - bestScore = score; - } - } - - return best; + return downloadQueue.find((item) => item.status === 'pending') || null; } function parseClockDurationSeconds(duration: string | null): number | null { @@ -6824,8 +6793,7 @@ async function processOneQueueItem(item: QueueItem): Promise { appendDebugLog('queue-item-start', { itemId: item.id, title: item.title, - url: item.url, - smartScore: config.smart_queue_scheduler ? getQueuePriorityScore(item) : 0 + url: item.url }); runtimeMetrics.downloadsStarted += 1; @@ -7107,7 +7075,7 @@ async function processQueue(manualOverride = false): Promise { appendDebugLog('queue-start', { items: downloadQueue.length, - smartScheduler: config.smart_queue_scheduler, + scheduling: 'queue-order', performanceMode: config.performance_mode, parallelDownloads: config.parallel_downloads || 1 }); diff --git a/src/main/domain/queue-addition.production-path.test.ts b/src/main/domain/queue-addition.production-path.test.ts index 8682a8f..397a152 100644 --- a/src/main/domain/queue-addition.production-path.test.ts +++ b/src/main/domain/queue-addition.production-path.test.ts @@ -1,8 +1,36 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; +import { runInNewContext } from 'node:vm'; +import { ModuleKind, ScriptTarget, transpileModule } from 'typescript'; describe('queue addition IPC contract', () => { + it('starts pending downloads in visible order even with a legacy smart scheduler setting', () => { + const source = readFileSync(join(process.cwd(), 'src', 'main.ts'), 'utf8'); + const start = source.indexOf('function pickNextPendingQueueItem'); + const end = source.indexOf('function parseClockDurationSeconds', start); + const context = { + config: { smart_queue_scheduler: true }, + downloadQueue: [ + { id: 'finished', status: 'completed' }, + { id: 'long-first', status: 'pending', duration_str: '10h', createdAt: '2026-09-07T10:00:00Z' }, + { id: 'short-second', status: 'pending', duration_str: '1m', customClip: { durationSec: 60 }, createdAt: '2026-09-07T10:01:00Z' }, + ], + }; + const code = transpileModule(`${source.slice(start, end)}\nglobalThis.pickNext = pickNextPendingQueueItem;`, { + compilerOptions: { module: ModuleKind.None, target: ScriptTarget.ES2022 }, + }).outputText; + const runtime = runInNewContext(`${code}\nglobalThis`, context); + expect(runtime.pickNext().id).toBe('long-first'); + context.downloadQueue[1].status = 'downloading'; + expect(runtime.pickNext().id).toBe('short-second'); + context.downloadQueue[1].status = 'pending'; + context.downloadQueue.reverse(); + expect(runtime.pickNext().id).toBe('short-second'); + context.downloadQueue.forEach((item) => { item.status = 'paused'; }); + expect(runtime.pickNext()).toBeNull(); + }); + it('keeps the legacy queue result and exposes the atomic accepted result separately', () => { const source = readFileSync(join(process.cwd(), 'src', 'main.ts'), 'utf8'); diff --git a/src/renderer-locale-de.ts b/src/renderer-locale-de.ts index 18bc5b5..b324854 100644 --- a/src/renderer-locale-de.ts +++ b/src/renderer-locale-de.ts @@ -88,8 +88,6 @@ const UI_TEXT_DE = { performanceModeStability: 'Max Stabilität', performanceModeBalanced: 'Ausgewogen', performanceModeSpeed: 'Max Geschwindigkeit', - smartSchedulerLabel: 'Smart Queue Scheduler aktivieren', - smartSchedulerHint: 'Bevorzugt kürzere VODs und ältere Queue-Einträge zuerst, damit der Durchsatz gleichmäßig bleibt. Deaktivieren = strikte Einfügereihenfolge.', sidebarSplitViewLabel: 'Streamer und Queue gleichzeitig anzeigen', sidebarSplitViewHint: 'Teilt die linke Seitenleiste gleichmäßig auf. Deaktiviert erscheinen wieder die Umschaltflächen.', streamerInvalid: 'Twitch-Username ungültig (4-25 Zeichen, Buchstaben/Zahlen/Unterstrich).', diff --git a/src/renderer-locale-en.ts b/src/renderer-locale-en.ts index c418a51..737f8c5 100644 --- a/src/renderer-locale-en.ts +++ b/src/renderer-locale-en.ts @@ -88,8 +88,6 @@ const UI_TEXT_EN = { performanceModeStability: 'Max Stability', performanceModeBalanced: 'Balanced', performanceModeSpeed: 'Max Speed', - smartSchedulerLabel: 'Enable smart queue scheduler', - smartSchedulerHint: 'Prefers shorter VODs and older queue entries first so the queue throughput stays steady. Disable to drain in strict insertion order.', sidebarSplitViewLabel: 'Show streamers and queue together', sidebarSplitViewHint: 'Splits the left sidebar evenly. Disable it to restore the Streamer and Queue switches.', streamerInvalid: 'Invalid Twitch username (4-25 chars, letters/digits/underscore).', diff --git a/src/renderer-queue.production-path.test.ts b/src/renderer-queue.production-path.test.ts index af05a67..7a8731e 100644 --- a/src/renderer-queue.production-path.test.ts +++ b/src/renderer-queue.production-path.test.ts @@ -413,6 +413,17 @@ describe('renderer queue production paths', () => { expect(metrics({ status: 'completed', progress: 100, speed: '4 MB/s', eta: '2m' })).toBe('100%'); }); + it('keeps transfer sizes compact while preserving non-transfer status messages', () => { + const runtime = evaluate(fragment('function getQueueProgressStatusText', 'function getQueueProgressMetricsText'), 'getQueueProgressStatusText', { UI_TEXT: { queue: queueText } }); + for (const size of ['500.0 MB', '1003.0 MB', '1.35 GB']) { + for (const suffix of ['downloaded', 'heruntergeladen']) { + expect(runtime.exposed.getQueueProgressStatusText({ status: 'downloading', progressStatus: `${size} ${suffix}` })).toBe(size); + } + } + expect(runtime.exposed.getQueueProgressStatusText({ status: 'downloading', progressStatus: 'Preparing download tools' })).toBe('Preparing download tools'); + expect(runtime.exposed.getQueueProgressStatusText({ status: 'error', last_error: 'Connection failed. Please retry.' })).toBe('Connection failed. Please retry.'); + }); + it('keeps monotonic progress while treating explicit empty telemetry as an authoritative reset', () => { const mergePath = rendererSource.slice( rendererSource.indexOf('function mergeQueueState'), diff --git a/src/renderer-queue.ts b/src/renderer-queue.ts index 69458c8..9bdfe12 100644 --- a/src/renderer-queue.ts +++ b/src/renderer-queue.ts @@ -458,7 +458,10 @@ function getQueueProgressStatusText(item: QueueItem): string { if (item.status === 'paused') return UI_TEXT.queue.statusPaused; if (item.status === 'completed') return UI_TEXT.queue.done; if (item.status === 'error') return UI_TEXT.queue.failed; - if (item.status === 'downloading' && item.progressStatus) return item.progressStatus; + if (item.status === 'downloading' && item.progressStatus) { + const downloadedSize = item.progressStatus.match(/^([\d.,]+\s(?:B|KB|MB|GB|TB)) (?:downloaded|heruntergeladen)$/); + return downloadedSize ? downloadedSize[1] : item.progressStatus; + } if (item.currentPart && item.totalParts) { return `${UI_TEXT.queue.part} ${item.currentPart}/${item.totalParts}`; } @@ -565,8 +568,14 @@ function updateQueueItemProgress(progress: DownloadProgress): void { bar.className = 'queue-progress-bar'; if (wrap) wrap.setAttribute('aria-valuenow', String(Math.round(pct))); } - if (status) status.textContent = getQueueProgressStatusText(item); - if (metrics) metrics.textContent = getQueueProgressMetricsText(item); + if (status) { + status.textContent = getQueueProgressStatusText(item); + status.title = item.progressStatus || status.textContent; + } + if (metrics) { + metrics.textContent = getQueueProgressMetricsText(item); + metrics.title = metrics.textContent; + } syncQueueRecordingHealth(el, item); } @@ -727,8 +736,8 @@ function renderQueue(): void { ${safeStatusLabel}
- ${safeProgressStatus} - ${safeProgressMetrics} + ${safeProgressStatus} + ${safeProgressMetrics}
diff --git a/src/renderer-settings-autosave.test.ts b/src/renderer-settings-autosave.test.ts index 87ad74a..8a6b0bb 100644 --- a/src/renderer-settings-autosave.test.ts +++ b/src/renderer-settings-autosave.test.ts @@ -11,7 +11,7 @@ type Input = { const inputIds = [ 'clientId', 'clientSecret', 'sidebarSplitViewToggle', 'downloadMode', 'partMinutes', 'parallelDownloads', - 'performanceMode', 'smartSchedulerToggle', 'duplicatePreventionToggle', 'persistQueueToggle', + 'performanceMode', 'duplicatePreventionToggle', 'persistQueueToggle', 'autoResumeQueueToggle', 'notifyEachCompletionToggle', 'streamlinkDisableAdsToggle', 'downloadChatReplayToggle', 'captureLiveChatToggle', 'logStreamEventsToggle', 'autoResumeLiveRecordingToggle', 'autoMergeResumedPartsToggle', 'deletePartsAfterMergeToggle', 'discordWebhookUrl', 'discordNotifyLiveStartToggle', 'discordNotifyLiveEndToggle', diff --git a/src/renderer-settings.ts b/src/renderer-settings.ts index b499102..8937bbf 100644 --- a/src/renderer-settings.ts +++ b/src/renderer-settings.ts @@ -900,7 +900,6 @@ function collectDownloadSettingsPayload(): Partial { part_minutes: parseInt(byId('partMinutes').value, 10) || 120, parallel_downloads: parseInt(byId('parallelDownloads').value, 10) || 1, performance_mode: byId('performanceMode').value as 'stability' | 'balanced' | 'speed', - smart_queue_scheduler: byId('smartSchedulerToggle').checked, prevent_duplicate_downloads: byId('duplicatePreventionToggle').checked, persist_queue_on_restart: byId('persistQueueToggle').checked, auto_resume_queue_on_startup: byId('autoResumeQueueToggle').checked, @@ -965,7 +964,6 @@ function getSettingsFingerprint(payload: Partial): string { effective.part_minutes ?? 120, effective.parallel_downloads ?? 1, effective.performance_mode ?? 'balanced', - effective.smart_queue_scheduler !== false, effective.prevent_duplicate_downloads !== false, effective.persist_queue_on_restart !== false, effective.auto_resume_queue_on_startup === true, @@ -1007,7 +1005,6 @@ function syncSettingsFormFromConfig(syncSecrets = true): void { byId('partMinutes').value = String((config.part_minutes as number) || 120); byId('parallelDownloads').value = String((config.parallel_downloads as number) || 1); byId('performanceMode').value = (config.performance_mode as string) || 'balanced'; - byId('smartSchedulerToggle').checked = (config.smart_queue_scheduler as boolean) !== false; byId('duplicatePreventionToggle').checked = (config.prevent_duplicate_downloads as boolean) !== false; byId('persistQueueToggle').checked = (config.persist_queue_on_restart as boolean) !== false; byId('autoResumeQueueToggle').checked = (config.auto_resume_queue_on_startup as boolean) === true; @@ -1151,7 +1148,6 @@ function initSettingsAutoSave(): void { 'sidebarSplitViewToggle', 'parallelDownloads', 'performanceMode', - 'smartSchedulerToggle', 'duplicatePreventionToggle', 'persistQueueToggle', 'autoResumeQueueToggle', diff --git a/src/renderer-texts.ts b/src/renderer-texts.ts index 534cbb5..74d42f3 100644 --- a/src/renderer-texts.ts +++ b/src/renderer-texts.ts @@ -269,9 +269,6 @@ function applyLanguageToStaticUI(): void { setText('performanceModeStability', UI_TEXT.static.performanceModeStability); setText('performanceModeBalanced', UI_TEXT.static.performanceModeBalanced); setText('performanceModeSpeed', UI_TEXT.static.performanceModeSpeed); - setText('smartSchedulerLabel', UI_TEXT.static.smartSchedulerLabel); - setTitle('smartSchedulerLabel', UI_TEXT.static.smartSchedulerHint); - setTitle('smartSchedulerToggle', UI_TEXT.static.smartSchedulerHint); setText('duplicatePreventionLabel', UI_TEXT.static.duplicatePreventionLabel); setText('persistQueueLabel', UI_TEXT.static.persistQueueLabel); setText('autoResumeQueueLabel', UI_TEXT.static.autoResumeQueueLabel); diff --git a/src/renderer.ts b/src/renderer.ts index 0dfc10d..3d5e09e 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -35,7 +35,6 @@ async function init(): Promise { byId('downloadMode').value = config.download_mode ?? 'full'; byId('partMinutes').value = String(config.part_minutes ?? 120); byId('performanceMode').value = (config.performance_mode as string) || 'balanced'; - byId('smartSchedulerToggle').checked = (config.smart_queue_scheduler as boolean) !== false; byId('duplicatePreventionToggle').checked = (config.prevent_duplicate_downloads as boolean) !== false; byId('metadataCacheMinutes').value = String((config.metadata_cache_minutes as number) || 10); byId('vodFilenameTemplate').value = (config.filename_template_vod as string) || DEFAULT_VOD_TEMPLATE; diff --git a/src/style-modules.production-path.test.ts b/src/style-modules.production-path.test.ts index 0a218c0..78d9ddf 100644 --- a/src/style-modules.production-path.test.ts +++ b/src/style-modules.production-path.test.ts @@ -20,7 +20,7 @@ describe('production style modules', () => { .replace(/\r\n/g, '\n')); const digest = createHash('sha256').update(content).digest('hex'); - expect(digest).toBe('ece1a9e9f064bc42b0797cd15fcdcdc6367530a67f7c8f6b72c9e10253d73f9a'); + expect(digest).toBe('674178288cf7695d7981ebc6f9360a4b7043c9eb66a44b363c830dd8b413114c'); }); test('derives the Windows hot-development executable version from package metadata', () => { diff --git a/src/styles-workflows.css b/src/styles-workflows.css index 32f6926..ce97d9c 100644 --- a/src/styles-workflows.css +++ b/src/styles-workflows.css @@ -280,10 +280,10 @@ display: flex; align-items: flex-start; justify-content: space-between; - gap: 8px; + gap: 4px; margin-top: 3px; - flex-wrap: wrap; - font-size: 12px; + flex-wrap: nowrap; + font-size: 11px; color: var(--text-secondary); line-height: 18px; } @@ -291,7 +291,24 @@ .queue-progress-status, .queue-progress-metrics { min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.queue-progress-status { + flex: 1; +} + +.queue-item:has(.status.error) .queue-progress-info { + flex-wrap: wrap; +} + +.queue-item:has(.status.error) .queue-progress-status { + flex-basis: 100%; + white-space: normal; overflow-wrap: anywhere; + font-size: 12px; } .queue-progress-status.is-starting { @@ -304,6 +321,8 @@ } .queue-progress-metrics { + flex-shrink: 0; + max-width: 100%; margin-left: auto; text-align: right; font-variant-numeric: tabular-nums;