- flashSize: 'detect' — esptool.js now detects the physical flash size and
updates the binary header before writing. Fixes boot panic on devices with
4MB embedded flash (e.g. ESP32-S3 WROOM-1-N4R2) when firmware was compiled
for 16MB: spi_flash_init() no longer sees a header/physical mismatch.
- Chip family validation — after loader.main() returns the detected chip,
check it against build.chipFamily from the manifest. Throw a UserError if
a non-ESP32-S3 (or unsupported chip family) is connected, instead of
silently flashing incompatible firmware.
- Better post-flash error messaging — track flashSucceeded flag; if flashing
succeeded but provisioning failed, show "Provisioning failed / unplug and
replug" instead of the misleading "Device not in download mode" help.
- Mothership auto-detection — offline fallback and ms-host placeholder now
use window.location.hostname instead of the hardcoded spaxel-mothership.local.
The browser is already talking to the mothership via this hostname, so it is
the correct default for the device to reach it as well.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The firmware prints 'SPAXEL READY <MAC>' a few seconds after the flash reset,
once the UART driver has initialised. Previously the browser sent the JSON
payload immediately when the port opened — the bytes arrived while the device
was still booting and were lost, causing a 15 s timeout.
New flow:
1. Open port (with retries for USB re-enumeration)
2. Read serial stream waiting for 'SPAXEL READY' line (up to 30 s)
3. Only then send the JSON provisioning payload
4. Wait for firmware's {"ok":true} acknowledgment (up to 10 s)
The MAC extracted from 'SPAXEL READY <MAC>' is used as a fallback in case
the firmware's JSON response is parsed before the mac field is available.
- SSID input: add autocorrect=off, autocapitalize=none, spellcheck=false to
prevent mobile browsers from silently altering SSIDs with special chars
- Password input: same attrs for consistency
- Firmware: accept WPA/WPA2 mixed mode (WIFI_AUTH_WPA_WPA2_PSK) so networks
with special characters in the password connect regardless of WPA version
- Firmware: detect open networks (empty password) and use WIFI_AUTH_OPEN so
passwordless networks are not rejected by the auth threshold
JSON encoding path (JSON.stringify → TextEncoderStream → cJSON) already
handles all characters correctly; these changes prevent browser-side mangling
and firmware-side connection rejection.
Previously: flash firmware → wait for 120s boot window → fill form → send over serial
Now: fill WiFi form → flash firmware → device reboots → send over serial immediately
The provisioning window opens at boot. With credentials already collected, the
browser sends the payload the moment the flash+reboot cycle completes — no human
action required and no race condition. Flash step now handles both flashing and
provisioning in one automated sequence (progress 0→80% flash, 80→100% provision).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Show real-time status messages and a collapsible log panel during WiFi provisioning
- Thread addProvLog/setProvStatus callbacks through provisionAndSend and sendPayloadOverSerial
- Log every stage: mothership fetch, payload assembly, port open retries, serial send/response
- All log lines also go to browser console.log/warn/error
- Firmware: extend provisioning window from 10s to 120s for fresh boards (15s for re-provisioning)
- Firmware: include MAC address in SPAXEL READY message for display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clears session state and returns to step 1. Useful when onboarding
multiple devices back-to-back without a page reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getAuthorizedPort() is unreliable after esptool reboots the device.
Use state.port (the port the user explicitly selected) and retry port.open
up to 5 times with 1s gaps to handle the brief window while the device
re-enumerates. Show specific UserError messages instead of the generic one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On page reload the serial port reference is lost. If saved state points
to flash_firmware or later and port is null, drop back to connect_device
so the user can re-select their device.
Also add a small '← Back to Connect' link at the bottom of the flash
step so there is always a visible escape route.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove <esp-web-install-button> and all shadow DOM polling hacks.
Use esptool-js (vendored as dashboard/js/esptool-bundle.js) via dynamic
import to flash directly. Flash starts automatically when step 3 loads —
no button click required. Progress shown inline. On failure, shows
BOOT+RST instructions and a retry button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ewt-install-dialog tag name check was too strict — drop it and take
the first non-style element child that appears in the shadow root, then
poll _installState on it. This matches how the previous version found
the dialog before we narrowed the check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esp-web-tools v10 does not dispatch a state-changed DOM event for
firmware flashing — that event only exists for Improv WiFi provisioning.
Flash state lives on ewt-install-dialog._installState (a LitElement
@state property). Replace the broken event listener with a 100ms poll
on _installState once the dialog appears in the shadow root.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esp-web-tools v10 removed flash-start/flash-progress/flash-success/flash-error
events. Only state-changed fires — but from inside the shadow root (not composed),
so host-element listeners never receive it.
Fix: MutationObserver watches the shadow root for the dialog element, then
attaches state-changed directly to it. States: erasing/writing drive inline
progress; finished auto-advances; error shows retry.
Also suppress the dialog overlay (CSS injection into shadow root) once erasing
starts, so the flash progress shows inline in the wizard with no separate modal.
The dialog remains visible briefly for the 'preparing' confirm step only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If esp-web-tools throws a JS error during progress display (e.g. the
manifest offset bug), flash-success never fires and the wizard gets stuck
with no way to advance. After reaching 100% progress, wait 4s for the
event; if it doesn't come, show a 'Continue →' button instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esp-web-tools reads the flash offset from the 'offset' field in the
manifest parts array. The field was named 'address', causing the offset
to be undefined, which produced 'Writing at 0xNaN' and the
'Cannot read properties of undefined (reading toString)' error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: use --flash_size 4MB and drop OTA data from merge_bin (OTA
data at 0xc10000 inflated binary to 12.6MB, exceeding 4MB chip flash)
- main.go: seedFirmwareDir now overwrites when source size differs, fixing
PVC staleness where old 1.6MB app-only binary was never replaced
- onboard.js: renderFlashFirmware() rewritten so all elements (button,
progress bar, status text, retry help, log panel) are inline in one
container — no separate floating modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix portal crossing direction bug in collectCrossings: direction was
determined by currSide (where blob is now), but should be determined
by prevSide (where blob came from). A→B means blob was on A side before.
- Add Enabled: true to portal test fixtures so they participate in
crossing detection (portals default to disabled).
- Refactor health.Checker: add injectable checkDB field defaulting to
defaultCheckDB() for cleaner unit test overriding.
- Fix TestHealthCheckSheddingLevelJSON: use getShedLevel override instead
of trying to poke shedder internals; fixes type mismatch in closure.
- Fix TestHealthCheckUptimeIncrement: backdate startTime to guarantee
the second check crosses a 1-second boundary.
Captures console.log/warn/error and esp-web-tools state-changed events
into a scrollable monospace log panel (collapsed by default, auto-opens
on flash-start and flash-error). Includes timestamps and error detail
from the flash-error event for diagnosing failures like the toString crash.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guru Meditation/IllegalInstruction after flashing was caused by the
merged binary using default flash parameters instead of the project's
settings. esptool merge_bin flags use underscores and go after the
subcommand: --flash_mode dio --flash_freq 80m --flash_size 16MB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On flash-error, show BOOT+RST instructions with board diagram and a
"Try Again" button instead of a dead-end error message. Escalates to
USB cable/hub advice after 2 failures. Adds a proactive collapsible
tips section before flashing starts for users who read ahead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esptool merge_bin combines bootloader (0x0), partition table (0x8000),
application (0x10000), and OTA data (0xc10000) into a single binary
flashable at offset 0x0 — matching the manifest address and enabling
correct initial flashing via the onboarding wizard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/dist/install-button.js has bare 'tslib' imports that browsers reject.
/dist/web/install-button.js is the fully bundled build with only
relative imports — works without an import map.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>