fix: keep queue titles stable when details expand
Windows CI / verify (push) Canceled after 0s

This commit is contained in:
Sucukdeluxe
2026-09-06 13:31:48 +02:00
parent f425b54ca6
commit 95f15ac834
5 changed files with 51 additions and 2 deletions
+1
View File
@@ -2,6 +2,7 @@
## Unreleased ## Unreleased
- 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. - Move the queue-card remove button slightly toward the top-right corner while retaining its 32-pixel click target.
- Show a yellow left border on waiting queue cards, matching their status dot. - Show a yellow left border on waiting queue cards, matching their status dot.
- Place the queue-card date on the left beside the details arrow and the download status on the right below the progress bar. - Place the queue-card date on the left beside the details arrow and the download status on the right below the progress bar.
+1
View File
@@ -20,6 +20,7 @@ Aktueller veröffentlichter Stand: **1.0.20**, Tag `v1.0.20`, veröffentlicht am
## Letzte Änderungen ## Letzte Änderungen
- 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. - 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.
- Wartende Queue-Cards haben jetzt eine gelbe linke Statuslinie passend zum Wartepunkt. Der Wartet-Text bleibt ohne Umrandung oder gelblichen Hintergrund. Build, fünf Stylesheet-Tests und isolierter Queue-UI-Test erfolgreich; Linie in Hell/Dunkel visuell geprüft. Noch nicht veröffentlicht. - Wartende Queue-Cards haben jetzt eine gelbe linke Statuslinie passend zum Wartepunkt. Der Wartet-Text bleibt ohne Umrandung oder gelblichen Hintergrund. Build, fünf Stylesheet-Tests und isolierter Queue-UI-Test erfolgreich; Linie in Hell/Dunkel visuell geprüft. Noch nicht veröffentlicht.
- Nach Release 1.0.20: Datum und Status in der Queue-Card getauscht. Datum steht jetzt links neben dem Detailpfeil, Status rechts unter dem Fortschrittsbalken. Schriftgrößen, gelber Wartepunkt und Aufklappanimation bleiben erhalten. Build, 19 gezielte Tests, Queue-UI-Test in Deutsch/Englisch und Hell/Dunkel sowie vollständiger Workspace-UI-Test erfolgreich; Lint ohne Fehler bei 15 bestehenden Warnungen. Änderung zunächst nur auf `public-v1`/Hot-Dev, noch nicht veröffentlicht. - Nach Release 1.0.20: Datum und Status in der Queue-Card getauscht. Datum steht jetzt links neben dem Detailpfeil, Status rechts unter dem Fortschrittsbalken. Schriftgrößen, gelber Wartepunkt und Aufklappanimation bleiben erhalten. Build, 19 gezielte Tests, Queue-UI-Test in Deutsch/Englisch und Hell/Dunkel sowie vollständiger Workspace-UI-Test erfolgreich; Lint ohne Fehler bei 15 bestehenden Warnungen. Änderung zunächst nur auf `public-v1`/Hot-Dev, noch nicht veröffentlicht.
+47 -1
View File
@@ -38,7 +38,11 @@ async function main() {
ipcMain.removeHandler('get-queue'); ipcMain.removeHandler('get-queue');
ipcMain.handle('get-queue', async () => items); ipcMain.handle('get-queue', async () => items);
}, fixtures); }, fixtures);
await win.evaluate(() => syncQueueAndDownloadState()); await win.evaluate(async () => {
await syncQueueAndDownloadState();
clearTimeout(queueSyncTimer);
queueSyncTimer = null;
});
assert.equal(await win.locator('#queueList .queue-item').count(), fixtures.length, 'Queue fixtures survive background synchronization'); assert.equal(await win.locator('#queueList .queue-item').count(), fixtures.length, 'Queue fixtures survive background synchronization');
for (const theme of ['twitch', 'light']) { for (const theme of ['twitch', 'light']) {
for (const language of ['de', 'en']) { for (const language of ['de', 'en']) {
@@ -71,6 +75,48 @@ async function main() {
assert(card.overflow <= 1, `Card overflow: ${JSON.stringify(card)}`); assert(card.overflow <= 1, `Card overflow: ${JSON.stringify(card)}`);
} }
await win.locator('.queue-section').screenshot({ path: path.join(artifacts, `queue-${theme}-${language}.png`) }); await win.locator('.queue-section').screenshot({ path: path.join(artifacts, `queue-${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'));
const item = items[0];
const details = item.querySelector('.queue-details');
const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
const measure = () => {
const title = item.querySelector('.title').getBoundingClientRect();
return { left: title.left, width: title.width, height: title.height, overflowing: list.scrollHeight > list.clientHeight };
};
try {
for (const other of items.slice(2)) other.style.display = 'none';
list.style.flex = '0 0 auto';
list.style.height = `${items.slice(0, 2).reduce((height, card) => height + card.getBoundingClientRect().height + parseFloat(getComputedStyle(card).marginBottom), 0) + 20}px`;
const before = measure();
const samples = [];
const animate = async () => {
toggleQueueDetails(item.dataset.id);
await frame();
await frame();
do {
samples.push(measure());
await frame();
} while (details.getAnimations().length);
return measure();
};
const expanded = await animate();
const collapsed = await animate();
return { before, expanded, collapsed, samples };
} finally {
[list, ...items].forEach((element, index) => {
if (originalStyles[index] === null) element.removeAttribute('style');
else element.setAttribute('style', originalStyles[index]);
});
}
});
assert(!expansionLayout.before.overflowing && expansionLayout.expanded.overflowing && !expansionLayout.collapsed.overflowing, `Expansion fixture crosses the scrollbar threshold (${theme}/${language}): ${JSON.stringify({ before: expansionLayout.before, expanded: expansionLayout.expanded, collapsed: expansionLayout.collapsed })}`);
for (const sample of [...expansionLayout.samples, expansionLayout.expanded, expansionLayout.collapsed]) {
for (const dimension of ['left', 'width', 'height']) {
assert(Math.abs(sample[dimension] - expansionLayout.before[dimension]) <= 0.5, `Title ${dimension} shifts during expansion (${theme}/${language})`);
}
}
} }
} }
const card = win.locator('.queue-item[data-id="pending"]'); const card = win.locator('.queue-item[data-id="pending"]');
+1 -1
View File
@@ -20,7 +20,7 @@ describe('production style modules', () => {
.replace(/\r\n/g, '\n')); .replace(/\r\n/g, '\n'));
const digest = createHash('sha256').update(content).digest('hex'); const digest = createHash('sha256').update(content).digest('hex');
expect(digest).toBe('db798e79ce538bd99fe6a21fa9de545150a2d0efd4a925e6f662a664bdb6ff03'); expect(digest).toBe('a2626a94632641b7e6039d5a50165e34224a75a1d286db90ae9e22bdae72064e');
}); });
test('derives the Windows hot-development executable version from package metadata', () => { test('derives the Windows hot-development executable version from package metadata', () => {
+1
View File
@@ -33,6 +33,7 @@
.queue-list { .queue-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable;
min-height: 60px; min-height: 60px;
} }