Skip to content

Offer to switch the web.esphome.io flow when a port belongs to another board - #1819

Merged
bdraco merged 15 commits into
mainfrom
web-flow-switch-suggestion
Sep 26, 2026
Merged

bdraco merged 15 commits into
mainfrom
web-flow-switch-suggestion

Conversation

@bdraco

@bdraco bdraco commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

What does this implement/fix?

web.esphome.io now offers to switch to the right board flow when the port a user picks, or a device they plug in, clearly belongs to another family. It never switches on its own: a toast says "The connected device looks like a Raspberry Pi Pico." with a "Switch to the Pico flow" action, and the flow the user is in carries on either way.

  • A small classifier reads the port's USB ids: the Raspberry Pi vendor id (minus its debug probes) is a Pico's own console; an nRF52 running ESPHome is a Zephyr USB device (0x2fe3, ESPHome's only Zephyr platform), Nordic's own id is nRF52 outright, and Adafruit's and Seeed's known nRF52840 product ids cover the bootloader and other firmwares; Espressif's id or a dedicated UART bridge is the esptool path. Anything else says nothing, and the site stays quiet. Ids are a hint, not proof, which is why this is a suggestion and not a route.
  • The ESP connect card and the nRF52 card announce a picked port to the shell; the Pico card's picker is already filtered to Pico ids, and the install dialogs pick a bootloader port right before a write, so neither announces. Accepting the toast switches the mode and writes the URL flag, so a reload stays put.
  • The shell also listens for Web Serial connect events, which the browser fires only for ports the site already has permission for, so a returning user who plugs a known board in gets the same offer. The event decoding, the "our own touch or flash re-enumerating" gate and the once-per-port memory moved out of the Device Builder's shell into shared helpers in serial-reacquire.ts, so both shells handle the event the same way.
  • No offer is made while an operation may be running: the shell stays quiet whenever a wrapper dialog (install, logs, no-port help) or the Wi-Fi setup dialog is up, since accepting a switch unmounts the current flow.
  • ?pico and ?nrf stay as the explicit override and the shareable link.

The Device Builder's "USB device connected / Set it up" toast is not reused: it opens the dashboard's setup flow and has no equivalent on the site.

Related issue or feature (if applicable):

Screenshots

An info toast in the site's existing toaster (bottom right) with one action button; no other UI change.

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — docs
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Checklist

  • The code change is tested and works locally.
  • Screenshots (or a recording) are included for any UI feature or change.
  • pnpm run lint passes.
  • pnpm run test passes.
  • Tests have been added to verify that the new code works (where applicable).

@bdraco bdraco added the new-feature New feature label Sep 25, 2026
@bdraco

bdraco commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Important issues found.

  • The plug-in listener reads event.port, which current Chromium leaves undefined, so it throws

@bdraco

bdraco commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Cleanup pass: the connect-event decoding (spec event.target, legacy event.port), the own-re-enumeration gate with its self-extending window, and the once-per-port announcement memory are now shared helpers in serial-reacquire.ts used by both the Device Builder shell and the site, so the site no longer re-toasts a reboot-looping board. The cards use fireEvent, the nRF install dialog's own pick announces too, and the plug-in listener is only registered outside flasher mode. Tests use the shared fakes and assert keys.

@esphbot review

@bdraco

bdraco commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

That review ran on the head before the cleanup commit; all four points are covered now:

  • connect event shape (warning 1): the shell no longer reads event.port; portOfSerialConnectEvent in serial-reacquire.ts takes the spec event.target first and the legacy event.port second, and both shells use it. The shell test now fires the spec shape and has a separate legacy-shape case.
  • Adafruit / Seeed vendor ids (2): those two vendors are matched by their known nRF52840 product ids (application and bootloader ids for the Feather nRF52840 Express and Sense, ItsyBitsy nRF52840, CLUE, Circuit Playground Bluefruit, LED Glasses, and the XIAO nRF52840 and Sense); Nordic's own id stays vendor-level. A Feather RP2040 or a tinyuf2 ESP32-S3 bootloader now gets no suggestion, with tests for both.
  • stale toast (3): _setMode returns early when the mode is already current, so clicking a stale toast after a manual switch pushes no second history entry. Tested.
  • test listeners (4): the card tests listen on the element itself.

@esphbot review

@esphbot

esphbot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

@bdraco: That review ran on the head before the cleanup commit; all four points are covered now:

Verified all four at head. portOfSerialConnectEvent handles target and legacy port. nRF product-id allowlist confirmed. _setMode early return confirmed. Card tests listen on element. One new nit: nRF dialog announcement under modal.

@esphbot

esphbot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco marked this pull request as ready for review September 25, 2026 23:33
Copilot AI lite review requested due to automatic review settings September 25, 2026 23:33
@bdraco

bdraco commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Hardware check: an nRF52 running ESPHome enumerates as a Zephyr USB device (0x2fe3:0x0100, "USB_DEV"), not under Adafruit, Nordic or Seeed; those ids only appear in the bootloader. Zephyr's vendor id now counts as nRF52 (ESPHome's only Zephyr platform), with a test.

