fix(wifi): repair provisioning — UI/firmware key mismatch, connect-on-save, asset cache-busting - #41
Merged
Merged
Conversation
…n-save WiFi provisioning via the web UI has been silently broken since the schema-driven UI rewrite (PR #29, 3a4be09): both skins POST the SSID as "wifiSsid" while configFromJson only reads "wifiSSID" (ArduinoJson is case-sensitive), so the SSID was dropped on save. Worse, "wifiPassword" DID match — re-provisioning a working device saved the new password under the old SSID and could knock it fully offline. The AP-churn fixes in PR #40 then (correctly) suppressed the periodic STA retry while a client sits on the SoftAP, which also removed the accidental path that used to pick up freshly saved creds within 30s. - skins: send the canonical "wifiSSID" key the firmware parses; the parser stays strict (one spelling, no alias). - wifi: requestWifiConnect() — config handler flags a credential change (atomic, web task -> loop task) and handleWifiMaintenance() fires one immediate disconnect+begin with the new creds, deliberately bypassing the AP-client suppression: this is the one scan provisioning needs. - status API: "wifi" is now the object API_V2.md documents and both skins + the mock dev server already read ({connected, ssid, rssi}); ssid is the configured one so the setup page can prefill while AP-only, and rssi is present only when connected. - skins: honest copy (the device never restarted on save despite the confirm/toast claiming so), drop the leftover mockup SSID prefills ("LUME-Studio"/"LUME-Workshop") which a working save would now actually provision, and guard the console prefill against clobbering the field mid-edit. data/ is intentionally untouched — the release CI syncs ui-concepts/ into it (PR #37). Run scripts/sync_web.py before a local uploadfs. Verified: C3 build clean, native tests 65/65, both skins node --check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…entry points /assets/ is served with max-age=604800 and stable filenames, so after any UI change — including OTA filesystem updates — browsers kept executing week-old cached app.js/engine.js with no way to invalidate short of a private tab. This bit during the 2026-07-28 provisioning debugging: the provisioning fix itself was invisible to a phone that had cached the broken app.js. - scripts/sync_web.py stamps every asset reference it writes with a per-file content hash (?v=<sha1[:8]>): changes exactly when the served bytes change, deterministic across runs, and the shared engine.js gets the same tag on both pages. /assets/ keeps its week-long max-age. - HTML entry points (/ via handleRoot, *.html + the SPA fallback in onNotFound) are now served Cache-Control: no-cache, so a cached page can never pin clients to an old asset set. beginResponse(LittleFS,...) is the same AsyncFileResponse path send() used — the .gz variant fallback is preserved. - /euclid/'s relative style.css/app.js refs get stamps too: not long-cached today, but this makes that safe to change later. Verified: sync output idempotent across runs, tag flips on a 1-byte content change, C3 build clean, flashed to hardware (boots, AP up, maintenance loop running). data/ untouched per convention — release CI regenerates it via sync_web.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes a broken WiFi provisioning flow introduced by the UI redesign by aligning UI/firmware config keys, triggering an immediate STA connect attempt after saving credentials, and preventing stale cached UI assets from masking updates.
Changes:
- Update both UI skins to send/read the canonical
wifiSSIDkey and improve provisioning UX (no fake restart messaging; avoid SSID prefill stomping user input). - Add a loop-task-safe “connect-on-save” path (
requestWifiConnect()+ one-shot flag consumed inhandleWifiMaintenance()), so newly saved creds are tried immediately even while a SoftAP client is connected. - Implement asset cache-busting via per-file
?v=<sha1[:8]>stamping inscripts/sync_web.py, and serve HTML entry points withCache-Control: no-cacheto avoid pinning old assets.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| ui-concepts/euclid-live/index.html | Remove mock SSID default; add placeholder for safer provisioning. |
| ui-concepts/euclid-live/app.js | Switch to wifiSSID key; update save confirmation/toast text. |
| ui-concepts/console-euclid-live/index.html | Remove mock SSID default; add placeholder. |
| ui-concepts/console-euclid-live/app.js | Use wifiSSID; prevent status polling from overwriting typed SSID; update UX strings. |
| src/storage.cpp | Document and enforce canonical wifiSSID JSON key parsing. |
| src/network/wifi.h | Add requestWifiConnect() API for loop-task reconnect triggering. |
| src/network/wifi.cpp | Implement atomic one-shot connect request consumed in maintenance loop. |
| src/network/server.cpp | Serve .html with Cache-Control: no-cache in SPA/static fallback paths. |
| src/api/status.cpp | Serve root HTML with no-cache; change /api/status wifi to structured object. |
| src/api/config.cpp | Detect WiFi credential changes and request immediate connect after successful save. |
| scripts/sync_web.py | Stamp asset references with content-hash ?v= tags while keeping long max-age for /assets/. |
| docs/TECH_DEBT.md | Mark stale-asset caching issue as resolved and document the approach. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WiFi provisioning via the web UI has been silently broken since the UI redesign (PR #29): both skins POSTed the SSID as
wifiSsidwhile the firmware parseswifiSSID(ArduinoJson is case-sensitive), so the SSID was dropped on every save. Worse,wifiPassworddid match — re-provisioning a working device saved the new password under the old SSID and could take it fully offline. The AP-churn fixes in PR #40 then (correctly) suppressed the periodic STA retry while a client sits on the SoftAP — which also removed the accidental 30s-retry path that used to pick up freshly saved creds.Commit 1 — provisioning fix
wifiSSID; the firmware parser stays strict (one spelling, no alias), with a comment marking the exact key as load-bearing.requestWifiConnect()— the config handler flags a credential change (atomic, web task → loop task) andhandleWifiMaintenance()fires one immediatedisconnect()+begin()with the new creds, deliberately bypassing the AP-client suppression. This is the one scan provisioning needs./api/statuswifiis now the object API_V2.md documents and both skins + the mock dev server already read ({connected, ssid, rssi});ssidis the configured one so the setup page prefills while AP-only;rssipresent only when connected.LUME-Studio/LUME-Workshop) removed — a working save would now actually provision them; the console SSID prefill stops once the user edits the field (mobile blurs the input when the keyboard closes, so the status poll was stomping typed input).Commit 2 — asset cache-busting (found during hardware testing)
/assets/is served withmax-age=604800and stable filenames, so phones kept executing week-old cached JS after UI updates — the provisioning fix itself was invisible to a phone that had cached the broken app.js.scripts/sync_web.pystamps every asset reference with a per-file content hash (?v=<sha1[:8]>);/assets/keeps its week-long max-age.Cache-Control: no-cacheso a cached page can never pin an old asset set (.gzserving path preserved).Test plan
node --checkCredentials changed; connecting to …immediately (phone still on the AP); after the phone left the AP the maintenance retry connected in <1s (Connected! IP: 172.20.10.2, mDNS/OTA up)data/untouched per convention — release CI regenerates it viasync_web.py🤖 Generated with Claude Code