Skip to content

Adding usermod palettes and fix UI palette display#5548

Merged
netmindz merged 10 commits into
wled:mainfrom
DedeHai:custom_and_AR_palettefix
May 3, 2026
Merged

Adding usermod palettes and fix UI palette display#5548
netmindz merged 10 commits into
wled:mainfrom
DedeHai:custom_and_AR_palettefix

Conversation

@DedeHai
Copy link
Copy Markdown
Collaborator

@DedeHai DedeHai commented Apr 26, 2026

Usermods get their own palette "space" right after the built-in palettes, named by the usermod name (for example "AudioReactive 0". Custom palettes now start at palette ID 200 and downwards, keeping IDs consistent to not break presets when enabling/disabling AR palettes.
Also added a fix to reload the palette gradients when the number of total palettes changes so the palette list should now always properly update.

Summary by CodeRabbit

  • New Features

    • Added a distinct "usermod" palette category with named usermod palettes visible in the UI and API and included in palette counts/exports.
    • Usermod palettes can occupy multiple slots and report per-slot names/variants.
  • Bug Fixes / Reliability

    • Improved palette ID range handling and clamping to prevent invalid references.
    • More robust removal of palettes that avoids index-shift issues and reports deletions.
  • UI

    • Palette lists and previews now show usermod entries separately; cache is cleared on delete to avoid stale previews.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Walkthrough

Adds a usermod-scoped palette registry (usermodPalettes) and explicit palette ID ranges for fixed/custom/usermod palettes; updates palette lookup, serialization, and UI to surface usermod palettes; and migrates AudioReactive to register, create, remove, and fill palettes from the new usermod palette pool.

Changes

Palette ID space, plumbing, UI, and AudioReactive usermod integration

Layer / File(s) Summary
Data Shape
wled00/const.h
Add WLED_USERMOD_PALETTE_ID_BASE, WLED_CUSTOM_PALETTE_ID_BASE, WLED_MAX_USERMOD_PALETTES; adjust WLED_MAX_CUSTOM_PALETTES computation.
Data Shape
wled00/colors.h, wled00/wled.h
Add UsermodPalette struct, declare extern std::vector<UsermodPalette> usermodPalettes, declare removeUsermodPalettes(const char*), and include usermod count in getPaletteCount().
Core API
wled00/colors.cpp
Implement removeUsermodPalettes(const char*) to remove matching UsermodPalette entries (reverse-iterate) and return removal count.
FX mapping
wled00/FX_fcn.cpp
Update Segment::loadPalette() and Segment::setPalette() to split ID space (fixed → custom → usermod), index into the correct pool, and clamp invalid IDs.
Name extraction
wled00/util.cpp
Change extractModeName() to map palette IDs to fixed/custom/usermod ranges and format usermod palette names using UsermodPalette fields.
JSON / Server serialization
wled00/json.cpp
serializeInfo() exports umpalcount/umpalnames; serializePalettes() enumerates fixed → usermod → custom palettes and sources color data from usermodPalettes[].palette for usermod slots.
Usermod (AudioReactive) wiring
usermods/audioreactive/audio_reactive.cpp
Replace customPalettes usage with usermodPalettes: createAudioPalettes() pushes up to MAX_PALETTES UsermodPalette entries (black-init, store name, palIndex, palName); removeAudioPalettes() calls removeUsermodPalettes(_name) and clamps counters; fillAudioPalettes() rebuilds gradients into usermodPalettes[].palette; update onStateChange() reconstruction condition; add PROGMEM display-name strings.
Client / UI
wled00/data/index.js, wled00/data/cpal/cpal.htm
Render usermod palettes separately (mapped to high IDs) then custom palettes, include umpalcount/umpalnames and validate preview cache by pcount+vid; on delete, clear localStorage['wledPalx'] and delay refresh to avoid stale preview.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Client UI
  participant Server as WLED Server
  participant Colors as Colors Module
  participant UM as Usermod (AudioReactive)

  UI->>Server: GET /json/info or /json/palettes
  Server->>Colors: serializeInfo()/serializePalettes()
  Colors->>UM: read `usermodPalettes` entries
  Colors-->>Server: JSON (fixed → usermod → custom, umpalcount, umpalnames)
  Server-->>UI: palette JSON
  UI->>UI: render usermod + custom palettes (validate cache pcount+vid)
  UI->>Server: DELETE palette request
  Server->>Colors: removeUsermodPalettes(name) or custom removal
  Colors->>UM: remove matching usermod palette entries
  Colors-->>Server: updated palette state
  Server-->>UI: delayed refresh (client clears cached wledPalx)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 summarizes the main changes: introducing a dedicated usermod palette space and fixing UI palette display to prevent ID conflicts.
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.

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


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@coderabbitai coderabbitai Bot added enhancement usermod usermod related labels Apr 26, 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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/data/index.js (1)

2831-2854: ⚠️ Potential issue | 🟠 Major

The new palcount cache key still leaves same-session palette previews stale.

d.pcount only invalidates localStorage on a fresh load. Once palettesData is set, Line 2832 returns early forever, so enabling/disabling usermod palettes or adding/removing custom palettes after connect never refetches /json/palx; the preview gradients stay stale until a full page reload.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/index.js` around lines 2831 - 2854, The early-return "if
(palettesData) return resolve();" causes same-session palette changes to be
ignored; instead, validate the in-memory cache against the stored cache and
current device metadata (lastinfo.vid and lastinfo.palcount) and only skip
fetching if they match. Update the logic around
palettesData/localStorage.getItem("wledPalx") to: if palettesData exists and the
cached meta (d.vid and d.pcount) equals lastinfo.vid and lastinfo.palcount then
resolve immediately; otherwise clear/refetch by calling getPalettesData(0, ...)
and then set localStorage and call redrawPalPrev. Ensure the check uses the same
symbols (palettesData, lastinfo.vid, lastinfo.palcount, getPalettesData,
redrawPalPrev, and "wledPalx") so enabling/disabling usermod or adding palettes
during a session triggers a refresh.
🧹 Nitpick comments (2)
usermods/audioreactive/audio_reactive.cpp (1)

2196-2199: Minor: comment example doesn't match palIndex starting at 0.

The loop pushes palIndex = 0, 1, 2, so the first generated name will be "AudioReactive 0", not "AudioReactive 1" as the comment suggests. Tweak the comment for accuracy.

📝 Proposed comment fix
-      // reuse _name ("AudioReactive") as base; palIndex 0/1/2... builds "AudioReactive 1" etc.
+      // reuse _name ("AudioReactive") as base; palIndex 0/1/2... builds "AudioReactive 0", "AudioReactive 1", ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/audioreactive/audio_reactive.cpp` around lines 2196 - 2199, Comment
mismatch: the loop pushing entries into usermodnPalettes uses (uint8_t)i as the
palette index so names will be "AudioReactive 0", "AudioReactive 1", ... not
starting at 1; update the inline comment near the usermodnPalettes.push_back
call (and any mention of palIndex) to accurately state that palIndex starts at 0
and the first generated name will be "AudioReactive 0".
wled00/colors.h (1)

64-70: Minor: comment typos and stale example.

  • "palette name name will be um_name + index" — duplicated word "name".
  • Example reads "AudioReactive 1" but with palIndex starting at 0 the first generated label is "AudioReactive 0".
📝 Proposed comment fix
 // Palette registered by a usermod at fixed IDs (255, 254, 253... 201)
-// palette name name will be um_name + index (e.g. "AudioReactive 1"), see util.cpp
+// Palette name will be um_name + palIndex (e.g. "AudioReactive 0"), see util.cpp.
 struct UsermodnPalette {
   CRGBPalette16 palette;
   const char   *name;      // PROGMEM base name string (must not be nullptr)
-  uint8_t       palIndex; // index of the palette for a usermod
+  uint8_t       palIndex;  // index of the palette for a usermod
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` around lines 64 - 70, Fix the comment on the UsermodnPalette
struct: remove the duplicated word "name", clarify construction of the palette
name (it's um_name + index as a suffix) and correct the example to reflect that
palIndex starts at 0 (e.g., "AudioReactive 0"); update the comment near the
struct UsermodnPalette (referencing fields palette, name, palIndex and
util.cpp/um_name) so it accurately documents that name is a PROGMEM base name
and the generated label uses palIndex starting from 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/data/index.js`:
- Around line 992-1023: The dynamic palette sections append usermod/custom rows
after requestJson() has already called updateSelectedPalette(selectedPal), so if
the active palette lives in those newly-created ID ranges it isn't visually
selected; fix by reapplying the selection after building those lists—i.e., after
the usermod loop and the custom palette loop (where generateListItemHtml,
genPalPrevCss, 'pallist', lastinfo and palettesData are used), call
updateSelectedPalette(selectedPal) (or the existing selection-reapply helper) so
the active item is visually marked.

In `@wled00/FX_fcn.cpp`:
- Around line 236-242: The palette validation block that checks pal against
FIXED_PALETTE_COUNT/WLED_CUSTOM_PALETTE_ID_BASE/WLED_USERMOD_PALETTE_ID_BASE
using custCount/umCount must be moved to after the code that resolves pal == 0
to _default_palette (the code path from setMode()), so that the resolved
_default_palette is also validated before indexing customPalettes or
usermodPalettes; update the same duplicated guard in the other function (the one
around lines 278–281) as well, ensuring pal (or resolved _default_palette) is
clamped to 0 when out of range before any access to customPalettes or
usermodPalettes.

In `@wled00/util.cpp`:
- Around line 320-329: The code dereferences usermodnPalettes[umIdx] without
checking that umIdx is within usermodnPalettes.size(), which can read past the
vector when a usermod is disabled or exposes fewer palettes; update the
JSON_palette_names branch where umIdx is computed (using
WLED_USERMOD_PALETTE_ID_BASE - mode) to validate that umIdx <
usermodnPalettes.size() before accessing usermodnPalettes[umIdx], and if out of
range use a safe fallback (e.g., empty or placeholder base name and palIndex 0)
when calling snprintf to format dest so no invalid indexing occurs.

---

Outside diff comments:
In `@wled00/data/index.js`:
- Around line 2831-2854: The early-return "if (palettesData) return resolve();"
causes same-session palette changes to be ignored; instead, validate the
in-memory cache against the stored cache and current device metadata
(lastinfo.vid and lastinfo.palcount) and only skip fetching if they match.
Update the logic around palettesData/localStorage.getItem("wledPalx") to: if
palettesData exists and the cached meta (d.vid and d.pcount) equals lastinfo.vid
and lastinfo.palcount then resolve immediately; otherwise clear/refetch by
calling getPalettesData(0, ...) and then set localStorage and call
redrawPalPrev. Ensure the check uses the same symbols (palettesData,
lastinfo.vid, lastinfo.palcount, getPalettesData, redrawPalPrev, and "wledPalx")
so enabling/disabling usermod or adding palettes during a session triggers a
refresh.

---

Nitpick comments:
In `@usermods/audioreactive/audio_reactive.cpp`:
- Around line 2196-2199: Comment mismatch: the loop pushing entries into
usermodnPalettes uses (uint8_t)i as the palette index so names will be
"AudioReactive 0", "AudioReactive 1", ... not starting at 1; update the inline
comment near the usermodnPalettes.push_back call (and any mention of palIndex)
to accurately state that palIndex starts at 0 and the first generated name will
be "AudioReactive 0".

In `@wled00/colors.h`:
- Around line 64-70: Fix the comment on the UsermodnPalette struct: remove the
duplicated word "name", clarify construction of the palette name (it's um_name +
index as a suffix) and correct the example to reflect that palIndex starts at 0
(e.g., "AudioReactive 0"); update the comment near the struct UsermodnPalette
(referencing fields palette, name, palIndex and util.cpp/um_name) so it
accurately documents that name is a PROGMEM base name and the generated label
uses palIndex starting from 0.
🪄 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: a4bc47a8-f688-42ad-8329-585d2d0c31c0

📥 Commits

Reviewing files that changed from the base of the PR and between a535c56 and fd6f568.

📒 Files selected for processing (10)
  • usermods/audioreactive/audio_reactive.cpp
  • wled00/FX_fcn.cpp
  • wled00/colors.cpp
  • wled00/colors.h
  • wled00/const.h
  • wled00/data/cpal/cpal.htm
  • wled00/data/index.js
  • wled00/json.cpp
  • wled00/util.cpp
  • wled00/wled.h

Comment thread wled00/data/index.js
Comment thread wled00/FX_fcn.cpp
Comment thread wled00/util.cpp
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

♻️ Duplicate comments (1)
wled00/FX_fcn.cpp (1)

236-244: ⚠️ Potential issue | 🔴 Critical

Validate _default_palette after resolving pal == 0.

The guard at Lines 236-242 still runs before Line 244 replaces pal with _default_palette, so a stale default palette in the custom/usermod range bypasses validation and can still drive the unchecked indexing at Lines 278-281.

Suggested fix
-  const int umCount   = usermodPalettes.size();
-  const int custCount = customPalettes.size();
-  if (pal >= FIXED_PALETTE_COUNT) {
-    if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range (IDs 201-255)
-      if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= umCount) pal = 0;
-    } else { // custom range
-      if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= custCount) pal = 0;
-    }
-  }
   //default palette. Differs depending on effect
   if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
