Skip to content

fix(wifi): repair provisioning — UI/firmware key mismatch, connect-on-save, asset cache-busting - #41

Merged
bring42 merged 2 commits into
mainfrom
claude/wifi-ap-setup-regression-35f84b
Jul 28, 2026
Merged

fix(wifi): repair provisioning — UI/firmware key mismatch, connect-on-save, asset cache-busting#41
bring42 merged 2 commits into
mainfrom
claude/wifi-ap-setup-regression-35f84b

Conversation

@bring42

@bring42 bring42 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

WiFi provisioning via the web UI has been silently broken since the UI redesign (PR #29): both skins POSTed the SSID as wifiSsid while the firmware parses wifiSSID (ArduinoJson is case-sensitive), so the SSID was dropped on every save. Worse, wifiPassword did 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

  • Skins send the canonical wifiSSID; the firmware parser stays strict (one spelling, no alias), with a comment marking the exact key as load-bearing.
  • Connect-on-save: requestWifiConnect() — the 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.
  • /api/status 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 prefills while AP-only; rssi present only when connected.
  • Skin honesty/UX: the confirm/toast claimed the device restarts on save (it never did); leftover mockup SSID prefills (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 with max-age=604800 and 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.py stamps every asset reference with a per-file content hash (?v=<sha1[:8]>); /assets/ keeps its week-long max-age.
  • HTML entry points are served Cache-Control: no-cache so a cached page can never pin an old asset set (.gz serving path preserved).

Test plan

  • C3 build clean, native tests 65/65, both skins node --check
  • Hardware-verified end-to-end on XIAO ESP32-C3: with a saved-but-unreachable SSID, AP serves the setup page; saving new creds fires Credentials 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)
  • sync output idempotent; version tag flips on a 1-byte content change
  • data/ untouched per convention — release CI regenerates it via sync_web.py

🤖 Generated with Claude Code

benno6 and others added 2 commits July 28, 2026 13:06
…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>
Copilot AI review requested due to automatic review settings July 28, 2026 11:23
@bring42
bring42 merged commit 1b39855 into main Jul 28, 2026
2 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 wifiSSID key 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 in handleWifiMaintenance()), 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 in scripts/sync_web.py, and serve HTML entry points with Cache-Control: no-cache to 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants