From 195dcefca978357cc7396fa26ad65a8b56546480 Mon Sep 17 00:00:00 2001 From: bharvey88 <8107750+bharvey88@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:01:00 -0500 Subject: [PATCH] Stop the release-notes blink on variant clicks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes in js/views/device.js: - The variant click handler had no same-variant guard, so clicking the already-active variant still ran variant = ...; epoch++; and re-rendered everything, including a wasted GitHub API fetch. Now it no-ops when the clicked variant is already selected. - renderReleaseNotes() blanked #release-slot up front before its async fetch resolved, so the box went empty for a network round-trip on every render (the visible flash). The old notes now stay in place until the fetch resolves and swaps them in; the existing epoch guard still prevents stale writes. Added two Playwright tests in tests/installer.spec.js that fail without these fixes: same-variant clicks make no extra release-notes fetch, and #release-slot never goes empty while switching variants. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- js/views/device.js | 2 +- tests/installer.spec.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/js/views/device.js b/js/views/device.js index 5cdbd32..0d45711 100644 --- a/js/views/device.js +++ b/js/views/device.js @@ -120,6 +120,7 @@ export function renderDevice(el, device) { if (seg) seg.addEventListener('click', (e) => { const b = e.target.closest('button[data-variant]'); if (!b) return; + if (b.dataset.variant === variant) return; // already selected — nothing to re-render variant = b.dataset.variant; epoch++; seg.querySelectorAll('button').forEach((x) => { @@ -188,7 +189,6 @@ export function renderDevice(el, device) { async function renderReleaseNotes() { const slot = el.querySelector('#release-slot'); - slot.innerHTML = ''; const myEpoch = epoch; const repo = repoFor(device, channel, variant); try { diff --git a/tests/installer.spec.js b/tests/installer.spec.js index a4df985..9ac5871 100644 --- a/tests/installer.spec.js +++ b/tests/installer.spec.js @@ -234,6 +234,43 @@ test('release notes follow the selected variant repo (per-variant repos override .toHaveAttribute('href', `https://github.com/${overrideRepo}/releases`); }); +test('clicking the already-selected variant makes no extra release-notes fetch', async ({ page }) => { + const d = registry.devices.find((x) => Object.keys(x.firmware.stable).length > 1); + test.skip(!d, 'no multi-variant device in registry'); + let apiCalls = 0; + await page.route('https://api.github.com/**', (route) => { + apiCalls++; + route.fulfill({ json: { name: 'v1.0.0.0', body: 'notes', html_url: 'https://github.com/a/b/releases/tag/v1' } }); + }); + await page.goto(`/#/${d.id}`); + await expect(page.locator('.release-notes')).toBeVisible(); // initial fetch completed + const callsAfterLoad = apiCalls; + const active = Object.keys(d.firmware.stable)[0]; // the default/active variant + await page.locator(`#variant-seg button[data-variant="${active}"]`).click(); + await page.waitForTimeout(300); + expect(apiCalls).toBe(callsAfterLoad); // no new fetch on same-variant click +}); + +test('switching variant never blanks the release-notes box', async ({ page }) => { + const d = registry.devices.find((x) => Object.keys(x.firmware.stable).length > 1); + test.skip(!d, 'no multi-variant device in registry'); + await page.route('https://api.github.com/**', (route) => + route.fulfill({ json: { name: 'v1.0.0.0', body: 'notes', html_url: 'https://github.com/a/b/releases/tag/v1' } })); + await page.goto(`/#/${d.id}`); + await expect(page.locator('.release-notes')).toBeVisible(); + // Record if #release-slot ever becomes empty during the switch. + await page.evaluate(() => { + window.__wentEmpty = false; + const slot = document.getElementById('release-slot'); + new MutationObserver(() => { if (slot.innerHTML.trim() === '') window.__wentEmpty = true; }) + .observe(slot, { childList: true }); + }); + const other = Object.keys(d.firmware.stable)[1]; + await page.locator(`#variant-seg button[data-variant="${other}"]`).click(); + await page.waitForTimeout(400); + expect(await page.evaluate(() => window.__wentEmpty)).toBe(false); +}); + test('header GitHub link and classic-installer links follow the selected variant', async ({ page }) => { const d = registry.devices.find((x) => x.installers && x.installers.stable && Object.values(x.installers.stable).some((v) => v === null));