@esphbot review

@esphbot

esphbot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

@bdraco: Hardware check: an nRF52 running ESPHome enumerates as a Zephyr USB device (0x2fe3:0x0100, "USB_DE...

Checked at head. ZEPHYR_USB_VID added in board-family.ts:11. Test covers 0x2fe3:0x0100. Any Zephyr device now reads as nRF52. Fine as a suggestion only.

@esphbot

esphbot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Generic UART bridges can be misclassified as ESP devices, and an in-flight nRF log connection can leak when the card is removed.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds board-family detection and opt-in flow-switch suggestions for ESPHome Web, covering selected and reconnected serial devices.

Changes:

  • Adds Pico, nRF52, and ESP classification with persistent flow switching.
  • Shares serial reconnect handling and deduplication helpers.
  • Adds translations, test coverage, and port-announcement integrations.
File Description
test/​web/​web-app-flow-switch.test.ts Tests flow suggestions and switching.
test/​web/​nrf-card.test.ts Tests nRF port announcements.
test/​web/​esp-connect-card.test.ts Tests ESP port announcements.
test/​web/​board-family.test.ts Tests board-family classification.
test/​web/​_make-web-serial-port.ts Adds serial-port test fixtures.
test/​util/​serial-connect-events.test.ts Tests shared serial-event helpers.
test/​_web-serial.ts Provides configurable serial mocks.
src/​web/​util/​board-family.ts Classifies ports by board family.
src/​web/​install/​esphome-web-install-nrf-dialog.ts Announces selected install ports.
src/​web/​esphome-web-app.ts Handles flow suggestions and switching.
src/​web/​dashboard/​esphome-web-nrf-card.ts Announces selected nRF ports.
src/​web/​dashboard/​esphome-web-esp-connect-card.ts Announces selected ESP ports.
src/​util/​web-serial.ts Exposes serial identity helpers.
src/​util/​serial-reacquire.ts Shares serial reconnect handling.
src/​util/​serial-console-match.ts Exposes UART bridge detection.
src/​translations/​en.json Adds flow-switch messages.
src/​components/​app-shell.ts Reuses shared serial helpers.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/web/dashboard/esphome-web-nrf-card.ts
Copilot AI review requested due to automatic review settings September 25, 2026 23:37
@bdraco

bdraco commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Agreed, and removed: the nRF install dialog no longer announces its pick. That pick is for the bootloader port right before the DFU write, and a toast accepted during the flash would have unmounted the dialog. The ESP card and the nRF card keep announcing, since their picks are the discovery moments. PR description updated.

@esphbot review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Three unresolved moderate issues affect serial deduplication and nRF flow/install behavior.

Review effort: Lite
Findings: 1 Medium severity

Open (1)

Copilot AI review requested due to automatic review settings September 25, 2026 23:41
@bdraco

bdraco commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

The offer is now suppressed while an operation may be running: the shell asks the base dialog registry (hasOpenDialog, so any install, logs or no-port dialog) and the Improv opener (isImprovDialogMounted) before toasting, since accepting a switch unmounts the current flow. Covered by a shell test and a registry test.

@esphbot review

Copilot AI review requested due to automatic review settings September 26, 2026 00:22
@bdraco

bdraco commented Sep 26, 2026

Copy link
Copy Markdown
Member Author

Both suggestions addressed: isImprovInProgress and its counter now sit next to activePorts, above openImprovDialog's own doc; and the ESP and Pico device cards got the same unmount guard as the nRF card after their openPortForLogs await (close the port, never open the dialog), with a Pico device card test.

@esphbot review

@esphbot

esphbot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

@bdraco: Both suggestions addressed: isImprovInProgress and its counter now sit next to activePorts, abov...

Both fixes confirmed. JSDoc back on openImprovDialog. ESP and Pico guards present. One gap remains: 1s pause before Improv. See inline suggestion.

@esphbot

esphbot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The changes span serial lifecycle handling and multiple flow-switch paths, warranting final human review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)

