Skip to content

Add settings page to hide unwanted effects from the picker#5585

Open
1saac-k wants to merge 2 commits intowled:mainfrom
1saac-k:hide-effect
Open

Add settings page to hide unwanted effects from the picker#5585
1saac-k wants to merge 2 commits intowled:mainfrom
1saac-k:hide-effect

Conversation

@1saac-k
Copy link
Copy Markdown

@1saac-k 1saac-k commented May 9, 2026

Background

This PR resolves #1262. I felt the same itch as a user myself: of the 220+ effects, I only regularly use a dozen or so, while the rest just add noise to the picker. The ability to trim the visible effect list per user is also useful for external integrations — particularly automation systems like Home Assistant that surface WLED effects as a dropdown.

Alternatives discussed in #1262

The issue was opened back in 2020, and many workarounds and alternatives have been proposed in the comments since. Summarizing:

  1. Deleting effects from source (incl. Joshfindit's hack) — Joshfindit blanks the effect name to make it unselectable in the UI / HA. Same build-environment burden, same ID-shift risk.
  2. Storing favorite effects as presets (Aircoookie and others) — Since 0.11.0 you can create up to ~100 named presets to bundle frequently used effect combinations. This is a workaround at best: the picker itself doesn't shrink, and external integrations (e.g. HA's effect dropdown) read the effect list, not presets, so it doesn't apply there.
  3. Hiding via external CSS (jeffmission.com/wledcss etc.) — Hides items only in the UI. The JSON API still exposes them, so external integrations are unaffected.
  4. Merging similar effects (Aircoookie) — Folding effectively-duplicate effects (e.g. Candle / Multi Candle) together via internal options (checkboxes etc.) to reduce the total count. Some merges have actually happened, but you can't meaningfully reduce 220+ entries this way, and it doesn't address per-user preferences (e.g. avoiding photosensitive-epilepsy triggers).
  5. Search bar / quick filters (blazoncek, 0.12+) — Substring search and category filters. Adds friction (you have to search every time) and doesn't apply to external integrations.
  6. Auto 2D/1D filter (blazoncek) — WLED already auto-hides 2D-only effects in 1D strip configurations. But within the same dimension it provides no per-user filtering.
  7. Effect metadata / tag-based filtering (Joshfindit, with blazoncek's response) — /json/fxdata already exposes some capability metadata, which clients like HA could use to filter. However (a) every external integration would have to ship a metadata-aware patch, and (b) it can't express arbitrary per-user preferences ("I just don't like any strobe-style effect").

This PR aims to fill the two gaps that the alternatives above each only partially address: (a) the firmware itself being able to remove unwanted effects from the picker, and (b) a single mechanism that applies uniformly to external integrations (HA, python-wled, etc.).

Approaches considered

Reframing the discussion above from an implementation perspective, the candidates I considered:

  1. Exclude at build time — The only option a user has today. The build setup overhead is high, and re-flashing custom firmware just to drop a few effects is operationally too expensive.
  2. Client-side filter in the web UI only — Have index.htm apply a user-specified list when drawing the effect picker. Small change footprint, but the JSON API still exposes everything, so HA and other integrations don't benefit. State sharing across browsers on the same device is also awkward (localStorage limits).
  3. Remove effects from _modeData (actual deletion) — The most direct option, but every subsequent ID shifts down, breaking every preset / playlist / automation that references an ID. Not realistic compatibility-wise.
  4. Overwrite _modeData[id] = _data_RESERVED (destructive RSVD swap) — Reuses the existing RSVD slot convention. The externally observable behavior is clean, but the original name is lost, which makes building an unhide UI structurally impossible.
  5. Bitmap + reuse of the RSVD convention (chosen) — Use uint32_t hiddenFxMask[8] (32 bytes) as the source of truth and add a one-line isFxHidden(id) check at the two sites that already test for RSVD (serializeModeNames, Segment::setMode). _modeData is never modified.

Other changes

This PR contains two commits:

  1. subPage guard fix — Replaces the hardcoded subPage > 10 guard in handleSettingsSet with a SUBPAGE_LAST reference. I found it while working on this feature, but it has actually been latent since SUBPAGE_PINS=11 was added (SUBPAGE_PINS is GET-only, so it never tripped the form-POST guard and the bug went unnoticed). It stands on its own as a cleanup, so I split it into a separate commit.

  2. Per-effect hide feature — Bitmap, runtime checks, /settings/fx page, /json/effects?all=1.

Limitations and possible follow-ups

Help link target not yet documented

The ? button on the settings page links to features/effects/#effect-visibility on kno.wled.ge, which doesn't exist yet.

A structural limitation: /json/effects responses still contain RSVD

Worth flagging separately from this PR, an existing API constraint. /json/effects returns a flat array of effect names where the array index is the effect ID — because the API used to set an effect (/json/state's seg.fx) is a numeric ID. So if the server were to drop RSVD slots from the response, clients could no longer derive the ID from the index. As long as the index = ID convention holds, RSVD slots have to remain in the response.

WLED's own index.htm filters out the RSVD name on the client side, so users never see it. Other clients vary. In particular, Home Assistant's WLED integration and the [python-wled](https://github.com/frenck/python-wled) wrapper it uses do no such filtering, so "RSVD" entries leak into HA's effect dropdown — a UX defect.

There are three possible directions to fix this:

  • Improve the WLED API — A new endpoint such as /json/effects-v2 that returns [{id, name}, ...]. Breaks the index dependency, so RSVD can be omitted safely.
  • Filter in python-wled — Filter RSVD at the library layer; every client that uses python-wled (HA included) benefits at once.
  • Filter in each end client (HA component etc.) — Most fragmented; the same patch would need to be repeated per integration.

As a first step, I've opened an issue against python-wled: frenck/python-wled#2053.

Effect reordering

This PR only addresses hide, not reorder, but the two-list (Visible / Hidden) layout of the settings page was chosen with reorder in mind as a future addition. Reorder runs into the same /json/effects index = ID constraint — it can't be exposed cleanly server-side until the API moves to id-name pairs as described above.

Screenshots

hide-effect1

New settings page. Open to feedback on consolidating it into an existing page (e.g., User Interface).

hide-effect2

Select All, then deselect 4 effects.

hide-effect3

Hide the selected effects.

hide-effect4

Effect picker after save.

Summary by CodeRabbit

  • New Features

    • Added an "Effect Visibility" settings page accessible from Settings to show/hide individual effects; changes persist.
    • Settings page supports selecting/moving effects between Visible and Hidden lists; reserved slots and effect 0 cannot be hidden.
    • API now supports listing all effects (including hidden) when requested.
  • Bug Fixes / Behavior

    • Effect selection now skips reserved or hidden effects in mode-selection flows.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

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: 9837d116-4ac7-44fd-8b6c-890e16eacb0f

📥 Commits

Reviewing files that changed from the base of the PR and between a36c666 and 93543a5.

📒 Files selected for processing (11)
  • tools/cdata.js
  • wled00/FX_fcn.cpp
  • wled00/cfg.cpp
  • wled00/const.h
  • wled00/data/settings.htm
  • wled00/data/settings_fx.htm
  • wled00/fcn_declare.h
  • wled00/json.cpp
  • wled00/set.cpp
  • wled00/wled.h
  • wled00/wled_server.cpp
✅ Files skipped from review due to trivial changes (1)
  • wled00/data/settings.htm
🚧 Files skipped from review as they are similar to previous changes (10)
  • wled00/wled_server.cpp
  • wled00/fcn_declare.h
  • wled00/const.h
  • wled00/set.cpp
  • wled00/data/settings_fx.htm
  • wled00/FX_fcn.cpp
  • wled00/cfg.cpp
  • wled00/wled.h
  • tools/cdata.js
  • wled00/json.cpp

Walkthrough

Adds per-effect hiding: a 256-bit hidden mask, config persistence, exclusion from mode selection, optional API hiding, a new Effect Visibility settings page, and build integration for the page.

Changes

Effect Visibility / Hiding

Layer / File(s) Summary
Data Structures and Constants
wled00/wled.h, wled00/const.h
Introduces hiddenFxMask[8] bitset with inline isFxHidden() and setFxHidden() helpers. Adds SUBPAGE_FX (12) and updates SUBPAGE_LAST.
Configuration Serialization
wled00/cfg.cpp
Deserializes fx.hidden JSON array into bitmask during config load; serializes hidden effect IDs back to config during save (skips id 0, clamps to mode count).
Effect Selection Logic
wled00/FX_fcn.cpp
Segment::setMode() now skips reserved and hidden effects when cycling modes.
Effect Name API & Declaration
wled00/fcn_declare.h, wled00/json.cpp
serializeModeNames() gains bypassHide parameter (default false). When false, hidden effects serialize as "RSVD"; when true, actual names are emitted. /json/eff passes bypassHide based on all query param.
Settings Page Routing
wled00/const.h, wled00/wled_server.cpp
Recognizes /settings/fx, maps to SUBPAGE_FX, serves PAGE_settings_fx, and adds the save-success text.
Form Submission Handler
wled00/set.cpp
handleSettingsSet() validates subPage against SUBPAGE_LAST and handles SUBPAGE_FX POST: resets bitmask and sets hidden IDs from comma-separated FXH.
Settings UI
wled00/data/settings.htm, wled00/data/settings_fx.htm
Adds an "Effect Visibility" nav button and a new settings page that fetches effects and config, renders Visible/Hidden lists, supports moving selections, detects unsaved changes, serializes hidden IDs into FXH, and submits.
Build Process
tools/cdata.js
Adds settings_fx.htm to HTML minify + gzip pipeline and generates PAGE_settings_fx in html_settings.h.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant Server
  participant Config
  participant Bitmask as hiddenFxMask
  Browser->>Server: GET /settings/fx (loads PAGE_settings_fx)
  Browser->>Server: GET /json/effects?all=1
  Browser->>Server: GET /json/cfg
  Server-->>Browser: effects list + cfg (includes fx.hidden)
  Browser->>Browser: render Visible/Hidden lists
  Browser->>Server: POST /settings (FXH=ids...)
  Server->>Bitmask: reset and setFxHidden(ids)
  Server->>Config: serializeConfig() writes fx.hidden
  Server-->>Browser: save response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • wled/WLED#4982: Also modifies tools/cdata.js HTML generation and asset chunking.
  • wled/WLED#5010: Adds static asset chunks via tools/cdata.js (related build artifact changes).
  • wled/WLED#4609: Overlapping changes to config/JSON construction and endpoints.

Suggested reviewers

  • blazoncek
  • netmindz
  • DedeHai
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: adding a settings page that allows users to hide effects from the picker, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #1262: per-effect visibility toggles in settings, hidden effects remain available to maintain ID/preset compatibility, and the effect picker respects the hidden state.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the hide effects feature: bitmap storage, settings page UI, JSON API updates, configuration persistence, and effect selection logic.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

@1saac-k 1saac-k changed the title Hide effect Add settings page to hide unwanted effects from the picker May 9, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
wled00/wled.h (1)

433-436: ⚡ Quick win

Enforce Solid protection directly in setFxHidden().

setFxHidden() currently allows id == 0, so the “Solid can’t be hidden” rule depends on every caller getting it right. Guarding it here makes the invariant global and future-proof.

Suggested hardening
 inline bool isFxHidden(uint8_t id)            { return (hiddenFxMask[id >> 5] >> (id & 31)) & 1u; }
-inline void setFxHidden(uint8_t id, bool hide){ if (hide) hiddenFxMask[id >> 5] |=  (1u << (id & 31));
-                                                else      hiddenFxMask[id >> 5] &= ~(1u << (id & 31)); }
+inline void setFxHidden(uint8_t id, bool hide){
+  if (id == 0) return; // Solid must always stay visible
+  if (hide) hiddenFxMask[id >> 5] |=  (1u << (id & 31));
+  else      hiddenFxMask[id >> 5] &= ~(1u << (id & 31));
+}
🤖 Prompt for 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.

In `@wled00/wled.h` around lines 433 - 436, setFxHidden currently permits hiding
effect id==0 ("Solid"), leaving protection to callers; modify
setFxHidden(uint8_t id, bool hide) to enforce Solid protection inside the
function by ignoring any attempt to hide id==0 (e.g., if id==0 treat hide as
false or return early). Use the existing symbols (setFxHidden, hiddenFxMask,
isFxHidden) to locate the logic and ensure the bitmask is only modified for ids
!= 0 so the invariant "Solid can't be hidden" is enforced globally.
🤖 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/settings_fx.htm`:
- Around line 84-93: The fx items are rendered as non-focusable divs so keyboard
users can't activate toggleSel; make each element with class 'fxitem' focusable
and keyboard-operable by adding tabindex="0", an appropriate ARIA role (e.g.,
role="button") and key handlers that call toggleSel on Enter/Space, and ensure
the existing click handler remains; update the same pattern used at the other
blocks (lines that create li with class 'fxitem' and set li.onclick) so they
also set li.tabIndex = 0, li.setAttribute('role','button') and add a keydown
listener that invokes toggleSel(li) when event.key is "Enter" or " " (Space);
also ensure the 'solid' item remains non-interactive (no tabindex/key handler)
and retains its title.

---

Nitpick comments:
In `@wled00/wled.h`:
- Around line 433-436: setFxHidden currently permits hiding effect id==0
("Solid"), leaving protection to callers; modify setFxHidden(uint8_t id, bool
hide) to enforce Solid protection inside the function by ignoring any attempt to
hide id==0 (e.g., if id==0 treat hide as false or return early). Use the
existing symbols (setFxHidden, hiddenFxMask, isFxHidden) to locate the logic and
ensure the bitmask is only modified for ids != 0 so the invariant "Solid can't
be hidden" is enforced globally.
🪄 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: 03f3b360-956c-4e5f-a0e3-33a08ca9a6a4

📥 Commits

Reviewing files that changed from the base of the PR and between 37623ed and 337c3ba.

📒 Files selected for processing (11)
  • tools/cdata.js
  • wled00/FX_fcn.cpp
  • wled00/cfg.cpp
  • wled00/const.h
  • wled00/data/settings.htm
  • wled00/data/settings_fx.htm
  • wled00/fcn_declare.h
  • wled00/json.cpp
  • wled00/set.cpp
  • wled00/wled.h
  • wled00/wled_server.cpp

Comment on lines +84 to +93
const li = d.createElement('div');
li.className = 'fxitem';
li.dataset.id = e.id;
li.textContent = e.name;
if (e.id === 0) {
li.classList.add('solid');
li.title = 'Solid cannot be hidden';
} else {
li.onclick = () => toggleSel(li);
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make effect selection keyboard-operable (currently mouse-only).

Line 84+ renders selectable effects as non-focusable <div> elements, so keyboard users can’t complete hide/show operations.

♿ Suggested fix
 function render() {
 	const visList = gId('visList');
 	const hidList = gId('hidList');
+	visList.setAttribute('role', 'listbox');
+	visList.setAttribute('aria-multiselectable', 'true');
+	hidList.setAttribute('role', 'listbox');
+	hidList.setAttribute('aria-multiselectable', 'true');
 	visList.innerHTML = '';
 	hidList.innerHTML = '';
@@
 			const li = d.createElement('div');
 			li.className = 'fxitem';
+			li.setAttribute('role', 'option');
+			li.setAttribute('aria-selected', 'false');
 			li.dataset.id = e.id;
 			li.textContent = e.name;
 			if (e.id === 0) {
 				li.classList.add('solid');
+				li.tabIndex = -1;
 				li.title = 'Solid cannot be hidden';
 			} else {
+				li.tabIndex = 0;
 				li.onclick = () => toggleSel(li);
+				li.onkeydown = (ev) => {
+					if (ev.key === 'Enter' || ev.key === ' ') {
+						ev.preventDefault();
+						toggleSel(li);
+					}
+				};
 			}
@@
 function toggleSel(li) {
-	li.classList.toggle('sel');
+	const selected = li.classList.toggle('sel');
+	li.setAttribute('aria-selected', selected ? 'true' : 'false');
 	updateBtns();
 }

Also applies to: 102-112, 163-177

🤖 Prompt for 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.

In `@wled00/data/settings_fx.htm` around lines 84 - 93, The fx items are rendered
as non-focusable divs so keyboard users can't activate toggleSel; make each
element with class 'fxitem' focusable and keyboard-operable by adding
tabindex="0", an appropriate ARIA role (e.g., role="button") and key handlers
that call toggleSel on Enter/Space, and ensure the existing click handler
remains; update the same pattern used at the other blocks (lines that create li
with class 'fxitem' and set li.onclick) so they also set li.tabIndex = 0,
li.setAttribute('role','button') and add a keydown listener that invokes
toggleSel(li) when event.key is "Enter" or " " (Space); also ensure the 'solid'
item remains non-interactive (no tabindex/key handler) and retains its title.

The guard in handleSettingsSet was `subPage > 10`, which silently
rejected SUBPAGE_PINS (11) and any future subPages. SUBPAGE_PINS
happens to be a GET-only page so the mismatch never surfaced;
referencing SUBPAGE_LAST keeps the guard in sync as new subPages
are added.
Lets users hide individual effects so they don't clutter the effect
picker. Hidden state persists in cfg.fx.hidden and takes effect
immediately. Effect IDs are not reshuffled, so existing presets,
playlists, and external automations keep parsing without errors.
References to a hidden effect fall through to the next visible
effect until it's un-hidden.

Implementation reuses the existing RSVD placeholder convention
(already used for build-disabled and deprecated effect slots): a
32-byte bitmap (uint32_t hiddenFxMask[8]) is consulted at the same
sites that already handle RSVD:
- serializeModeNames emits "RSVD" when isFxHidden(id)
- Segment::setMode skips hidden effects alongside true reserved slots

Existing clients (web UI effect picker, HA, python-wled, etc.) thus
treat user-hidden effects exactly like RSVD without any changes.
/json/effects is byte-identical to prior firmware when nothing is
hidden; once any effects are hidden, those ids surface as "RSVD"
through the existing filter path.

_modeData is never modified, so original names remain recoverable.
A new opt-in /json/effects?all=1 query returns real names for
user-hidden effects so the un-hide UI can show what's currently
hidden. Solid (id 0) is protected -- never user-hidable.

UI: new /settings/fx page with stacked Visible/Hidden lists,
multi-select, Hide / Show move buttons, and per-list All/None
batch selection.
@softhack007
Copy link
Copy Markdown
Member

Adds per-effect hiding: a 256-bit hidden mask

@1saac-k (early feedback) a mask with 256 bits will probably not be future - proof. We are discussing to allow more than 254 effects, and one approach is to widen the 8bit effect ID to 16bit. Maybe re-think your proposal so it's not limited to 256 effects. Usermods can add effects, and with all 1D and 2D effects we have + usermod effects, we are landing around 280 or more effects already.

@softhack007 softhack007 added the needs_rework PR needs improvements before merging (RED FLAG) label May 9, 2026
@softhack007
Copy link
Copy Markdown
Member

@1saac-k force-pushed the hide-effect branch from 337c3ba to a36c666
6 hours ago

@1saac-k please do NOT force-push while you have a PR open. We will squash-and-merge your PR, so just add commits when you have something new.

https://github.com/wled/WLED?tab=contributing-ov-file#during-review

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

Labels

effect enhancement needs_rework PR needs improvements before merging (RED FLAG)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Disable effects

2 participants