+  const int umCount   = usermodPalettes.size();
+  const int custCount = customPalettes.size();
+  if (pal >= FIXED_PALETTE_COUNT) {
+    if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range (IDs 201-255)
+      if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= umCount) pal = 0;
+    } else { // custom range
+      if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= custCount) pal = 0;
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_fcn.cpp` around lines 236 - 244, After assigning pal =
_default_palette when pal==0, re-run the same validation logic against
_default_palette to ensure it doesn't fall into the custom/usermod ranges
unchecked; specifically, after the line that sets pal = _default_palette, apply
the same checks used above (compare pal against FIXED_PALETTE_COUNT, then handle
ranges using WLED_USERMOD_PALETTE_ID_BASE, WLED_CUSTOM_PALETTE_ID_BASE and the
counts umCount/custCount) and reset pal to 0 (or another safe fallback) if the
default is out of bounds to prevent invalid indexing later in the code.
🧹 Nitpick comments (2)
wled00/colors.h (1)

64-69: Verify the struct name UsermodnPalette — looks like a typo.

The lowercase n between Usermod and Palette reads as a typo. If it stands for "named" or "numbered", consider UsermodNamedPalette / UsermodNumberedPalette; otherwise UsermodPalette would be more idiomatic and matches the surrounding usermodPalettes vector. Renaming now (before this lands and ripples to public usermod-facing examples) is much cheaper than later.

♻️ Suggested rename
-struct UsermodnPalette {
+struct UsermodPalette {
   CRGBPalette16 palette;
   const char   *name;      // PROGMEM base name string (must not be nullptr), this name is used in removeusermodPalettes()
   uint8_t       palIndex;  // index of the palette for a usermod
 };
@@
-extern std::vector<UsermodnPalette> usermodPalettes;
+extern std::vector<UsermodPalette> usermodPalettes;

Mirror in wled00/wled.h (WLED_GLOBAL declaration) and any usermod call sites.

As per coding guidelines: "Use PascalCase for classes and structs".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` around lines 64 - 69, The struct name UsermodnPalette
appears to be a typo; rename it to a clearer PascalCase name (e.g.,
UsermodPalette or UsermodNamedPalette) and update all references (the struct
declaration containing CRGBPalette16 palette, const char *name, uint8_t
palIndex) accordingly, including the WLED_GLOBAL declaration in wled.h and any
uses in the usermodPalettes vector or usermod call sites and helper utilities
(e.g., util.cpp) so symbol names remain consistent across the codebase and
public usermod examples.
wled00/data/index.js (1)

