Skip to content

Add usermod web UI injection mechanism (/um.js)#5741

Open
intermittech wants to merge 2 commits into
wled:mainfrom
intermittech:usermod-ui-inject
Open

Add usermod web UI injection mechanism (/um.js)#5741
intermittech wants to merge 2 commits into
wled:mainfrom
intermittech:usermod-ui-inject

Conversation

@intermittech

@intermittech intermittech commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Adds a small, usermod-agnostic mechanism that lets usermods inject their own elements into the main web UI without patching index.js — based on the design proposed by @blazoncek.

How it works

  • Usermods override virtual void addUIInjectCode(Print &dest) and print plain JavaScript.
  • The device serves /um.js (no-store), wrapping all usermods' output in function umInject(s){...}.
  • The main UI loads /um.js once (loadUmInject(), triggered when a state response carries info.u, i.e. usermods are present) and calls umInject(s) at the end of every readState(s) — so injected elements survive UI re-renders (e.g. populateSegments()), for both fetch and WebSocket updates.
  • Without usermods (or on builds where none inject anything), /um.js serves an empty function body and the UI is unchanged; if the script fails to load, onerror logs and the UI continues normally.

Deltas from the original proposal

  • umInject(s) receives the freshly applied state object — injected UI usually renders usermod data carried in the state (via addToJsonState()), and passing it avoids a second fetch.
  • loadUmInject() is guarded against double-insertion of the script tag.
  • The script's onload triggers a requestJson() so the first injection runs with state available (the script finishes loading after the initial render).
  • A WLED_ENABLE_UM_UI_INJECT capability macro next to the virtual, so external usermods can #ifdef-guard their override and keep compiling against WLED bases that don't have the mechanism (yet).

Tested

Verified on physical hardware — a QuinLED Dig-Next-2 (ESP32) — with the PowerManager usermod serving a segment-card menu through this mechanism: injection runs after every render (fetch and WS), re-renders keep injected elements, and stock behavior is untouched when no usermod provides inject code. esp32dev builds clean; generated html_*.h files are intentionally not included.

What this allows in practice — a usermod-provided menu living right on the segment card, with zero PowerManager-specific code in index.js:

The PowerManager usermod's 'Power relays' menu injected into a WLED segment card via /um.js

One practical note for usermod authors that surfaced during testing: injected code runs on the main UI page, where only its globals (d, gId, requestJson, ...) exist — settings-page helpers like cE() are not available.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for usermods to inject custom JavaScript into the main web interface.
    • Added the /um.js endpoint to serve dynamic usermod UI enhancements.
    • Usermod-provided interface changes are loaded automatically when available.
  • Bug Fixes

    • Protected UI injection errors from interrupting normal state rendering.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16155a26-0385-499b-976e-81e9872a59d6

📥 Commits

Reviewing files that changed from the base of the PR and between cd7de51 and 8d86f87.

📒 Files selected for processing (1)
  • wled00/data/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/data/index.js

Walkthrough

WLED adds usermod UI-injection hooks, aggregates generated JavaScript into a new /um.js endpoint, and loads that script in the main UI when usermod data is reported. State rendering safely invokes umInject with the latest state.

Changes

Usermod UI injection

Layer / File(s) Summary
Usermod injection contracts
wled00/fcn_declare.h
Adds the UI-injection compile-time flag and virtual hooks for usermods and UsermodManager.
Injection endpoint aggregation
wled00/um_manager.cpp, wled00/wled_server.cpp
Aggregates usermod JavaScript and serves it from /um.js with non-caching headers.
Browser loading and state integration
wled00/data/index.js
Loads /um.js once when usermod information exists and safely calls umInject(s) during state rendering.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant WLEDServer
  participant UsermodManager
  participant Usermods
  Browser->>WLEDServer: GET /um.js
  WLEDServer->>UsermodManager: addUIInjectCode(dest)
  UsermodManager->>Usermods: addUIInjectCode(dest)
  Usermods-->>UsermodManager: JavaScript injection code
  UsermodManager-->>WLEDServer: Aggregated JavaScript
  WLEDServer-->>Browser: application/javascript response
Loading
sequenceDiagram
  participant WLEDUI
  participant WLEDServer
  participant umInject
  WLEDUI->>WLEDUI: Receive json.info.u
  WLEDUI->>WLEDServer: Load /um.js once
  WLEDServer-->>WLEDUI: Define umInject(s)
  WLEDUI->>WLEDUI: Render state with readState(s)
  WLEDUI->>umInject: Safely invoke umInject(s)
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a usermod web UI injection mechanism via /um.js.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

intermittech added a commit to intermittech/wled-usermod-powermanager that referenced this pull request Jul 18, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wled00/data/index.js`:
- Line 1624: Wrap the umInject(s) call in the usermod injection check within a
try...catch so exceptions from injected JavaScript cannot escape readState().
Preserve the existing function-type guard, and handle the caught error locally
without disrupting requestJson() or triggering its retry behavior.
- Line 1823: Update the usermod injection check in the surrounding dashboard
initialization to use logical checks instead of optional chaining, preserving
the condition that usermod data exists. Call loadUmInject with the current state
object s so it does not perform a redundant fetch during script loading.
- Around line 232-240: Update loadUmInject to accept the current state s, and in
its script onload handler invoke umInject(s) directly when available instead of
calling requestJson(). Wrap the usermod invocation in try/catch so injected
exceptions are contained and do not interrupt readState(); regenerate the web UI
headers by running npm run build after editing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6472d9b7-57e0-4787-90fd-dcd4ebb1361c

📥 Commits

Reviewing files that changed from the base of the PR and between a962116 and cd7de51.

📒 Files selected for processing (4)
  • wled00/data/index.js
  • wled00/fcn_declare.h
  • wled00/um_manager.cpp
  • wled00/wled_server.cpp

Comment thread wled00/data/index.js Outdated
Comment thread wled00/data/index.js Outdated
Comment thread wled00/data/index.js Outdated
intermittech added a commit to intermittech/wled-usermod-powermanager that referenced this pull request Jul 18, 2026
- umInjectSafe() wraps umInject() in try/catch so an exception in
  usermod-provided JS cannot abort readState() and trigger
  requestJson()'s retry loop
- pass the current state into loadUmInject(s) and run the first
  injection directly on script load instead of refetching /json/si
- use json.info && json.info.u to match the surrounding style
intermittech added a commit to intermittech/wled-usermod-powermanager that referenced this pull request Jul 18, 2026
@willmmiles

Copy link
Copy Markdown
Member

I guess that's 3 of us working on this feature now!

I would vote against this approach entirely, though. Usermod UI assembly and injection should be done at compile time, so it can also leverage the zipping and minification features. Runtime assembly also opens a number of memory management problems that can be entirely avoided.

I've been working on a manifest-based system, though I haven't got to the main UI reparse points yet. I've pushed it up what's available now as PR #5742 . Maybe we could integrate the main UI injection hook to run at build time?

@blazoncek

Copy link
Copy Markdown
Contributor

Usermod needs to inject variable elements at runtime. So... I'm going to watch closely how you'll manage to do that at compile time.

On the other hand, nothing is prohibiting usermod to include pre-minimised and compressed web page header and (then) register a web request handler right now. For the later see my Multi relay usermod.

The footprint and complexity of this approach is minimal and all that is needed additionally are some support JS functions and DOM objects so that usermod developer can leverage some work.

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