Copilot AI review requested due to automatic review settings September 26, 2026 00:27
@bdraco

bdraco commented Sep 26, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco

bdraco commented Sep 26, 2026

Copy link
Copy Markdown
Member Author

Both taken: a refused toast action now replaces the toast with "Finish the current operation first, then switch flows." (new web.flow_switch.busy key, tested), and the three orphaned-port releases go through one releaseOrphanedPort helper that logs a failed close with console.warn instead of discarding it.

@esphbot review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Moderate port-cleanup issues remain in two device-card flows.

Review effort: Lite
Findings: None

Resolved since last review (1)

Copilot AI review requested due to automatic review settings September 26, 2026 00:32
@bdraco

bdraco commented Sep 26, 2026

Copy link
Copy Markdown
Member Author

Correction: da6892c carried only the releaseOrphanedPort helper and the ESP and Pico device cards. The nRF card's release, the refused-action toast (web.flow_switch.busy) and its test landed in 1694c36 (same subject). Both are on the branch now.

@esphbot review

@esphbot

esphbot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

@bdraco: Correction: da6892c carried only the releaseOrphanedPort helper and the ESP and Pico device cards...

Verified on branch. releaseOrphanedPort used in all three cards (esp-device-card:65, pico-device-card:47, nrf-card:77). Busy toast at esphome-web-app.ts:163, tested. Both prior findings resolved.

@esphbot

esphbot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

PR Review — Offer to switch the web.esphome.io flow when a port belongs to another board

Ready to merge. The findings from earlier reviews are fixed, and this pass found nothing new.

What the PR does well:

  • Shared connect-event helpers. portOfSerialConnectEvent, isOwnSerialReenumeration and SerialConnectAnnouncements now live in serial-reacquire.ts and both shells use them.
    • app-shell.ts drops about 50 lines as a result.
    • Port detection now checks whether getInfo is a function instead of using instanceof SerialPort, so an older browser that has no SerialPort global no longer throws.
  • Nothing unmounts a running operation.
    • _operationInProgress() is checked both when the toast is offered and when its action is clicked.
    • A refused click shows the web.flow_switch.busy toast, which replaces the offer because it uses the same id.
    • isImprovInProgress() is true from the first await of openImprovDialog.
    • The ESP device card and the Pico connect card check isConnected after the 1 s pause before Wi-Fi setup.
  • No leaked serial ports. All three logs paths (ESP device card, Pico device card, nRF card) go through releaseOrphanedPort, which logs a failed close with console.warn. Each path has a test that removes the card while the port open is still pending.
  • Conservative classifier. boardFamilyOfPort returns null for Raspberry Pi debug probes and for Adafruit or Seeed products that are not nRF52. The plug-in memory is only used up when a toast actually shows, so a plug-in suppressed while a dialog was open still gets its offer on the next replug.
  • Stale-toast click is safe. _setMode returns early when the mode is already set, so clicking an old toast after a manual switch does not push a second history entry.
  • Conventions followed. New strings are in en.json only, and esphome-web-app.ts is 241 lines, well under the 500–600 line limit.

Nothing needs attention.



Checklist

  • Connect-event decoding works with current (event.target) and legacy (event.port) browser behaviour
  • No flow switch while an operation is running (when offered, when clicked, and during the pause before Wi-Fi setup)
  • Ports opened for logs are released after an unmount, and a failed release is logged
  • User gets feedback when a toast action is refused
  • Listeners removed on disconnect; plug-in listener not registered in flasher mode
  • Localization in en.json only
  • File size within repo cap
  • No hardcoded secrets / security issues

Automated review by Kōan (Claude) HEAD=1694c36 17s

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The change spans serial lifecycle, multiple flows, and browser/device interactions, warranting final human review.

Review effort: Lite
Findings: None

Copilot AI review requested due to automatic review settings September 26, 2026 00:36
@bdraco
bdraco merged commit 6cfbb0b into main Sep 26, 2026
13 checks passed
@bdraco
bdraco deleted the web-flow-switch-suggestion branch September 26, 2026 00:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Generic UART bridge IDs can misclassify unrelated devices as ESP boards and should not trigger an ESP flow-switch offer.

Review effort: Lite
Findings: None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Offer to switch the web.esphome.io flow when a picked or plugged-in port belongs to another board

3 participants