992-1023: LGTM — usermod & custom palette appending matches the firmware ID layout.

The descending IDs (255-j and 200-j) match wled00/json.cpp (lines 971-980) and wled00/const.h (WLED_USERMOD_PALETTE_ID_BASE / WLED_CUSTOM_PALETTE_ID_BASE). The !isEmpty(li) guard correctly prevents this code from running on the initial loadPalettes() call (before requestJson() populates lastinfo), and the gray-placeholder skip behavior is preserved. The updateSelectedPalette(selectedPal) call at line 1024 correctly addresses the previous review feedback.

Minor optional cleanup: the createElement('div') → appendChild → outerHTML = dance can be replaced with gId('pallist').insertAdjacentHTML('beforeend', generateListItemHtml(...)), which avoids the throwaway wrapper element. Two near-identical loops could also be deduplicated into a small helper. Not blocking — current behavior is correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/index.js` around lines 992 - 1023, Replace the throwaway-div
pattern (createElement + appendChild + div.outerHTML =) in both loops with
gId('pallist').insertAdjacentHTML('beforeend', generateListItemHtml(...)) to
append items directly; keep the same arguments used with generateListItemHtml
(palette, id, label, 'setPalette', genPalPrevCss(id) HTML) and preserve the
gray-placeholder skip using palettesData and the li checks; optionally factor
the repeated logic into a small helper (e.g., renderPaletteListItem(id, label))
that calls generateListItemHtml and insertAdjacentHTML to avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/colors.h`:
- Line 72: The function name removeusermodPalettes is inconsistent with
camelCase; rename it to removeUsermodPalettes across the codebase. Update the
declaration in colors.h (change removeusermodPalettes to removeUsermodPalettes),
the definition in wled00/colors.cpp (function implementation name), and all call
sites (e.g., wled00/colors.cpp references and
usermods/audioreactive/audio_reactive.cpp) to use removeUsermodPalettes; ensure
any forward declarations, headers, and usages are updated and recompiled to fix
linker/reference errors.

