diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8507b..3118781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Show the Twitch display name with its original capitalization in queue details and refresh it when profile names load. - Put queue detail URLs on a separate single line; click or use the keyboard to copy the complete URL with confirmation. - Keep queue-card titles and content aligned when expanding or collapsing details by reserving scrollbar space. - Move the queue-card remove button slightly toward the top-right corner while retaining its 32-pixel click target. diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md index d44bb51..5165ff6 100644 --- a/PROJECT_MEMORY.md +++ b/PROJECT_MEMORY.md @@ -20,6 +20,7 @@ Aktueller veröffentlichter Stand: **1.0.20**, Tag `v1.0.20`, veröffentlicht am ## Letzte Änderungen +- 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). Noch nicht veröffentlicht. - Queue-Details zeigen `URL:` separat und darunter einen einzeiligen, anklickbaren Link in 11 px. Klick, Enter oder Leertaste kopieren die vollständige URL über die vorhandene Kopierfunktion mit lokalisierter Erfolgs-/Fehlermeldung. Standard-VOD-URL einschließlich zehnstelliger ID vollständig sichtbar; überlange URLs werden nur in der Anzeige mit Ellipse gekürzt, bleiben vollständig kopierbar und im Tooltip lesbar. Build, 19 gezielte Tests, Queue-UI-Prüfung einschließlich Layout und Kopieraktionen sowie Lint erfolgreich (15 bestehende Warnungen); Hell/Dunkel visuell geprüft. Noch nicht veröffentlicht. - Titelsprung beim Auf-/Zuklappen der Queue-Cards behoben: `scrollbar-gutter: stable` reserviert den 8-px-Platz der Scrollleiste, damit sich Kartenbreite und Titelumbruch nicht ändern. In Tab- und Split-Ansicht nachgemessen. Neuer Queue-UI-Regressionscheck überschreitet beim Öffnen gezielt die Scrollgrenze und prüft die Titelgeometrie während beider Animationen in Hell/Dunkel und Deutsch/Englisch; Hintergrund-Synchronisierung wird nach dem Fixture-Abgleich für diese deterministische Messung angehalten. Vor dem Fix fehlgeschlagen, danach erfolgreich. Build, 19 gezielte Tests und Lint erfolgreich (15 bestehende Warnungen). Noch nicht veröffentlicht. - Löschen-Button in Queue-Cards um jeweils 3 px nach oben und rechts verschoben; Klickfläche bleibt 32 × 32 px. Build, fünf Stylesheet-Tests und isolierter Queue-UI-Test erfolgreich; Hell/Dunkel visuell geprüft. Noch nicht veröffentlicht. diff --git a/scripts/smoke-test-queue-cards.js b/scripts/smoke-test-queue-cards.js index 3d964be..ba3d8d5 100644 --- a/scripts/smoke-test-queue-cards.js +++ b/scripts/smoke-test-queue-cards.js @@ -24,7 +24,7 @@ async function main() { panel.dataset.vodsLayout = 'tabs'; panel.dataset.vodsWorkspace = 'queue'; queue = [ - { id: 'pending', status: 'pending', title: 'Ein langer Streamtitel mit gut lesbaren Download-Details und einer zweiten Zeile', progress: 0, url: 'https://www.twitch.tv/videos/2863358704' }, + { id: 'pending', status: 'pending', title: 'Ein langer Streamtitel mit gut lesbaren Download-Details und einer zweiten Zeile', progress: 0, url: 'https://www.twitch.tv/videos/2863358704', streamer: 'xrohat' }, { id: 'running', status: 'downloading', title: 'Sommerstream am See – gemeinsam unterwegs', progress: 42, progressStatus: 'Video wird heruntergeladen', speed: '12.5 MB/s', eta: '08:24' }, { id: 'paused', status: 'paused', title: 'Community-Abend mit Freunden', progress: 23 }, { id: 'error', status: 'error', title: 'Ein weiterer langer Streamtitel mit einer Fehlermeldung', progress: 10, last_error: 'Die Verbindung wurde unterbrochen. Bitte erneut versuchen.' }, @@ -44,6 +44,10 @@ async function main() { queueSyncTimer = null; }); assert.equal(await win.locator('#queueList .queue-item').count(), fixtures.length, 'Queue fixtures survive background synchronization'); + assert.equal(await win.locator('[data-id="pending"] .queue-streamer-name').textContent(), 'xrohat', 'Unknown display names fall back to the login'); + await win.evaluate(() => rememberStreamerDisplayName('XROHAT', 'xRohat')); + assert.equal(await win.locator('[data-id="pending"] .queue-streamer-name').textContent(), 'xRohat', 'Loaded display casing refreshes existing queue cards'); + assert.equal(await win.evaluate(() => queue.find((item) => item.id === 'pending').streamer), 'xrohat', 'Display casing preserves the stored login'); for (const theme of ['twitch', 'light']) { for (const language of ['de', 'en']) { await win.setViewportSize({ width: 1280, height: 900 }); diff --git a/src/renderer-queue.production-path.test.ts b/src/renderer-queue.production-path.test.ts index f1db2a4..af05a67 100644 --- a/src/renderer-queue.production-path.test.ts +++ b/src/renderer-queue.production-path.test.ts @@ -503,7 +503,7 @@ describe('renderer queue production paths', () => { const fingerprints = evaluate( fragment('function getQueueRenderFingerprint', 'function hasActiveQueueDuplicate'), 'getQueueRenderFingerprint', - { currentLanguage: 'en', selectedQueueIds: [], expandedQueueIds: new Set() } + { currentLanguage: 'en', selectedQueueIds: [], expandedQueueIds: new Set(), getStreamerDisplayName: (login: string) => login } ); const base = { id: 'live-1', status: 'downloading', progress: 1, isLive: true }; const unknown = fingerprints.exposed.getQueueRenderFingerprint([{ ...base, recordingHealth: 'unknown' }]) as string; diff --git a/src/renderer-queue.ts b/src/renderer-queue.ts index 798c2a2..69458c8 100644 --- a/src/renderer-queue.ts +++ b/src/renderer-queue.ts @@ -193,6 +193,7 @@ function getQueueRenderFingerprint(items: QueueItem[]): string { const lang = typeof currentLanguage === 'string' ? currentLanguage : 'en'; const pieces = items.map((item) => [ item.id, + getStreamerDisplayName(item.streamer), item.status, Math.round((Number(item.progress) || 0) * 10), item.currentPart || 0, @@ -732,7 +733,7 @@ function renderQueue(): void {
URL:
-
${escapeHtml(UI_TEXT.queue.detailStreamer)} ${escapeHtml(item.streamer)}
+
${escapeHtml(UI_TEXT.queue.detailStreamer)} ${escapeHtml(getStreamerDisplayName(item.streamer))}
${escapeHtml(UI_TEXT.queue.detailDuration)} ${escapeHtml(item.duration_str)}
${escapeHtml(UI_TEXT.queue.detailDate)} ${escapeHtml(formatUiDateTime(item.date))}
${renderQueueItemFileActions(item)} diff --git a/src/renderer-streamers.state-regressions.test.ts b/src/renderer-streamers.state-regressions.test.ts index a806d74..42e4767 100644 --- a/src/renderer-streamers.state-regressions.test.ts +++ b/src/renderer-streamers.state-regressions.test.ts @@ -417,6 +417,7 @@ describe('renderer streamer and VOD state regressions', () => { config: { streamers: ['nightbot'], streamer_display_names: { nightbot: 'NightBot' } }, currentStreamer: 'nightbot', renderStreamers: vi.fn(), + renderQueue: vi.fn(), api: { getStreamerDisplayNames: () => new Promise>((resolve) => { resolveNames = resolve; }) }, byId: (id: string) => elements.get(id) ?? element(), queryAll: () => [], @@ -440,6 +441,7 @@ describe('renderer streamer and VOD state regressions', () => { expect(displayRuntime.exposed.getStreamerDisplayName('nightbot')).toBe('NightBot'); expect((displayRuntime.window as Record).getStreamerDisplayName).toBe(displayRuntime.exposed.getStreamerDisplayName); expect(displayRuntime.renderStreamers).toHaveBeenCalledOnce(); + expect(displayRuntime.renderQueue).toHaveBeenCalledOnce(); const tabRuntime = evaluate( fragment(rendererSource, 'function showTab', 'function parseDurationToSeconds'), diff --git a/src/renderer-streamers.ts b/src/renderer-streamers.ts index 59e991c..3287c7e 100644 --- a/src/renderer-streamers.ts +++ b/src/renderer-streamers.ts @@ -45,7 +45,10 @@ function rememberStreamerDisplayName(login: string, displayName: string): void { const setTitle = (window as unknown as { setPageTitle?: (text: string) => void }).setPageTitle; if (typeof setTitle === 'function') setTitle(normalizedDisplayName); } - if (changed) renderStreamers(); + if (changed) { + renderStreamers(); + renderQueue(); + } } (window as unknown as { rememberStreamerDisplayName: typeof rememberStreamerDisplayName }).rememberStreamerDisplayName = rememberStreamerDisplayName; @@ -56,6 +59,7 @@ function renderHydratedStreamerDisplayNames(): void { if (typeof setTitle === 'function') setTitle(getStreamerDisplayName(currentStreamer)); } renderStreamers(); + renderQueue(); } async function hydrateStreamerDisplayNames(): Promise {