From 3a720096312c322d70f5f3d40ad8dbe6dad4b7bc Mon Sep 17 00:00:00 2001 From: bharvey88 <8107750+bharvey88@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:51:54 -0500 Subject: [PATCH] Reuse the install button on variant change to stop the flicker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renderInstall() rewrote installSlot.innerHTML on every variant/channel change. On the WebSerial path that destroyed and recreated the esp-web-install-button custom element each time, re-running its shadow-DOM init and flashing the "Connect & Install" button. esp-web-tools only reads the manifest on click, not on render, so the only thing that needs to change between variants is the button's manifest attribute. The hasSerial branch now reuses the existing button when present and just updates its manifest attribute in place, building the markup only on first render. The no-serial fallback branch is unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- js/views/device.js | 19 +++++++++++++------ tests/installer.spec.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/js/views/device.js b/js/views/device.js index f976e76..5cdbd32 100644 --- a/js/views/device.js +++ b/js/views/device.js @@ -140,12 +140,19 @@ export function renderDevice(el, device) { const repo = repoFor(device, channel, variant); const classic = classicInstallerFor(device, channel, variant); if (hasSerial) { - installSlot.innerHTML = ` - - - -

- Plug the device into this computer with a USB data cable, click the button, and pick the serial port.

`; + const existing = installSlot.querySelector('esp-web-install-button'); + if (existing) { + // Reuse the button; only the manifest differs between variants. Rebuilding + // it would recreate the web component and flash on every variant change. + existing.setAttribute('manifest', manifest); + } else { + installSlot.innerHTML = ` + + + +

+ Plug the device into this computer with a USB data cable, click the button, and pick the serial port.

`; + } } else { installSlot.innerHTML = `
diff --git a/tests/installer.spec.js b/tests/installer.spec.js index 106e85e..a4df985 100644 --- a/tests/installer.spec.js +++ b/tests/installer.spec.js @@ -90,6 +90,34 @@ test('channel/variant toggles rewire the install button', async ({ page }) => { } }); +test('changing variant reuses the install button instead of recreating it', async ({ page }) => { + const d = registry.devices.find((x) => Object.keys(x.firmware.stable).length > 1); + test.skip(!d, 'no multi-variant device in registry'); + // Force the WebSerial path (headless Chromium lacks navigator.serial) so the + // esp-web-install-button renders. + await page.addInitScript(() => { + if (!('serial' in navigator)) { + Object.defineProperty(navigator, 'serial', { value: {}, configurable: true }); + } + }); + await page.goto(`/#/${d.id}`); + + // Tag the initially-rendered install button so we can tell if it survives. + await page.evaluate(() => { + document.querySelector('#install-slot esp-web-install-button').dataset.tag = 'orig'; + }); + + const variants = Object.keys(d.firmware.stable); + const other = variants[1]; + await page.locator(`#variant-seg button[data-variant="${other}"]`).click(); + + // Same element must survive (tag intact) with its manifest updated in place. + await expect(page.locator('#install-slot esp-web-install-button')) + .toHaveAttribute('data-tag', 'orig'); + await expect(page.locator('#install-slot esp-web-install-button')) + .toHaveAttribute('manifest', d.firmware.stable[other]); +}); + test('manual fallback renders when WebSerial is unavailable', async ({ page }) => { await page.addInitScript(() => { Object.defineProperty(Navigator.prototype, 'serial', { get: () => undefined });