---

Duplicate comments:
In `@wled00/FX_fcn.cpp`:
- Around line 236-244: After assigning pal = _default_palette when pal==0,
re-run the same validation logic against _default_palette to ensure it doesn't
fall into the custom/usermod ranges unchecked; specifically, after the line that
sets pal = _default_palette, apply the same checks used above (compare pal
against FIXED_PALETTE_COUNT, then handle ranges using
WLED_USERMOD_PALETTE_ID_BASE, WLED_CUSTOM_PALETTE_ID_BASE and the counts
umCount/custCount) and reset pal to 0 (or another safe fallback) if the default
is out of bounds to prevent invalid indexing later in the code.

---

Nitpick comments:
In `@wled00/colors.h`:
- Around line 64-69: The struct name UsermodnPalette appears to be a typo;
rename it to a clearer PascalCase name (e.g., UsermodPalette or
UsermodNamedPalette) and update all references (the struct declaration
containing CRGBPalette16 palette, const char *name, uint8_t palIndex)
accordingly, including the WLED_GLOBAL declaration in wled.h and any uses in the
usermodPalettes vector or usermod call sites and helper utilities (e.g.,
util.cpp) so symbol names remain consistent across the codebase and public
usermod examples.

In `@wled00/data/index.js`:
- Around line 992-1023: Replace the throwaway-div pattern (createElement +
appendChild + div.outerHTML =) in both loops with
gId('pallist').insertAdjacentHTML('beforeend', generateListItemHtml(...)) to
append items directly; keep the same arguments used with generateListItemHtml
(palette, id, label, 'setPalette', genPalPrevCss(id) HTML) and preserve the
gray-placeholder skip using palettesData and the li checks; optionally factor
the repeated logic into a small helper (e.g., renderPaletteListItem(id, label))
that calls generateListItemHtml and insertAdjacentHTML to avoid duplication.
🪄 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: 042906d8-0485-499d-9775-423c66d61248

📥 Commits

Reviewing files that changed from the base of the PR and between fd6f568 and f16ca9c.

📒 Files selected for processing (8)
  • usermods/audioreactive/audio_reactive.cpp
  • wled00/FX_fcn.cpp
  • wled00/colors.cpp
  • wled00/colors.h
  • wled00/data/index.js
  • wled00/json.cpp
  • wled00/util.cpp
  • wled00/wled.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • wled00/wled.h
  • usermods/audioreactive/audio_reactive.cpp

Comment thread wled00/colors.h Outdated
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.

🧹 Nitpick comments (2)
usermods/audioreactive/audio_reactive.cpp (1)

1956-1962: Optional: simplify the capacity guard.

