alex 7735d7f0cb Portal customization API v2, SPA hardening, and test cleanup
- Group portal state into brand/pages/actions/layout structs; portal* API
- Bootstrap JSON: nested brand/context/pages/actions/layout; param kind
- Handlers: PROGMEM default CSS when no override/append; dynamic CSS path
- PortalAppJS: portal timeout countdown; safe WiFi scan DOM; serialized
  scan-status polling; fetch error handling; wm:view-changed on redirects
- CSS: scan list icon alignment; wm-status for timeout
- Tests: remove low-value/no-op tests; trim fragile JS substring checks
2026-05-04 22:03:43 +10:00

WiFiManager

This repository is a breaking fork of upstream tzapu/WiFiManager. It is not a drop-in replacement for upstream behavior, APIs, templates, or portal customization patterns.

If you are evaluating this fork, assume that core web-portal architecture has changed and review the code before adopting it in an existing upstream-based project.

This repository: alexhopeoconnor/WiFiManager
Upstream: tzapu/WiFiManager

Breaking Changes

This fork intentionally modernizes and restructures the configuration portal. Notable differences from upstream include:

  • The portal now serves a single HTML shell from GET /.
  • Client navigation is handled as a SPA with hash routing.
  • Interactive behavior is exposed through JSON APIs under /api/... plus firmware upload at POST /u.
  • Legacy multi-page portal routes and legacy root-template override paths have been removed.
  • The portal rendering pipeline is built around DFTE instead of upstream's monolithic HTML string assembly.
  • JSON endpoints are expected to be data-first, not derived from generated HTML fragments.

Current Improvements And Modernization

This fork currently includes the following architectural improvements:

  • A single-shell portal architecture with embedded bootstrap JSON and embedded application JS.
  • A SPA-based configuration UI for WiFi setup, parameters, info views, device actions, and OTA flow.
  • A clean API surface for WiFi scanning, WiFi save, parameters, info, status, restart, erase, portal exit, and captive-portal close behavior.
  • Captive portal redirect handling retained while removing duplicate legacy UI architecture.
  • Data-first JSON generation for portal APIs, including info/device/about data, instead of HTML-to-JSON parsing.
  • Capability-driven UI flags in bootstrap/API payloads so features like info, update, erase, and action visibility can be controlled by backend state.
  • Portal bootstrap contract v2: nested brand, context, pages, actions, layout, extraHomeCards; Wi-Fi meta params include kind (field | html) for first-class custom HTML parameters.
  • SPA-native feedback UX using in-DOM dialog/toast behavior rather than page-based action flows.
  • Request-scoped shell rendering: the root portal page is built for each GET / from WM_ROOT_SHELL_TEMPLATE using a fresh placeholder registry. Shell inputs are %PAGE_TITLE%, %STYLES%, %BOOTSTRAP_JSON%, %PORTAL_APP_JS%, and %PORTAL_APPEND_JS% — filled in WiFiManagerHandlers from WiFiManager state and embedded assets (not from a server-wide template registry).
  • Customization via WiFiManager portal* APIs (portalSetBrandTitle, portalSetPageInfoVisible, portalSetLayoutParamsLocation, portalAddParameter, asset hooks, etc.) and JSON under /api/..., not by exposing placeholder-registry mutation to consumers.
  • A clearer separation between:
    • shell rendering (handlers + SPA bootstrap)
    • JSON API responses
    • captive portal behavior
    • OTA handling
  • Updated tests focused on the shell contract, bootstrap payloads, and API JSON shapes rather than removed legacy portal pages.

Portal Customization Boundary

Stable, supported portal customization is intentionally scoped:

  • portalSetBrand* for title, intro text, and logo SVG, plus portalSetContextIdentityText(...) for the user-facing runtime identity string on the home view.
  • portalSetPage*, portalSetAction*, portalSetLayout*, and portalSetBehavior* for built-in portal capabilities and runtime behavior.
  • portalAddParameter(...) for first-class custom parameters, including raw HTML blocks inside parameter-rendering surfaces (#/wifi or #/setup).
  • portalAddInfoSection(...) and portalAddHomeCard(...) for structured extra content rendered by the built-in SPA.
  • portalAppendCss(...), portalOverrideCss(...), and portalAppendJs(...) for light theming and enhancement hooks.

Not part of the stable API:

  • arbitrary HTML injection into home/info/nav/shell
  • replacing built-in SPA routing or action flow
  • depending on undocumented DOM IDs or route internals
  • treating include/templates/* as a supported consumer override surface

Appended JS should enhance rather than replace the built-in SPA. The documented hook contract is:

  • wm:ready with detail.boot
  • wm:view-changed with detail.route

If a consumer needs custom live widgets, new primary navigation concepts, or new backend-to-frontend workflows, that is considered fork territory rather than portal customization.

Portal Customization Example

WiFiManager wm;

wm.portalSetBrandTitle("Solar Battery Monitor Setup");
wm.portalSetContextIdentityText("Solar Battery Monitor");
wm.portalSetBrandHomeIntro(
    "Connect your monitor to WiFi, then review battery and inverter settings."
);
wm.portalSetBrandLogoSvg(
    "<svg viewBox='0 0 24 24' aria-hidden='true'>"
    "<path d='M12 2L4 12h5l-1 10 8-10h-5z'></path>"
    "</svg>"
);

wm.portalSetPageInfoVisible(true);
wm.portalSetPageUpdateVisible(false);
wm.portalSetActionEraseVisible(false);
wm.portalSetActionRestartVisible(true);
wm.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage);

wm.portalSetBehaviorCaptivePortalEnabled(true);
wm.portalSetBehaviorConnectOnSave(true);
wm.portalSetBehaviorExitAllowed(true);

wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Masked);
wm.portalSetFieldStaticIpVisibility(PortalFieldVisibility::Auto);
wm.portalSetFieldStaticDnsVisibility(PortalFieldVisibility::Auto);

WiFiManagerParameter mqttHost(
    "mqtt_host",
    "MQTT host",
    "broker.local",
    64,
    "placeholder='broker.local'"
);
wm.portalAddParameter(&mqttHost);

// Raw HTML remains first-class for custom parameters, but only inside the
// parameter-rendering surfaces (#/wifi or #/setup), not arbitrary portal regions.
WiFiManagerParameter gpsHelp(
    "<div class='wm-callout wm-callout--info'>"
    "<p>GPS options below apply only when a GPS module is connected.</p>"
    "</div>"
);
wm.portalAddParameter(&gpsHelp);

PortalInfoSection battery;
battery.id = "battery";
battery.title = "Battery";
battery.items.push_back({"soc", "State of charge", "84%"});
battery.items.push_back({"voltage", "Voltage", "13.2V"});
wm.portalAddInfoSection(battery);

PortalHomeCard solar;
solar.id = "solar";
solar.title = "Solar summary";
solar.kind = PortalHomeCardKind::KeyValue;
solar.items.push_back({"pv", "PV input", "420W"});
solar.items.push_back({"load", "Load", "180W"});
wm.portalAddHomeCard(solar);

wm.portalAppendCss(
    ".wm-brand-logo svg{width:40px;height:40px;display:block;}"
    ".wm-hero-intro{max-width:42ch;}"
);

wm.portalAppendJs(
    "document.addEventListener('wm:ready', function(e){"
    "  console.log('Portal booted', e.detail.boot);"
    "});"
);

Dependencies

This fork depends on DFTE (Device Framework Template Engine) and ESP32Async/ESPAsyncWebServer.

For this repo's platformio.ini test setup, DFTE is expected as a sibling checkout:

lib_deps =
    ESP32Async/ESPAsyncWebServer@3.9.1
    symlink://../DFTE

Installation

[env:your_environment]
platform = espressif8266   ; or espressif32
board = d1_mini            ; your board
framework = arduino
lib_deps =
    https://github.com/alexhopeoconnor/WiFiManager.git

Or a local path:

lib_deps =
    file:///path/to/WiFiManager

AI Assistance Notice

Parts of this codebase have been developed and refactored with the aid of AI coding agents under human direction and review.

License

See LICENSE.

S
Description
No description provided
Readme MIT
4.2 MiB
Languages
C++ 74.3%
Shell 15.3%
C 4.7%
JavaScript 3.6%
Python 2%
Other 0.1%