createAudioPalettes() already enforces the per-entry capacity check on each push_back, so the additional (int)(usermodPalettes.size() + MAX_PALETTES) <= WLED_MAX_USERMOD_PALETTES here is redundant — and slightly stricter than needed (it refuses to re-create any palettes if there isn't room for all MAX_PALETTES). The (int) cast on a size_t sum is also unnecessary. Given today's configuration this is unreachable, but the redundancy is worth tidying.

♻️ Proposed simplification
 void onStateChange(uint8_t callMode) override {
-  if (initDone && enabled && addPalettes && palettes==0
-      && (int)(usermodPalettes.size() + MAX_PALETTES) <= WLED_MAX_USERMOD_PALETTES) {
+  if (initDone && enabled && addPalettes && palettes == 0) {
     // if palettes were removed during JSON call re-add them
     createAudioPalettes();
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/audioreactive/audio_reactive.cpp` around lines 1956 - 1962, The
onStateChange method contains a redundant and overly strict capacity guard:
remove the "(int)(usermodPalettes.size() + MAX_PALETTES) <=
WLED_MAX_USERMOD_PALETTES" check and the cast inside onStateChange (function
onStateChange) so it simply checks initDone, enabled, addPalettes and
palettes==0 then calls createAudioPalettes(); rely on createAudioPalettes() and
its per-push_back capacity checks to enforce limits for usermodPalettes and
MAX_PALETTES instead of pre-checking here.
wled00/colors.cpp (1)

315-322: LGTM — minor polish on the loop counter type.

The reverse-iteration approach correctly avoids the index-shift issue from erase() and the pointer-identity comparison matches the documented contract on name. One nit: int i = usermodPalettes.size() - 1 relies on the size_t(-1) → int(-1) narrowing when the vector is empty (implementation-defined), and while it works in practice, an unsigned counter avoids the wart.

♻️ Optional polish
-size_t removeUsermodPalettes(const char *name) {
-  size_t before = usermodPalettes.size();
-  for (int i = usermodPalettes.size() - 1; i >= 0; i--) {
-    if (usermodPalettes[i].name == name)
-      usermodPalettes.erase(usermodPalettes.begin() + i);
-  }
-  return before - usermodPalettes.size();
-}
+size_t removeUsermodPalettes(const char *name) {
+  const size_t before = usermodPalettes.size();
+  for (size_t i = before; i-- > 0;) {
+    if (usermodPalettes[i].name == name)
+      usermodPalettes.erase(usermodPalettes.begin() + i);
+  }
+  return before - usermodPalettes.size();
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.cpp` around lines 315 - 322, In removeUsermodPalettes, avoid
using a signed int that underflows when the vector is empty; change the loop
counter to an unsigned size type (size_t) and iterate backwards in a safe
pattern (start from usermodPalettes.size() and decrement while checking bounds,
using i-1 as the index) so erase() still works correctly; keep the existing
comparison usermodPalettes[i].name == name and the return value logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@usermods/audioreactive/audio_reactive.cpp`:
- Around line 1956-1962: The onStateChange method contains a redundant and
overly strict capacity guard: remove the "(int)(usermodPalettes.size() +
MAX_PALETTES) <= WLED_MAX_USERMOD_PALETTES" check and the cast inside
onStateChange (function onStateChange) so it simply checks initDone, enabled,
addPalettes and palettes==0 then calls createAudioPalettes(); rely on
createAudioPalettes() and its per-push_back capacity checks to enforce limits
for usermodPalettes and MAX_PALETTES instead of pre-checking here.

In `@wled00/colors.cpp`:
- Around line 315-322: In removeUsermodPalettes, avoid using a signed int that
underflows when the vector is empty; change the loop counter to an unsigned size
type (size_t) and iterate backwards in a safe pattern (start from
usermodPalettes.size() and decrement while checking bounds, using i-1 as the
index) so erase() still works correctly; keep the existing comparison
usermodPalettes[i].name == name and the return value logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0dfb1e84-daa8-4712-ad66-9fcb5c3ff810

📥 Commits

Reviewing files that changed from the base of the PR and between d7d4e7d and 449069a.

📒 Files selected for processing (3)
  • usermods/audioreactive/audio_reactive.cpp
  • wled00/colors.cpp
  • wled00/colors.h

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/colors.h`:
- Line 75: getPaletteCount() no longer equals "max valid palette ID + 1" because
IDs are non-contiguous (fixed, custom, usermod ranges), so callers that do
things like updateVal(..., 0, getPaletteCount()-1) (e.g., paletteChanged in
set.cpp using updateVal) must stop treating the return as a contiguous upper
bound; change those call sites to validate against the actual ID ranges (fixed:
0..FIXED_PALETTE_COUNT-1, custom:
FIXED_PALETTE_COUNT..FIXED_PALETTE_COUNT+custCount-1 or up to 200, usermod:
201..255 or using umCount) or split the input path to separately clamp/accept
fixed vs custom vs usermod IDs (follow the pattern used in FX_fcn.cpp which
checks umCount/custCount separately) so valid custom/usermod IDs are not
silently clamped.
🪄 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: af06a7fa-620a-4c26-a455-3c75cdc6dafc

📥 Commits

Reviewing files that changed from the base of the PR and between 449069a and eb60ff9.

📒 Files selected for processing (3)
  • wled00/colors.h
  • wled00/util.cpp
  • wled00/wled.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/wled.h

Comment thread wled00/colors.h
@DedeHai DedeHai requested a review from softhack007 April 27, 2026 06:16
@DedeHai
Copy link
Copy Markdown
Collaborator Author

DedeHai commented Apr 27, 2026

@softhack007 this PR is kind of mendatory for 16.0 release, without it, AR palettes mess with the ID numbering of palettes of the now "unrestricted" amount of custom palettes. It takes a fully modular approach by splitting custom palettes and usermod palettes into their own ID space,
UM: 255-201
custom: 200-72 (or whichever is the last regular one)

it is a different "divergence" from MM but it may also be less messy to merge once we move to the out of tree AR version - not sure you would want to adopt this approach into MM but please have a look.

cc @netmindz

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.

🧹 Nitpick comments (2)
usermods/audioreactive/audio_reactive.cpp (1)

2184-2188: Removal accounting: cast is safe today, but a bit fragile.

removeUsermodPalettes() returns size_t; casting to int8_t would wrap if it ever exceeds 127. Today it can't (MAX_PALETTES = 3), and the post-clamp at Line 2187 hides any glitch, so this is fine. Minor stylistic suggestion: do the arithmetic in a wider type to remove the clamp altogether and avoid masking real bugs in case MAX_PALETTES changes later.

♻️ Optional cleanup
-  palettes -= (int8_t)removeUsermodPalettes(_name);
-  if (palettes < 0) palettes = 0; // safeguard
+  const size_t removed = removeUsermodPalettes(_name);
+  palettes = (removed >= (size_t)palettes) ? 0 : (int8_t)(palettes - removed);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/audioreactive/audio_reactive.cpp` around lines 2184 - 2188, The
subtraction currently casts removeUsermodPalettes(_name) to int8_t which can
wrap if the returned size_t ever exceeds 127; instead compute the removal in a
wider signed type and then adjust palettes safely: call
removeUsermodPalettes(_name) into a size_t or int variable (e.g., removed),
subtract that from palettes using a signed conversion, and clamp palettes to >=
0 (and optionally <= MAX_PALETTES) without relying on an int8_t cast; update the
removeAudioPalettes function to use these symbols: removeAudioPalettes,
removeUsermodPalettes, and palettes.
wled00/util.cpp (1)

320-340: Bounds check now in place; minor consistency nits.

The previously‑flagged out‑of‑bounds dereference of usermodPalettes is now guarded at Line 324 — good.

Two small, optional refinements:

  • The custom branch at Line 337 uses snprintf_P(dest, maxLen, ...) (size = maxLen) while the new usermod branch uses snprintf(dest, maxLen + 1, ...) (size = maxLen + 1). Both produce a null‑terminated string within the caller's buffersize = maxLen + 1 buffer, but mixing the two conventions in the same function is easy to misread later — consider aligning them (both maxLen + 1, since that matches the actual buffer size).
  • The format string "%s %u" is a RAM literal; using snprintf_P with PSTR("%s %u") would be consistent with the snprintf_P call just below and save a few bytes on ESP8266.
♻️ Optional tweak
-      snprintf(dest, maxLen + 1, "%s %u", base, (unsigned)ump.palIndex); // palette name is um name + index (e.g. "AudioReactive 1")
+      snprintf_P(dest, maxLen + 1, PSTR("%s %u"), base, (unsigned)ump.palIndex); // palette name is um name + index (e.g. "AudioReactive 1")
       return strlen(dest);
     }
     if (mode >= FIXED_PALETTE_COUNT && mode <= WLED_CUSTOM_PALETTE_ID_BASE) {
       // user custom palette (IDs FIXED_PALETTE_COUNT up to WLED_CUSTOM_PALETTE_ID_BASE=200)
-      snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
-      dest[maxLen] = '\0';
+      snprintf_P(dest, maxLen + 1, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/util.cpp` around lines 320 - 340, Align the two branches to use the
same buffer-size convention and place format strings in flash: change
snprintf(dest, maxLen + 1, "%s %u", (unsigned)...) in the usermod-palette branch
to snprintf_P(dest, maxLen + 1, PSTR("%s %u"), base, (unsigned)ump.palIndex)
(referencing usermodPalettes and ump.name), and change the custom-palette
snprintf_P call to pass maxLen + 1 as the size (snprintf_P(dest, maxLen + 1,
PSTR("~ Custom %d ~"), ...)) so both branches consistently use the full caller
buffer size; keep the existing explicit null-termination logic as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@usermods/audioreactive/audio_reactive.cpp`:
- Around line 2184-2188: The subtraction currently casts
removeUsermodPalettes(_name) to int8_t which can wrap if the returned size_t
ever exceeds 127; instead compute the removal in a wider signed type and then
adjust palettes safely: call removeUsermodPalettes(_name) into a size_t or int
variable (e.g., removed), subtract that from palettes using a signed conversion,
and clamp palettes to >= 0 (and optionally <= MAX_PALETTES) without relying on
an int8_t cast; update the removeAudioPalettes function to use these symbols:
removeAudioPalettes, removeUsermodPalettes, and palettes.

In `@wled00/util.cpp`:
- Around line 320-340: Align the two branches to use the same buffer-size
convention and place format strings in flash: change snprintf(dest, maxLen + 1,
"%s %u", (unsigned)...) in the usermod-palette branch to snprintf_P(dest, maxLen
+ 1, PSTR("%s %u"), base, (unsigned)ump.palIndex) (referencing usermodPalettes
and ump.name), and change the custom-palette snprintf_P call to pass maxLen + 1
as the size (snprintf_P(dest, maxLen + 1, PSTR("~ Custom %d ~"), ...)) so both
branches consistently use the full caller buffer size; keep the existing
explicit null-termination logic as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a5f1544a-46cc-4085-ae50-2bf1aaf9b930

📥 Commits

Reviewing files that changed from the base of the PR and between 449069a and 120da32.

📒 Files selected for processing (4)
  • usermods/audioreactive/audio_reactive.cpp
  • wled00/colors.h
  • wled00/util.cpp
  • wled00/wled.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • wled00/wled.h
  • wled00/colors.h

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.

🧹 Nitpick comments (1)
wled00/util.cpp (1)

337-338: Use maxLen + 1 consistently for the custom placeholder branch.

This path is safe today, but it unnecessarily trims one extra character versus the rest of extractModeName() behavior.

♻️ Proposed tweak
-      snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
+      snprintf_P(dest, maxLen + 1, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
       dest[maxLen] = '\0';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/util.cpp` around lines 337 - 338, The custom placeholder branch in
extractModeName() calls snprintf_P(dest, maxLen, ...) and then sets dest[maxLen]
= '\0', which trims one character compared to other branches; change the
snprintf_P call to use maxLen + 1 (snprintf_P(dest, maxLen + 1, ...)) and keep
null-termination consistent by ensuring the terminator index matches (i.e.,
dest[maxLen] = '\0' remains correct after using maxLen + 1). Update references
to WLED_CUSTOM_PALETTE_ID_BASE and the variables dest/maxLen accordingly so this
branch matches the buffer sizing used elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@wled00/util.cpp`:
- Around line 337-338: The custom placeholder branch in extractModeName() calls
snprintf_P(dest, maxLen, ...) and then sets dest[maxLen] = '\0', which trims one
character compared to other branches; change the snprintf_P call to use maxLen +
1 (snprintf_P(dest, maxLen + 1, ...)) and keep null-termination consistent by
ensuring the terminator index matches (i.e., dest[maxLen] = '\0' remains correct
after using maxLen + 1). Update references to WLED_CUSTOM_PALETTE_ID_BASE and
the variables dest/maxLen accordingly so this branch matches the buffer sizing
used elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c90ca8c-0736-4530-a926-5cc87650b5ba

📥 Commits

Reviewing files that changed from the base of the PR and between 449069a and 120da32.

📒 Files selected for processing (4)
  • usermods/audioreactive/audio_reactive.cpp
  • wled00/colors.h
  • wled00/util.cpp
  • wled00/wled.h

@DedeHai
Copy link
Copy Markdown
Collaborator Author

DedeHai commented Apr 29, 2026

@softhack007 any objections on merging this?

@netmindz
Copy link
Copy Markdown
Member

netmindz commented May 3, 2026

Does it need to be any more complex than this?

names.patch

@netmindz
Copy link
Copy Markdown
Member

netmindz commented May 3, 2026

just trying to test and can't actually see either the custom nor usermod show up, don't see any errors in the console either

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/audioreactive/audio_reactive.cpp`:
- Around line 1960-1963: The current guard only calls createAudioPalettes() when
palettes == 0 which leaves partially-created palettes stuck; change the
condition to detect incomplete palettes and retry creation (e.g., use palettes <
WLED_MAX_USERMOD_PALETTES or a similar “not full” check) so
createAudioPalettes() runs whenever addPalettes is true and the palette count is
below the configured maximum; apply the same fix to the other similar check
around createAudioPalettes() later in the file so both spots use palettes <
WLED_MAX_USERMOD_PALETTES (or equivalent) instead of palettes == 0.
🪄 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: 734b3981-f62f-4457-becb-9d916ff45098

📥 Commits

Reviewing files that changed from the base of the PR and between 120da32 and 7872c63.

📒 Files selected for processing (3)
  • usermods/audioreactive/audio_reactive.cpp
  • wled00/colors.h
  • wled00/util.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/colors.h

Comment on lines +1960 to 1963
if (initDone && enabled && addPalettes && palettes==0) {
// if palettes were removed during JSON call re-add them
createAudioPalettes();
}
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 | 🟡 Minor | ⚡ Quick win

Partial palette creation can get stuck and never backfill.

When WLED_MAX_USERMOD_PALETTES is temporarily near full, this can create only some AR palettes, set palettes > 0, and then permanently skip adding the missing ones (if (palettes) return; + palettes == 0 gate).

Suggested fix
 void AudioReactive::createAudioPalettes(void) {
-  if (palettes) return;
+  bool present[MAX_PALETTES] = {false};
+  palettes = 0;
+  for (const auto &ump : usermodPalettes) {
+    if (ump.name != _name || ump.palIndex >= MAX_PALETTES) continue;
+    if (!present[ump.palIndex]) {
+      present[ump.palIndex] = true;
+      palettes++;
+    }
+  }
+  if (palettes >= MAX_PALETTES) return;
   DEBUG_PRINTLN(F("Adding audio palettes."));
   static const char *const palNames[MAX_PALETTES] PROGMEM = {_palName0, _palName1, _palName2};
-  for (int i=0; i<MAX_PALETTES; i++) {
+  for (uint8_t i = 0; i < MAX_PALETTES; i++) {
+    if (present[i]) continue;
     if (usermodPalettes.size() < WLED_MAX_USERMOD_PALETTES) {
       usermodPalettes.push_back({CRGBPalette16(CRGB(BLACK)), _name, (uint8_t)i, palNames[i]}); // start black, filled each loop by fillAudioPalettes()
       palettes++;
       DEBUG_PRINTLN(palettes);
     } else break;
   }
 }

 void onStateChange(uint8_t callMode) override {
-  if (initDone && enabled && addPalettes && palettes==0) {
+  if (initDone && enabled && addPalettes && palettes < MAX_PALETTES) {
     // if palettes were removed during JSON call re-add them
     createAudioPalettes();
   }
 }

Also applies to: 2194-2203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/audioreactive/audio_reactive.cpp` around lines 1960 - 1963, The
current guard only calls createAudioPalettes() when palettes == 0 which leaves
partially-created palettes stuck; change the condition to detect incomplete
palettes and retry creation (e.g., use palettes < WLED_MAX_USERMOD_PALETTES or a
similar “not full” check) so createAudioPalettes() runs whenever addPalettes is
true and the palette count is below the configured maximum; apply the same fix
to the other similar check around createAudioPalettes() later in the file so
both spots use palettes < WLED_MAX_USERMOD_PALETTES (or equivalent) instead of
palettes == 0.

netmindz added 2 commits May 3, 2026 14:24
Palette display names now follow the format "UMName: palName" (e.g.
"AudioReactive: Audio Responsive Hue") when a usermod supplies a
specific palName, keeping the usermod name as a consistent prefix.
Fallback when palName is nullptr remains "UMName index".

Update AudioReactive palette names to the full descriptive names from
the original WLED-MM port: "Audio Responsive Ratio", "Audio Responsive
Hue", and "Audio Responsive Spectrum".
Since the display format is now "UMName: palName", shorten the palette
names to just "Ratio", "Hue", and "Spectrum" — rendering as
"AudioReactive: Ratio" etc. rather than "AudioReactive: Audio Responsive Ratio".
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/util.cpp`:
- Around line 344-349: The snprintf_P call in the custom-palette branch uses
maxLen as the size but the buffer is maxLen+1 (others use maxLen+1), and then
redundantly writes dest[maxLen] = '\0'; fix by passing maxLen + 1 to snprintf_P
(like the other branches) and remove the manual NUL assignment; update the call
involving snprintf_P(dest, maxLen + 1, PSTR("~ Custom %d ~"),
WLED_CUSTOM_PALETTE_ID_BASE - mode) and drop the dest[maxLen] = '\0' line so
snprintf_P handles termination.
🪄 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: 772d168f-965c-4f2b-b0e7-a66ce4745605

📥 Commits

Reviewing files that changed from the base of the PR and between 7872c63 and 8aa5501.

📒 Files selected for processing (2)
  • wled00/colors.h
  • wled00/util.cpp
✅ Files skipped from review due to trivial changes (1)
  • wled00/colors.h

Comment thread wled00/util.cpp
Comment on lines +344 to +349
if (mode >= FIXED_PALETTE_COUNT && mode <= WLED_CUSTOM_PALETTE_ID_BASE) {
// user custom palette (IDs FIXED_PALETTE_COUNT up to WLED_CUSTOM_PALETTE_ID_BASE=200)
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
dest[maxLen] = '\0';
return strlen(dest);
}
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 | 🟡 Minor | ⚡ Quick win

snprintf_P size argument is off-by-one — inconsistent with the rest of the function.

Line 346 passes maxLen as the buffer size, while the actual buffer is maxLen + 1 bytes. Lines 337 and 340 both correctly pass maxLen + 1. The discrepancy means this path can write one fewer character than the buffer permits. For the ~ Custom %d ~ template the result is ≤ 15 chars in practice, so no truncation occurs today, but it's a correctness inconsistency. The redundant dest[maxLen] = '\0' on line 347 can also be removed once the size is corrected (snprintf always NUL-terminates within its size argument).

🛠️ Proposed fix
-    snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
-    dest[maxLen] = '\0';
+    snprintf_P(dest, maxLen + 1, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/util.cpp` around lines 344 - 349, The snprintf_P call in the
custom-palette branch uses maxLen as the size but the buffer is maxLen+1 (others
use maxLen+1), and then redundantly writes dest[maxLen] = '\0'; fix by passing
maxLen + 1 to snprintf_P (like the other branches) and remove the manual NUL
assignment; update the call involving snprintf_P(dest, maxLen + 1, PSTR("~
Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode) and drop the dest[maxLen] =
'\0' line so snprintf_P handles termination.

@DedeHai
Copy link
Copy Markdown
Collaborator Author

DedeHai commented May 3, 2026

Does it need to be any more complex than this?

names.patch

we can roll with this for now, I was thinking of adding naming feature for other custom palettes too but did not look into it. Since UM and custom are now independent arrays, there should not be any conflict when adding that feature at a later point.

@netmindz netmindz merged commit bef897f into wled:main May 3, 2026
258 checks passed
@netmindz
Copy link
Copy Markdown
Member

netmindz commented May 3, 2026

picked into 16_x

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

Labels

enhancement usermod usermod related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants